Cloud Chat Routing — Anleitung
Phase 70. Wenn du mit einem Worker aus dem CRM oder Telegram chattest, läuft diese Unterhaltung jetzt in deinem Hetzner Cloud-Container und nicht mehr auf dem Master-Server. Klapp den Laptop zu, wechsel zu Telegram, arbeite weiter — die Sitzung, die Dateien und die offenen Repos leben alle in deinem Cloud-Workspace.
TL;DR
- Cloud-Plan-Nutzer: Jede Chat-Nachricht, die dein Worker bearbeitet, spawnt
claude -pin/workspace/<project>in deinem Container. - Free / Starter-Nutzer: unverändert — Chat läuft weiterhin auf dem Master-Server.
- Container pausiert? Die erste Nachricht weckt ihn (~1 Sekunde extra) und setzt die Unterhaltung dort fort, wo du aufgehört hast.
- Drei neue Oberflächen zeigen dir, dass es funktioniert: ein
Cloud-Pill im Header, eineclaude spawn: container/ready-Zeile im Worker-Log und Änderungen, die in/workspace/<project>statt in/opt/repos/<project>erscheinen.
Warum das wichtig ist
Phase 60 lieferte Standard Cloud als "Always-on Linux-Box mit Claude Code".
Phase 69 verdrahtete deine Repos mit der Bot-Org. Aber bis Phase 70 spawnte
der Chat-Workflow claude -p immer noch auf dem Master-VPS — dein
Cloud-Container lag brach und deine Chat-Arbeit lebte auf dem Master.
Phase 70 leitet Chat in den Container, damit das Wertversprechen tatsächlich hält:
- Laptop zuklappen, von Telegram weitermachen. Gleiche Sitzung, gleiche Dateien, gleicher Repo-Stand. Kein "Ich musste warten, bis ich wieder am Laptop war".
- Änderungen landen im Container. Deine Bot-Org-Repos unter
/workspace/erhalten die Änderungen; Auto-Commit bei Idle + Push (Phase 69.3 + #349) schickt sie zu GitHub;arc pullauf deinem Laptop synct sie zurück. - Kein Cloudflare-Roundtrip. Worker-Output streamt von Container ↔ Master ↔ Chat — kein öffentlicher Netzwerk-Hop im inneren Loop.
Wie das Routing funktioniert
Jede Chat-Nachricht durchläuft diesen Entscheidungsbaum:
chat msg arrives → child-bot on master
│
▼
getWorkerTarget(ownerChatId)
│
├─ user.plan === 'cloud' AND container.status ∈ (ready, paused) ?
│ │ YES
│ ▼
│ ensureAwake(target) ← Phase 70.4: wake if paused (~1s)
│ │
│ ▼
│ spawnWorker → docker exec ← Phase 70.2: --workdir /workspace/<slug>
│ (Phase 70.3 maps project_name → slug)
│ │
│ ▼
│ bash -lc 'exec "$@"' bash claude -p "..."
│ ↑ login shell sources ~/.profile so
│ ANTHROPIC_API_KEY env reaches claude
│
└─ otherwise → Bun.spawn(["claude", ...]) on master (today's default)
Diese eine Entscheidung wird einmal pro Nachricht getroffen. Das Worker-Log gibt eine einzige Zeile aus, die dir das gewählte Ziel verrät:
claude spawn: container/ready
claude spawn: container/ready (woke from paused)
claude spawn: local
Wenn du dich je fragst, wo deine letzte Nachricht ausgeführt wurde — das ist die Zeile, nach der du grep'st.
So überprüfst du, dass es funktioniert
1. Header-Pill
Nachdem dein Cloud-Workspace bereitgestellt wurde, Einstellungen → CRM neu laden und schau oben rechts in den Header. Du solltest eines davon sehen:
| Pill | Bedeutung |
|---|---|
Cloud (grüner Punkt) |
Container lebt, Chat wird hineingeroutet |
Cloud · asleep (grauer Punkt) |
Container pausiert; nächste Chat-Nachricht weckt ihn |
| (nichts) | Noch kein Container bereitgestellt |
Der Pill pollt den Container-Status alle 30 Sekunden, deshalb hinkt er einem Wake-/Pause-Ereignis um bis zu eine halbe Minute hinterher.
2. Worker-Log
Wenn du Terminal-Zugriff auf den Master hast (tmux attach -t citadel-child
für den Arc OS-Dev-Bot), kannst du den Worker-Output verfolgen und bei jeder
Nachricht auf die Spawn-Zeile achten.
3. Dateien tatsächlich im Container
Öffne das Cloud-Terminal aus /cloud, dann:
cd /workspace/<your-project>
git log -3
ls -lh
Wenn hier Chat-gesteuerte Änderungen auftauchen (und arc cloud sync von
deinem Laptop behind mit der korrekten Commit-Anzahl meldet), ist das
Routing korrekt verdrahtet.
Lebenszyklus: pause, wake, push, fetch
Der Phase-69-Lebenszyklus gilt weiterhin:
- 30 min idle → Cron pausiert den Container.
- Vor dem Pausieren →
snapshotAndPushauto-committet dirty Trees in jedem Repo unter/workspace/und pusht sie zur Bot-Org. - Nächste Chat-Nachricht →
ensureAwakeunpausiert den Container (~1 s), dann feuertfetchAllim Hintergrund, damit derorigin/main-Pointer jedes Repos frisch ist. arc pull <project>auf deinem Laptop liest diese Auto-Commits.
Die Kosten von Pause/Wake sind in die Latenz der nächsten Chat-Nachricht eingefaltet — du musst nicht darüber nachdenken.
Sitzungskontinuität
Claude Code speichert den Konversationsverlauf unter
~/.claude/projects/<cwd-hash>/<session-id>.jsonl. Im Container liegt dieses
Verzeichnis auf einem Volume-Mount und überlebt damit docker pause /
docker unpause. Solange dein Chat dasselbe Projekt anvisiert, findet
claudes --resume <session-id> nach dem Wake dieselbe JSONL und führt den
Thread fort.
Ein Caveat: cwd-Hash ändert sich zwischen local ↔ cloud
Der Hash wird vom Pfad des Arbeitsverzeichnisses abgeleitet. Auf dem Master
läuft claude in /opt/repos/<slug>; im Container in /workspace/<slug>.
Zwei verschiedene Pfade → zwei verschiedene Hashes → zwei verschiedene
Konversationsverläufe.
Was das in der Praxis bedeutet:
- Erstes Upgrade von Free/Starter zu Cloud: Dein bisheriger Chat-Verlauf liegt auf dem Master und bleibt dort. Die erste Cloud-Nachricht startet einen neuen Konversations-Thread.
- Steady-State-Cloud-Nutzer: Jede Sitzung läuft nahtlos über Pause/Wake hinweg weiter. Keine Aktion nötig.
- Falls du wieder auf Local downgradest: Der In-Container-Verlauf bleibt im Container; neue Nachrichten routen frisch auf den Master.
Wir haben überlegt, /opt/repos → /workspace auf dem Master zu symlinken,
um die Hashes ausgerichtet zu halten, aber das vermischte die beiden
Dateibäume und brach die Master-Bots, die auf Free-Plänen laufen. Zwei
Verläufe sind das kleinere Übel.
Troubleshooting
"claude spawn: local", aber ich bin auf dem Cloud-Plan
Prüfe in dieser Reihenfolge:
subscription.plan === 'cloud'im Billing-Pill des User-Dropdowns?/cloud/statusgibtstatus: 'ready'oder'paused'zurück?- Hast du das Projekt NACH dem Provisioning erstellt? Container klonen nur Projekt-Repos, die zum Provisioning-Zeitpunkt existierten (Phase 69.3). Provisioniere neu oder triggere das Bootstrap erneut, um neu erstellte Projekte aufzunehmen.
Chat hängt ~5 Sekunden, dann antwortet er
Das ist der erste Wake nach längerer Idle-Zeit. Der Wake dauert ~1 s; der Rest ist claudes eigenes Startup + erste Inferenz. Nachfolgende Nachrichten bei warm bleibendem Container haben normale Latenz.
"Not logged in" von claude
Phase 70.5 hat einen Bug behoben, bei dem ~/.bashrc für nicht-interaktive
Shells früh returnte, sodass ANTHROPIC_API_KEY claude nie erreichte. Fix:
Der Key lebt jetzt in ~/.profile (gesourct vom Login-Shell-Wrapper). Wenn
du mitten in Phase 70 upgegradet bist und dein Token nie neu gespeichert
hast, geh zu /cloud Schritt 1 und füge dein sk-ant-api03-… erneut ein.
Neu gespeicherte Tokens landen in der richtigen Datei.
Container meldet stundenlang paused nach einer Chat-Nachricht
Das 30-Sekunden-Polling am Header-Pill ist eine Ursache. Die andere: Wenn
wakeContainer() still fehlschlug, liest die DB-Zeile ready, aber Docker
sagt paused. Führe arc cloud sync <project> aus — es zeigt die Divergenz
in der Matrix.
Siehe auch
- standard-cloud.md — Container-Provisioning + Web-Terminal
- cloud-repos.md — die 3 Repo-Typen und wie
/workspace/aufgebaut ist - arc-cli-reference.md —
arc push/arc pull/arc cloud sync docs/architecture/PHASE_69_CLOUD_REPO_MODEL.md— die Repo-seitige Architektur-Spec