Cloud Chat Routing — Гайд

Phase 70. Коли ти спілкуєшся з воркером з CRM або Telegram, ця розмова тепер виконується всередині твого Hetzner Cloud контейнера, а не на master-сервері. Закрий ноутбук, перейди в Telegram, продовжуй працювати — сесія, файли та відкриті репозиторії живуть у твоєму Cloud workspace.


TL;DR


Чому це важливо

Phase 60 запустив Standard Cloud як "завжди ввімкнений Linux-бокс з Claude Code". Phase 69 під'єднав твої репозиторії до bot org. Але до Phase 70 чат-воркфлоу все ще спавнив claude -p на master VPS — твій Cloud-контейнер простоював, а твоя робота в чаті жила на master.

Phase 70 маршрутизує чат у контейнер, щоб value prop реально працював:


Як працює маршрутизація

Кожне повідомлення в чаті проходить через це дерево рішень:

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)

Це єдине рішення приймається один раз на повідомлення. Лог воркера видає один рядок, який повідомляє про вибраний target:

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

Якщо колись захочеш дізнатися, де виконалося твоє останнє повідомлення — це той самий рядок для grep.


Як перевірити, що це працює

1. Пілюля в хедері

Після провіженінгу твого Cloud Workspace, Налаштування → перезавантаж CRM і подивися у верхній правий хедер. Ти маєш побачити одне з:

Пілюля Значення
Cloud (зелена крапка) Контейнер живий, чат маршрутизуватиметься в нього
Cloud · asleep (сіра крапка) Контейнер на паузі; наступне повідомлення в чаті розбудить його
(нічого) Контейнер ще не провіженовано

Пілюля опитує статус контейнера кожні 30 секунд, тож вона відстає від подій wake/pause максимум на пів хвилини.

2. Лог воркера

Якщо в тебе є термінальний доступ до master (tmux attach -t citadel-child для dev-бота Arc OS), стеж за виводом воркера і дивись на рядок spawn на кожному повідомленні.

3. Файли реально всередині контейнера

Відкрий Cloud-термінал з /cloud, потім:

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

Якщо правки з чату з'являються тут (а arc cloud sync з твого ноутбука позначає behind з правильною кількістю комітів) — маршрутизація налаштована правильно.


Lifecycle: pause, wake, push, fetch

Lifecycle Phase 69 і далі застосовується:

  1. Idle 30 хв → cron ставить контейнер на паузу.
  2. Перед паузоюsnapshotAndPush авто-комітить брудні дерева у кожному репозиторії під /workspace/ і пушить їх у bot org.
  3. Наступне повідомлення в чатіensureAwake знімає контейнер з паузи (~1 с), потім фоном запускає fetchAll, щоб вказівник origin/main кожного репо був свіжим.
  4. arc pull <project> на твоєму ноутбуці підтягує ці авто-коміти.

Вартість pause/wake вшита в латенсі наступного чату — тобі не треба про неї думати.


Безперервність сесії

Claude Code зберігає історію розмови в ~/.claude/projects/<cwd-hash>/<session-id>.jsonl. Усередині контейнера ця директорія лежить на volume mount, тож вона переживає docker pause / docker unpause. Поки твій чат і далі таргетить той самий проєкт, --resume <session-id> claude знаходить той самий JSONL після wake і продовжує тред.

Один нюанс: cwd hash змінюється між local ↔ cloud

Hash виводиться зі шляху робочої директорії. На master claude виконується в /opt/repos/<slug>; у контейнері — в /workspace/<slug>. Два різні шляхи → два різні хеші → дві різні історії розмов.

Що це означає на практиці:

Ми розглядали можливість симлінкувати /opt/repos/workspace на master, щоб тримати хеші вирівняними, але це поєднувало б два файлові дерева і ламало master ботів, які виконуються на Free-планах. Дві історії — менше зло.


Усунення проблем

"claude spawn: local", але я на Cloud-плані

Перевір по порядку:

  1. subscription.plan === 'cloud' у пілюлі billing в user dropdown?
  2. /cloud/status повертає status: 'ready' або 'paused'?
  3. Ти створив проєкт ПІСЛЯ провіженінгу? Контейнери клонують лише проєктні репозиторії, які існували на момент провіженінгу (Phase 69.3). Перепровізіонь або перезапусти bootstrap, щоб підхопити нові створені проєкти.

Чат висить ~5 секунд, потім відповідає

Це перший wake після довгого idle. Wake — ~1 с; решта — це власний startup claude + перший inference. Наступні повідомлення, поки контейнер залишається теплим, мають звичайну латенсі.

"Not logged in" від claude

Phase 70.5 виправив баг, де ~/.bashrc робив early-return для неінтерактивних шеллів, тож ANTHROPIC_API_KEY ніколи не доходив до claude. Фікс: ключ тепер лежить в ~/.profile (sourced login-шелл-врапером). Якщо ти апгрейднувся посеред Phase 70 і ніколи не пересейвив свій токен, перейди в /cloud Step 1, встав свій sk-ant-api03-… знову. Свіжо збережені токени потрапляють у правильний файл.

Контейнер показує paused годинами після повідомлення в чаті

30-секундний полінг пілюлі в хедері — одна причина. Інша: якщо wakeContainer() тихо впав, рядок у DB читається як ready, але Docker каже paused. Запусти arc cloud sync <project> — це покаже розбіжність в матриці.


Дивись також