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


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:


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:

  1. 30 min idle → Cron pausiert den Container.
  2. Vor dem PausierensnapshotAndPush auto-committet dirty Trees in jedem Repo unter /workspace/ und pusht sie zur Bot-Org.
  3. Nächste Chat-NachrichtensureAwake unpausiert den Container (~1 s), dann feuert fetchAll im Hintergrund, damit der origin/main-Pointer jedes Repos frisch ist.
  4. 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:

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:

  1. subscription.plan === 'cloud' im Billing-Pill des User-Dropdowns?
  2. /cloud/status gibt status: 'ready' oder 'paused' zurück?
  3. 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