ByteClaw – Go-Agent-Backend + Android-Client
Find a file
2026-10-31 11:56:53 +01:00
android app 0.3.0: Login/Registrierung, Abmelden, 401 → Login; Previews + Landingpage v0.3.0 2026-10-31 11:56:53 +01:00
cmd/byteclaw auth: Registrierung, Login, Logout, per-user Sessions (Backend) 2026-10-31 11:28:17 +01:00
internal auth: Registrierung, Login, Logout, per-user Sessions (Backend) 2026-10-31 11:28:17 +01:00
preview app 0.3.0: Login/Registrierung, Abmelden, 401 → Login; Previews + Landingpage v0.3.0 2026-10-31 11:56:53 +01:00
site app 0.3.0: Login/Registrierung, Abmelden, 401 → Login; Previews + Landingpage v0.3.0 2026-10-31 11:56:53 +01:00
site-preview site-preview: live Firebase screenshots 2026-10-26 17:40:24 +01:00
.dockerignore build: Makefile with cross-builds (linux/termux arm64, armv7), non-root distroless Dockerfile, example config 2026-10-25 03:15:32 +01:00
.firebaserc hosting: deploy to Firebase byteclaw-app-65807, APK via GitHub release 2026-10-26 17:40:10 +01:00
.gitignore fix: track Android data package (was hidden by root .gitignore 'data/') 2026-10-25 04:29:42 +01:00
config.example.yaml auth: Registrierung, Login, Logout, per-user Sessions (Backend) 2026-10-31 11:28:17 +01:00
Dockerfile chore: rename project to ByteClaw (module, binary, env prefix BYTECLAW_, Android package/app/theme) 2026-10-25 04:28:22 +01:00
firebase.json hosting: deploy to Firebase byteclaw-app-65807, APK via GitHub release 2026-10-26 17:40:10 +01:00
go.mod auth: Registrierung, Login, Logout, per-user Sessions (Backend) 2026-10-31 11:28:17 +01:00
go.sum auth: Registrierung, Login, Logout, per-user Sessions (Backend) 2026-10-31 11:28:17 +01:00
Makefile chore: rename project to ByteClaw (module, binary, env prefix BYTECLAW_, Android package/app/theme) 2026-10-25 04:28:22 +01:00
README.md app 0.3.0: Login/Registrierung, Abmelden, 401 → Login; Previews + Landingpage v0.3.0 2026-10-31 11:56:53 +01:00

ByteClaw

Schlanker, selbst gehosteter KI-Assistent: Go-Backend (ein statisches Binary, kein CGO) + nativer Android-Client (Kotlin, Jetpack Compose).

Architektur

Android-App (Compose)                     Go-Backend (cmd/byteclaw)
 ├─ Einstellungen (EncryptedSharedPrefs)   ├─ internal/server  HTTP-API + WebSocket, Bearer-Token, Origin-Check
 ├─ Chat  ──── WebSocket /api/ws ───────▶  ├─ internal/agent   Agent-Loop mit Tool-Calling, Lock pro Session
 └─ Sessions ─ REST /api/sessions ──────▶  ├─ internal/llm     Provider-Interface + OpenAI-kompatibler Streaming-Provider
                                           ├─ internal/tools   time_now, web_fetch, file_read/_write/_list
                                           ├─ internal/store   SQLite (modernc.org/sqlite, pure Go)
                                           └─ internal/config  YAML/JSON + Umgebungsvariablen

Ablauf einer Nachricht: Client → /api/ws → Agent speichert User-Nachricht → LLM (Streaming) → bei Tool-Calls werden Tools ausgeführt, Ergebnisse gespeichert und das LLM erneut gefragt (max. agent.max_iterations) → Tokens werden live als delta-Events gestreamt, am Ende done.

Abhängigkeiten des Backends: gopkg.in/yaml.v3, modernc.org/sqlite, github.com/coder/websocket.

Sichere Defaults

  • Konten: POST /api/auth/register|login|logout, GET /api/auth/me; Passwörter als bcrypt-Hash, Tokens zufällig + gehasht gespeichert, 30 Tage gültig, Logout widerruft. Sessions sind pro Nutzer getrennt. Login-Rate-Limit pro IP. allow_registration (Standard an). Optionaler statischer auth_token (mind. 16 Zeichen) = Admin, sieht alle Sessions; Vergleich in konstanter Zeit.
  • Lauscht standardmäßig nur auf 127.0.0.1:8787.
  • Browser-Origins: nur gleicher Host oder explizit in allowed_origins; * muss bewusst gesetzt werden. Native Clients (ohne Origin-Header) brauchen trotzdem das Token.
  • TLS-Zertifikatsprüfung ist überall an (LLM-Provider, web_fetch); es gibt keinen Schalter zum Abschalten.
  • web_fetch: nur http(s), Timeout, Größenlimit, max. 5 Redirects, blockiert Loopback/Private/Link-Local/CGNAT-Adressen beim Verbindungsaufbau (schützt auch gegen DNS-Rebinding und Redirect-Tricks). Abschaltbar per allow_private.
  • Datei-Tools sind per os.Root auf den Workspace beschränkt (kein .., keine absoluten Pfade, keine Symlink-Ausbrüche) und größenbegrenzt.
  • Shell-Tool standardmäßig AUS (tools.shell.enabled). Wenn an: nur Befehle aus allowed_commands (Interpreter/Netzwerk-/Shell-Programme wie sh, bash, env, xargs, python, curl, git sind nie erlaubt), jedes Glied von |, &&, ||, ; wird geprüft, $, Backticks, <( ), Umleitungen, &, Klammern werden abgelehnt, keine Pfade außerhalb des Workspace (/, ~, ..), Ausführung ohne sh -c direkt per exec im Workspace, Timeout, Output-Limit, bereinigte Umgebung (nur PATH, HOME=Workspace, LANG, TERM – keine API-Keys/Token).
  • Docker: distroless, User 65532 (nonroot), Go-Version fixiert.
  • Android: Release-Builds erlauben nur HTTPS/WSS; Klartext (http/ws) nur im Debug-Build für lokale Entwicklung. Token/URL verschlüsselt per Android Keystore.

Quickstart Backend

cp config.example.yaml config.yaml
export BYTECLAW_AUTH_TOKEN=$(openssl rand -hex 32)
export BYTECLAW_LLM_API_KEY=sk-...           # OpenAI / OpenRouter; für Ollama leer lassen
make run                                     # = go build + ./bin/byteclaw -config config.yaml

Provider-Presets (llm.provider bzw. BYTECLAW_LLM_PROVIDER). Ein Preset füllt base_url und model, wenn sie leer sind; explizite Werte haben Vorrang. Der API-Key fällt auf die genannte Umgebungsvariable zurück. Alle nutzen Authorization: Bearer <key> und /chat/completions.

provider base_url Default-Modell Key-Env Quelle
openai https://api.openai.com/v1 gpt-4o-mini OPENAI_API_KEY platform.openai.com
openrouter https://openrouter.ai/api/v1 openai/gpt-4o-mini OPENROUTER_API_KEY openrouter.ai/docs
ollama http://localhost:11434/v1 llama3.1 – ollama docs
dahl https://inference.dahl.global/v1 MiniMaxAI/MiniMax-M2.7 DAHL_API_KEY https://docs.dahl.global/
gonkarouter https://api.gonkarouter.io/v1 MiniMaxAI/MiniMax-M2.7 GONKAROUTER_API_KEY https://gonkarouter.io/docs
custom (Pflicht) (Pflicht) – –

Dahl (dahl.global) und GonkaRouter sind OpenAI-kompatible Inferenz-Dienste für offene Modelle auf dem dezentralen Gonka-GPU-Netz. Weitere Modelle (Stand 25.10.2026): zai-org/GLM-5.3-Flash, deepseek-ai/DeepSeek-V4-Flash-0731. Aktuelle IDs immer über GET <base_url>/models prüfen. Bei Dahl braucht der Key ein zugewiesenes Token-Guthaben (sonst HTTP 402).

llm:
  provider: dahl          # oder gonkarouter
  model: zai-org/GLM-5.3-Flash   # optional

GET /api/info liefert Provider, Modell und base_url des Servers (ohne Key); die Android-App zeigt das bei „Verbindung testen" an.

Test:

curl localhost:8787/healthz
curl -H "Authorization: Bearer $BYTECLAW_AUTH_TOKEN" -d '{"text":"Wie spät ist es in Berlin?"}' localhost:8787/api/chat

API

Methode Pfad Zweck
GET /healthz Health (ohne Auth)
GET /api/info Version, Provider, Modell (ohne Secrets)
GET /api/sessions Sessions (neueste zuerst)
POST /api/sessions Session anlegen {"title":""}
GET/PATCH/DELETE /api/sessions/{id} Lesen / umbenennen / löschen
GET /api/sessions/{id}/messages Verlauf
POST /api/chat Nicht-streamend {"session_id"?, "text"}
GET /api/ws WebSocket-Streaming
POST /api/sessions/{id}/cancel Laufende Antwort abbrechen
POST /api/sessions/{id}/summarize Zusammenfassung manuell auslösen
GET /api/sessions/{id}/summary Letzte Zusammenfassung
GET /api/providers Provider-Liste + aktiv (ohne Keys)
PUT /api/providers/active Aktiven Provider/Modell setzen {"provider","model"?}
GET /api/providers/{name}/models Modellliste vom Provider (/v1/models)
PUT /api/sessions/{id}/provider Provider nur für diese Session ({"provider":""} = Standard)

Alle /api/* brauchen Authorization: Bearer <token>. WebSocket: Client sendet {"type":"message","session_id":"<optional>","text":"..."}; Server sendet Events session, summarized, delta, tool_call, tool_result, done, cancelled oder error ({"type","text","tool","args","call_id","session_id"}). {"type":"ping"} → pong. Abbrechen: {"type":"cancel","session_id":"…"} über denselben WebSocket oder POST /api/sessions/{id}/cancel. Der Abbruch läuft per Context bis in den HTTP-Request zum Provider; die bis dahin gestreamte Teilantwort wird als Assistant-Nachricht mit "cancelled":true gespeichert.

Provider umschalten

Alle Presets (openai, openrouter, ollama, dahl, gonkarouter) plus der in llm.* konfigurierte Provider stehen zur Auswahl. Keys kommen nur aus Env (DAHL_API_KEY, GONKAROUTER_API_KEY, …) oder llm.providers.<name>.api_key und verlassen den Server nie; die App sieht nur configured: true/false. Die Auswahl wird in SQLite gespeichert und überlebt Neustarts; pro Session kann ein anderer Provider gesetzt werden.

Lange Sessions

agent.summarize: ab max_messages (Standard 40) oder ~max_tokens (12000, Zeichen/4) noch nicht zusammengefasster Nachrichten fasst das aktive LLM die älteren Nachrichten zusammen (die neuesten keep_recent bleiben wörtlich, Schnitt immer vor einer User-Nachricht). Die Zusammenfassung liegt in der Tabelle summaries, die Originalnachrichten bleiben erhalten.

Build, Tests, Cross-Builds

make            # vet + test + build
make race       # Tests mit Race-Detector (benötigt CGO)
make cross      # dist/: linux-amd64, linux-arm64, linux-armv7, termux-arm64, termux-armv7
make docker

Termux: byteclaw-termux-arm64 ist ein GOOS=android-Build; für armv7 wird ein statisches linux/arm-Binary gebaut, da android/arm ohne NDK/CGO nicht linkbar ist. In Termux z. B. nach $PREFIX/bin kopieren, chmod +x und starten.

Docker

docker build -t byteclaw .
docker run -d -p 127.0.0.1:8787:8787 -v byteclaw-data:/data \
  -e BYTECLAW_AUTH_TOKEN=... -e BYTECLAW_LLM_API_KEY=... byteclaw

Im Container lauscht der Server auf 0.0.0.0:8787; nach außen nur hinter einem TLS-Reverse-Proxy (Caddy, Traefik) oder in einem VPN (z. B. Tailscale) freigeben.

Android-Client

android/
 ├─ app/src/main/java/dev/byteclaw/client/
 │   ├─ MainActivity.kt          Navigation (Chat, Sessions, Einstellungen)
 │   ├─ data/ByteClawApi.kt      OkHttp REST + WebSocket-Flow
 │   ├─ data/SettingsStore.kt    EncryptedSharedPreferences
 │   ├─ data/Models.kt           kotlinx.serialization
 │   └─ ui/                      ChatViewModel, Compose-Screens
 └─ gradle/libs.versions.toml    Versionskatalog (AGP 8.7.3, Kotlin 2.0.21, compileSdk 35, minSdk 26)

Build (JDK 17 + Android SDK, ANDROID_HOME oder local.properties mit sdk.dir):

cd android
./gradlew assembleDebug        # app/build/outputs/apk/debug/app-debug.apk
./gradlew testDebugUnitTest
./gradlew assembleRelease      # signiert, wenn Keystore-Daten vorhanden sind (sonst unsigniert)

Release-Signing

Die signingConfig liest Keystore-Daten aus Umgebungsvariablen (BYTECLAW_KEYSTORE_FILE, BYTECLAW_KEYSTORE_PASSWORD, BYTECLAW_KEY_ALIAS, BYTECLAW_KEY_PASSWORD) oder aus android/keystore.properties (Vorlage: keystore.properties.example). keystore.properties, *.jks und *.keystore sind gitignored – Keystore niemals ins Repo.

mkdir -p ~/byteclaw-keys && chmod 700 ~/byteclaw-keys
keytool -genkeypair -keystore ~/byteclaw-keys/byteclaw-release.jks -storetype PKCS12 \
  -alias byteclaw -keyalg RSA -keysize 3072 -validity 10000 -dname "CN=ByteClaw, C=DE"
cp android/keystore.properties.example android/keystore.properties   # Passwörter eintragen
cd android && ./gradlew assembleRelease
$ANDROID_HOME/build-tools/35.0.0/apksigner verify --print-certs app/build/outputs/apk/release/app-release.apk

Keystore und Passwörter sicher sichern: Ohne denselben Schlüssel lassen sich Updates nicht installieren.

App-Icon

Adaptive Icon als Vektor (res/drawable/ic_launcher_{background,foreground,monochrome}.xml, mipmap-anydpi-v26): drei Krallen-Kratzer in Claw-Orange plus Byte-Bits in Türkis auf dunklem Navy – dieselben Markenfarben wie im Compose-Theme (ui/Theme.kt, res/values/colors.xml). Da minSdk 26 ist, deckt der Vektor alle Dichten ab; die Monochrom-Ebene sorgt für Themed Icons ab Android 13. Vorschau: preview/icon.png.

In der App unter Einstellungen Server-URL (https://…; im Debug-Build auch http://10.0.2.2:8787 für den Emulator) und Token eintragen, „Verbindung testen", speichern.

Nächste Schritte

  • CI (GitHub Actions) für Go + Android
  • Weitere Provider (Anthropic nativ)
  • App: Provider pro Session wählen (API existiert schon)
  • Optional: Einrichtung per QR-Code (URL + Token)

Vorschau

Screenshots mit Demo-Daten (Paparazzi, ohne Emulator) liegen in preview/ (hell/dunkel): Chat mit Stopp-Button, Sessions, Einstellungen, Provider-Auswahl, icon.png. Neu erzeugen: cd android && ./gradlew recordPaparazziDebug.