Cloud Chat Routing — Guia

Phase 70. Quando você conversa com um worker pelo CRM ou Telegram, essa conversa agora roda dentro do seu container Hetzner Cloud, não no servidor master. Feche o laptop, mude para o Telegram, continue trabalhando — a sessão, os arquivos e os repos abertos vivem todos no seu workspace Cloud.


TL;DR


Por que isso importa

A Phase 60 entregou o Standard Cloud como "máquina Linux sempre ligada com Claude Code". A Phase 69 conectou seus repos à org do bot. Mas até a Phase 70, o fluxo de chat ainda fazia spawn de claude -p no VPS master — seu container Cloud ficava ocioso e seu trabalho de chat vivia no master.

A Phase 70 roteia o chat para dentro do container para que a proposta de valor realmente entregue:


Como o roteamento funciona

Toda mensagem de chat passa por esta árvore de decisão:

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)

Essa decisão acontece uma vez por mensagem. O log do worker emite uma única linha indicando o alvo escolhido:

claude spawn: container/ready
claude spawn: container/ready (woke from paused)
claude spawn: local

Se você quiser saber onde sua última mensagem rodou, é essa a linha que você procura com grep.


Como verificar que está funcionando

1. Pill no header

Após provisionar seu Cloud Workspace, Configurações → recarregue o CRM e olhe para o canto superior direito do header. Você deve ver um destes:

Pill Significado
Cloud (ponto verde) Container ativo, chat será roteado para ele
Cloud · asleep (ponto cinza) Container pausado; a próxima mensagem de chat o desperta
(nada) Nenhum container provisionado ainda

O pill consulta o status do container a cada 30 segundos, então ele atrasa um evento de wake/pause em até meio minuto.

2. Log do worker

Se você tem acesso ao terminal do master (tmux attach -t citadel-child para o bot dev do Arc OS), acompanhe a saída do worker e observe a linha de spawn em cada mensagem.

3. Arquivos realmente dentro do container

Abra o terminal Cloud em /cloud, então:

cd /workspace/<your-project>
git log -3
ls -lh

Se edições disparadas por chat aparecem aqui (e arc cloud sync do seu laptop sinaliza behind com a contagem certa de commits), o roteamento está conectado corretamente.


Ciclo de vida: pause, wake, push, fetch

O ciclo de vida da Phase 69 continua valendo:

  1. Ocioso por 30 min → o cron pausa o container.
  2. Antes do pausesnapshotAndPush faz auto-commit das árvores sujas em todos os repos sob /workspace/ e dá push para a org do bot.
  3. Próxima mensagem de chatensureAwake despausa o container (~1 s), depois dispara fetchAll em background para que o ponteiro origin/main de cada repo esteja atualizado.
  4. arc pull <project> no seu laptop lê esses auto-commits.

O custo de pause/wake está embutido na latência do próximo chat — você não precisa pensar nisso.


Continuidade de sessão

O Claude Code armazena o histórico da conversa em ~/.claude/projects/<cwd-hash>/<session-id>.jsonl. Dentro do container, esse diretório fica num volume montado, então sobrevive a docker pause / docker unpause. Enquanto seu chat continuar apontando para o mesmo projeto, o --resume <session-id> do claude encontra o mesmo JSONL após o wake e continua o thread.

Uma ressalva: o hash do cwd muda entre local ↔ cloud

O hash é derivado do caminho do diretório de trabalho. No master, o claude roda em /opt/repos/<slug>; no container, ele roda em /workspace/<slug>. Dois caminhos diferentes → dois hashes diferentes → dois históricos de conversa diferentes.

O que isso significa na prática:

Consideramos criar um symlink /opt/repos/workspace no master para manter os hashes alinhados, mas isso misturava as duas árvores de arquivos e quebrava os bots master que rodam em planos Free. Dois históricos é o menor dos males.


Troubleshooting

"claude spawn: local" mas estou no plano Cloud

Verifique, nesta ordem:

  1. subscription.plan === 'cloud' no pill de billing do user dropdown?
  2. /cloud/status retorna status: 'ready' ou 'paused'?
  3. Você criou o projeto DEPOIS de provisionar? Containers só clonam repos de projetos que existiam no momento do provisionamento (Phase 69.3). Reprovisione ou re-dispare o bootstrap para incluir projetos recém-criados.

Chat trava por ~5 segundos, depois responde

É o primeiro wake depois de uma ociosidade longa. O wake leva ~1 s; o restante é a inicialização do próprio claude + a primeira inferência. As mensagens seguintes enquanto o container fica quente têm latência normal.

"Not logged in" do claude

A Phase 70.5 corrigiu um bug em que ~/.bashrc fazia early-return para shells não-interativos, então ANTHROPIC_API_KEY nunca chegava ao claude. Correção: a chave agora vive em ~/.profile (carregado pelo wrapper de login shell). Se você fez upgrade no meio da Phase 70 e nunca re-salvou seu token, vá em /cloud Step 1, cole seu sk-ant-api03-… novamente. Tokens recém-salvos vão para o arquivo certo.

Container diz pausado por horas depois de uma mensagem de chat

O polling de 30 segundos no pill do header é uma das causas. A outra: se wakeContainer() falhou silenciosamente, a linha do DB lê ready mas o Docker diz paused. Rode arc cloud sync <project> — ele vai expor a divergência na matriz.


Veja também