Cloud Chat Routing — przewodnik
Phase 70. Kiedy rozmawiasz z workerem z CRM lub Telegram, ta konwersacja działa teraz wewnątrz twojego kontenera Hetzner Cloud, a nie na serwerze master. Zamknij laptopa, przełącz się na Telegram, pracuj dalej — sesja, pliki i otwarte repozytoria żyją w twoim workspace Cloud.
TL;DR
- Użytkownicy planu Cloud: każda wiadomość chat obsługiwana przez workera spawnuje
claude -pwewnątrz/workspace/<project>w kontenerze. - Użytkownicy Free / Starter: bez zmian — chat nadal działa na serwerze master.
- Kontener wstrzymany? Pierwsza wiadomość budzi go (~1 sekunda dodatkowo) i wznawia konwersację od miejsca, w którym przerwałeś.
- Trzy nowe powierzchnie informują, że to działa: pill
Cloudw nagłówku, liniaclaude spawn: container/readyw logu workera oraz edycje pojawiające się w/workspace/<project>zamiast/opt/repos/<project>.
Dlaczego to ważne
Phase 60 dostarczył Standard Cloud jako "zawsze włączoną maszynę Linux z Claude Code".
Phase 69 podłączył twoje repozytoria do bot org. Ale do Phase 70 workflow chatu
nadal spawnował claude -p na master VPS — twój kontener Cloud siedział bezczynnie,
a praca chatowa żyła na masterze.
Phase 70 rutuje chat do kontenera, żeby propozycja wartości faktycznie się ziściła:
- Zamknij laptopa, kontynuuj z Telegram. Ta sama sesja, te same pliki, ten sam stan repozytorium. Bez "musiałem poczekać, aż wrócę do laptopa".
- Edycje lądują w kontenerze. Repozytoria bot org pod
/workspace/otrzymują zmiany; auto-commit przy idle + push (Phase 69.3 + #349) wysyła je na GitHub;arc pullna laptopie ściąga je w dół. - Brak round-tripu przez Cloudflare. Output workera streamuje się z kontenera ↔ master ↔ chat — bez publicznego hopu sieciowego w pętli wewnętrznej.
Jak działa routing
Każda wiadomość chat przechodzi przez to drzewo decyzyjne:
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)
Ta pojedyncza decyzja zapada raz na wiadomość. Log workera emituje pojedynczą linię informującą o wybranym celu:
claude spawn: container/ready
claude spawn: container/ready (woke from paused)
claude spawn: local
Jeśli kiedykolwiek zastanawiasz się, gdzie uruchomiła się twoja ostatnia wiadomość, to ta linia, której powinieneś szukać.
Jak zweryfikować, że to działa
1. Pill w nagłówku
Po sprovisionowaniu twojego Cloud Workspace, Ustawienia → przeładuj CRM i spójrz na nagłówek u góry po prawej. Powinieneś zobaczyć jedno z:
| Pill | Znaczenie |
|---|---|
Cloud (zielona kropka) |
Kontener żyje, chat zrutuje się do niego |
Cloud · asleep (szara kropka) |
Kontener wstrzymany; następna wiadomość chat go obudzi |
| (nic) | Brak sprovisionowanego kontenera |
Pill odpytuje status kontenera co 30 sekund, więc opóźnia zdarzenie wake/pause nawet o pół minuty.
2. Log workera
Jeśli masz dostęp do terminala mastera (tmux attach -t citadel-child
dla bota dev Arc OS), tail output workera i obserwuj linię spawn
przy każdej wiadomości.
3. Pliki faktycznie wewnątrz kontenera
Otwórz terminal Cloud z /cloud, następnie:
cd /workspace/<your-project>
git log -3
ls -lh
Jeśli edycje wywołane chatem pojawiają się tutaj (a arc cloud sync z twojego laptopa
flaguje behind z odpowiednią liczbą commitów), routing jest podłączony prawidłowo.
Cykl życia: pause, wake, push, fetch
Cykl życia z Phase 69 nadal obowiązuje:
- Idle 30 min → cron wstrzymuje kontener.
- Przed pause →
snapshotAndPushauto-commituje brudne drzewa w każdym repozytorium pod/workspace/i pushuje je do bot org. - Następna wiadomość chat →
ensureAwakeodpauzowuje kontener (~1 s), następnie odpalafetchAllw tle, żeby wskaźnikorigin/mainkażdego repozytorium był świeży. arc pull <project>na twoim laptopie czyta te auto-commity.
Koszt pause/wake jest wkalkulowany w latency następnego chatu — nie musisz o tym myśleć.
Ciągłość sesji
Claude Code przechowuje historię konwersacji w ~/.claude/projects/<cwd-hash>/<session-id>.jsonl.
Wewnątrz kontenera ten katalog siedzi na volume mount, więc przeżywa
docker pause / docker unpause. Dopóki twój chat celuje w ten sam
projekt, --resume <session-id> claude'a znajduje ten sam JSONL po
wybudzeniu i kontynuuje wątek.
Jedna uwaga: hash cwd zmienia się między local ↔ cloud
Hash jest derivowany ze ścieżki katalogu roboczego. Na masterze claude
uruchamia się w /opt/repos/<slug>; w kontenerze uruchamia się w /workspace/<slug>.
Dwie różne ścieżki → dwa różne hashe → dwie różne historie
konwersacji.
Co to oznacza w praktyce:
- Pierwszy upgrade z Free/Starter na Cloud: twoja wcześniejsza historia chatu jest na masterze i tam zostaje. Pierwsza wiadomość cloud zaczyna nowy wątek konwersacji.
- Steady-state użytkownik Cloud: każda sesja kontynuuje się płynnie przez pause/wake. Żadne działanie nie jest wymagane.
- Jeśli downgradujesz z powrotem na local: historia wewnątrz kontenera zostaje w kontenerze; nowe wiadomości rutują się do mastera, startując od zera.
Rozważaliśmy symlinkowanie /opt/repos → /workspace na masterze, żeby utrzymać
hashe zgodne, ale to mieszałoby dwa drzewa plików i psułoby boty mastera,
które działają na planach Free. Dwie historie to mniejsze zło.
Troubleshooting
"claude spawn: local", ale jestem na planie Cloud
Sprawdź, w kolejności:
subscription.plan === 'cloud'w pillu billingowym dropdownu użytkownika?/cloud/statuszwracastatus: 'ready'lub'paused'?- Czy utworzyłeś projekt PO sprovisionowaniu? Kontenery klonują tylko repozytoria projektów, które istniały w momencie provisioningu (Phase 69.3). Re-provisionuj lub re-triggeruj bootstrap, żeby podchwycić nowo utworzone projekty.
Chat zawiesza się na ~5 sekund, potem odpowiada
To pierwsze wybudzenie po długim idle. Wake to ~1 s; reszta to własny startup claude'a + pierwsza inferencja. Kolejne wiadomości, gdy kontener pozostaje rozgrzany, mają normalne latency.
"Not logged in" od claude'a
Phase 70.5 naprawił bug, w którym ~/.bashrc wcześnie returnował dla nieinteraktywnych
shelli, więc ANTHROPIC_API_KEY nigdy nie docierał do claude'a. Fix: klucz żyje teraz
w ~/.profile (sourcowanym przez wrapper login shell). Jeśli upgrade'owałeś w trakcie
Phase-70 i nigdy nie zapisałeś ponownie tokena, idź do /cloud Krok 1, wklej swój
sk-ant-api03-… ponownie. Świeżo zapisane tokeny lądują we właściwym pliku.
Kontener pokazuje się jako wstrzymany przez godziny po wiadomości chat
Jedną przyczyną jest polling co 30 sekund w pillu nagłówka. Drugą: jeśli
wakeContainer() zawiódł po cichu, wiersz DB czyta ready, ale Docker mówi
paused. Uruchom arc cloud sync <project> — to wyciągnie rozbieżność
w macierzy.
Zobacz też
- standard-cloud.md — provisioning kontenera + terminal webowy
- cloud-repos.md — 3 typy repozytoriów i jak ułożony jest
/workspace/ - arc-cli-reference.md —
arc push/arc pull/arc cloud sync docs/architecture/PHASE_69_CLOUD_REPO_MODEL.md— architektoniczna specyfikacja po stronie repozytoriów