Cloud Chat Routing — Гайд
Phase 70. Коли ти спілкуєшся з воркером з CRM або Telegram, ця розмова тепер виконується всередині твого Hetzner Cloud контейнера, а не на master-сервері. Закрий ноутбук, перейди в Telegram, продовжуй працювати — сесія, файли та відкриті репозиторії живуть у твоєму Cloud workspace.
TL;DR
- Користувачі Cloud-плану: кожне повідомлення в чаті, яке обробляє твій воркер, спавнить
claude -pвсередині/workspace/<project>у твоєму контейнері. - Користувачі Free / Starter: без змін — чат і далі виконується на master-сервері.
- Контейнер на паузі? Перше повідомлення розбудить його (~1 секунда додатково) і відновить розмову з місця, де ти зупинився.
- Три нові індикатори показують, що це працює: пілюля
Cloudу хедері, рядокclaude spawn: container/readyу лозі воркера та правки, які з'являються в/workspace/<project>замість/opt/repos/<project>.
Чому це важливо
Phase 60 запустив Standard Cloud як "завжди ввімкнений Linux-бокс з Claude Code".
Phase 69 під'єднав твої репозиторії до bot org. Але до Phase 70 чат-воркфлоу
все ще спавнив claude -p на master VPS — твій Cloud-контейнер
простоював, а твоя робота в чаті жила на master.
Phase 70 маршрутизує чат у контейнер, щоб value prop реально працював:
- Закрив ноутбук — продовжуй у Telegram. Та сама сесія, ті самі файли, той самий стан репо. Жодних "доведеться чекати, поки повернуся до ноутбука".
- Правки потрапляють у контейнер. Твої bot-org репозиторії під
/workspace/отримують зміни; auto-commit на idle + push (Phase 69.3 + #349) відправляє їх на GitHub;arc pullна твоєму ноутбуці синхронізує їх назад. - Жодного раунд-тріпу через Cloudflare. Вивід воркера стрімить контейнер ↔ master ↔ чат — без публічного мережевого хопа у внутрішньому циклі.
Як працює маршрутизація
Кожне повідомлення в чаті проходить через це дерево рішень:
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 і далі застосовується:
- Idle 30 хв → cron ставить контейнер на паузу.
- Перед паузою →
snapshotAndPushавто-комітить брудні дерева у кожному репозиторії під/workspace/і пушить їх у bot org. - Наступне повідомлення в чаті →
ensureAwakeзнімає контейнер з паузи (~1 с), потім фоном запускаєfetchAll, щоб вказівникorigin/mainкожного репо був свіжим. 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>.
Два різні шляхи → два різні хеші → дві різні історії
розмов.
Що це означає на практиці:
- Перший апгрейд з Free/Starter на Cloud: твоя попередня історія чату на master і там залишається. Перше повідомлення в cloud починає новий тред розмови.
- Стабільний Cloud-користувач: кожна сесія безшовно продовжується через pause/wake. Жодних дій не треба.
- Якщо ти даунгрейдиш назад на local: історія всередині контейнера залишається в контейнері; нові повідомлення маршрутизуються на master, починаючи з чистого аркуша.
Ми розглядали можливість симлінкувати /opt/repos → /workspace на master, щоб тримати
хеші вирівняними, але це поєднувало б два файлові дерева і ламало master
ботів, які виконуються на Free-планах. Дві історії — менше зло.
Усунення проблем
"claude spawn: local", але я на Cloud-плані
Перевір по порядку:
subscription.plan === 'cloud'у пілюлі billing в user dropdown?/cloud/statusповертаєstatus: 'ready'або'paused'?- Ти створив проєкт ПІСЛЯ провіженінгу? Контейнери клонують лише проєктні репозиторії, які існували на момент провіженінгу (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> — це покаже розбіжність
в матриці.
Дивись також
- standard-cloud.md — провіженінг контейнера + веб-термінал
- cloud-repos.md — 3 типи репо і як влаштовано
/workspace/ - arc-cli-reference.md —
arc push/arc pull/arc cloud sync docs/architecture/PHASE_69_CLOUD_REPO_MODEL.md— архітектурна специфікація з боку репо