Русский · English
Автономная инфраструктура поверх Claude Code: control-плоскость в Telegram, которая (1) раздаёт с телефона удалённые Claude-сессии по всем твоим проектам - включая возврат в любую прошлую сессию по её имени - и (2) держит парк фоновых агентов - с событийной очередью, бюджетами, кросс-машинным handoff, независимой приёмкой результата и детерминированной раскаткой канона через pull request'ы.
Часть системы из двух репозиториев. Второй - claude-toolkit: канон правил/агентов/скиллов и транзакционный движок его упаковки в immutable-релизы.
claude-controlэти релизы раскатывает по парку (см. Слой 2 -> canon fleet-reconciler).
Note
Ядро (remote-control) стоит поверх фичи claude remote-control - на момент написания в статусе research preview. Нужен Claude Code CLI ≥ 2.1.51 и логин через Claude-подписку (claude /login); API-ключи Anthropic для remote-control не работают.
Система росла двумя слоями, каждый самодостаточен и ставится одним install.sh.
flowchart TB
phone["📱 Телефон<br/>(приложение Claude / Telegram)"]
subgraph host["Хост: macOS (launchd) или Linux VM (systemd --user)"]
direction TB
subgraph L1["Слой 1 - сессии из бота"]
menu["/sessions в Telegram<br/>проекты → сессии по именам"]
rc["claude-rc up/down/new<br/>транзиентный юнит на сессию"]
menu --> rc
end
subgraph L2["Слой 2 - автономный агентный слой (Linux)"]
recon["reconciler<br/>event-spool + бюджеты"]
tgbot["tgbot<br/>дашборд + /new /task /limits"]
canon["canon-maintainer<br/>fleet-reconciler канона"]
takeover["takeover<br/>Mac → VM handoff"]
harvest["acceptor + harvester<br/>приёмка + правила ролей"]
end
end
toolkit["claude-toolkit<br/>канон + движок релизов"]
fleet["git-парк проектов<br/>(PR-раскатка канона)"]
phone <--> tgbot
tgbot --> menu
rc --> projA["ccsession-<uuid>: сессия A"]
rc --> projB["ccsession-<uuid>: сессия B"]
toolkit -. "canon.lock.json (immutable)" .-> canon
canon -- "PR canon/vN" --> fleet
recon --> canon
- Слой 1 - сессии из бота (Linux; на macOS доступен CLI, но без транзиентных юнитов). В Telegram
/sessions: проекты -> сессии проекта под их собственными именами -> поднять, положить, создать новую. Поднятая сессия живёт транзиентнымsystemd-юнитом и появляется в приложении Claude Code. Доступ к любому репо и к любой прошлой сессии без SSH и ручногоcd. - Слой 2 - автономный агентный слой (Linux/systemd на VM). Фоновые агенты под надзором reconciler'а: событийная очередь, контур задач
/newс телефона (worktree, карточки, приёмка тапом), бюджеты по запускам, circuit breaker, кросс-машинный takeover, независимая ролевая приёмка, harvester операторских поправок и детерминированный fleet-reconciler канона.
Оба слоя - stdlib Python + shell, ноль внешних зависимостей, только пользовательские юниты (никакого sudo, никаких системных сервисов), идемпотентная установка/снос.
Claude Code умеет открывать сессию для удалённого управления, к ней подключаешься с телефона. Но сам по себе он этого не закрывает: чтобы попасть в нужный репозиторий, надо физически сесть за машину, cd в проект и запустить claude --remote-control. А чтобы вернуться во вчерашний разговор - ещё и вспомнить, какой именно из десятков это был.
claude-control закрывает оба зазора одним экраном в Telegram:
/sessions-> список проектов из~/.claude-control/projects.yaml;- проект -> его сессии под теми же именами, что видны в Cursor (
/renameпишет имя в транскрипт, бот читает оттуда же), у поднятых - кружок; - тап по сессии ->
▶ поднять/⏹ положить; отдельной кнопкой -➕ новая сессия.
Поднятая сессия появляется в приложении Claude Code, и дальше работа идёт там. На хосте она живёт транзиентным systemd-юнитом ccsession-<uuid>: переживает выход вызывающего, получает cgroup и потолок памяти, гасится по имени. Никакой постоянно живущей сессии-диспетчера и никакого tmux в схеме больше нет.
Что это даёт: доступ к любому проекту и к любой прошлой сессии за два тапа, без заранее открытых сессий, с реестром проектов в одном файле. Восстановление прошлой сессии в мост - отдельный трюк: --resume без промпта выходит всегда, а без pty процесс отрабатывает промпт и завершается, не подключившись. Разбор - в контракте этапа.
Как выглядит с телефона:
Ты (в Telegram) - /sessions
Бот - [claude-control] [проект 1] [проект 2] ...
Ты - проект 1
Бот - ➕ новая сессия
● control-v2 ← поднята
сессия 1
LLM start
Ты - сессия 1 -> ▶ поднять
Ты - открываешь Claude Code, выбираешь "сессия 1" - ты внутри
То же с машины, если бот недоступен:
claude-rc sessions <проект> --porcelain # uuid, имя, поднята ли
claude-rc up <проект> <uuid> # поднять
claude-rc new <проект> # новая пустая
claude-rc down <uuid> # положить
claude-rc live # что поднято сейчас
claude-rc reap [--dry-run] # погасить зомби: юнит жив, а мост снесенЗомби заводится, когда карточку сессии архивируют в браузере: CLI сносит мост, а
процесс остается жить - сессия выглядит работающей, хотя достучаться до нее уже
нельзя. Руками звать не нужно: жнец висит вторым ExecStart в юните watchdog и
проходит раз в две минуты. Команда пригодится, чтобы посмотреть глазами -
--dry-run только докладывает, ничего не гася.
Поверх диспетчера - парк фоновых агентов, которые продолжают миссию, когда ты вышел из сессии. Живёт на Linux (нужны transient-юниты и cgroups от systemd --user). Спроектирован по state-machine контракту: у каждого агента разведены spec (что делать), control (armed/budget/latch) и reconciler (кто приводит факт к желаемому).
Ядро автономии. Durable spool событий (at-least-once с producer-идемпотентностью по update_id), executor в headless-режиме, бюджет по запускам (агент не жжёт бесконечно), fail-closed на неизвестных отказах (событие нельзя терять). Разбор в дизайне этапа 4.
Поверх spool'а - полный жизненный цикл задачи без открытой сессии: /new <проект> <текст> в Telegram рождает задачу из шаблона со строгим поясом прав (fail-closed: нет валидного шаблона - нет задачи), агент работает в git-worktree проекта и заявляет о готовности; карточка приёмки прилетает в личку, тап "принять" мержит ветку в проект, уборка и архив - автоматически. Одиннадцать этапов V2.0-V2.10, каждый со своим SDD-контрактом и adversarial-аудитом:
- Scale-to-zero и память. Executor гаснет на пустом inbox, reconciler будит по событию (V2.0); worktree и пояса прав per-agent (V2.1); тред-память задачи переживает прогоны (V2.2).
- Вопросы и подтверждения - durable-исход прогона, не смерть задачи: агент спрашивает (
claude-agent-ask) или упирается в гейт прав, карточка с кнопками уезжает в TG, ответ тапом/reply-ем возвращается ровно один раз (V2.3-V2.6). - Приёмка - durable FSM
requested -> accepted -> integrated -> cleaned -> archivedс фиксацией SHA заявки (V2.7a, V2.7b); расписание как источник событий (V2.8); поправки человека по ходу задачи дистиллируются в правила проекта (V2.9). - У агента нет git. Три круга аудита нашли три независимых способа исполнить код агента до человеческой приёмки через git-механизмы (хуки, флаги вроде
git log --output=, clean-фильтры, fsmonitor) - глушить их по одному оказалось невыигрываемой гонкой. Git отобран целиком: коммитит рантайм, после заявки о готовности (V2.10).
Long-poll Telegram-бот (getUpdates, не webhook - webhooks режет DPI в ряде сетей). Команды /agents, /agent <name>, /new <проект> <текст> (родить задачу), /task <name> <текст> (событие существующему агенту), /menu и /limits (остатки подписочных лимитов Claude/Codex); карточки вопросов и приёмки - с inline-кнопками, ответ тапом или reply-ем. Приватные чаты + whitelist по from.id; весь вывод агентов - недоверенные данные, эскейпится и шлётся как <pre>.
Раскатывает ревизии канона из claude-toolkit по парку git-проектов через pull request'ы, детерминированно и без LLM в data-plane. Потребляет транзакционный дельта-движок toolkit'а (canon-delta.py). Инженерно самая плотная часть:
- Модель B: reconciler на VM держит клоны парка, канон едет веткой
canon/<vN>+ PR;appliedфиксируется только по факту присутствия байт канона в post-merge default-ветке (post-merge truth). Mac-чекауты и не-git vault'ы не мутируются никогда - только observe. - Immutable-релизы: identity ревизии = git commit_sha аннотированного тега
canon-vN; отклонённый релиз (закрытый PR) суперсидится следующей версией, не пересобирается. - Кольца раскатки canary -> snapshot -> rest + circuit breaker (защёлка на incompat/error/smoke, снятие только явным
ack). - Semantic smoke кандидата до push, budget применений на проход, break-glass rollback на предыдущую ревизию, observe-first (первые проходы только наблюдают) и мгновенный kill-switch
disarm. - Полный runbook и дизайн этапа 8.
Перенос живой миссии Mac -> VM не переносом транскрипта (фундаментально небезопасно - утащил бы чужой контекст), а fresh brief-seeded сессией: новый агент поднимается на VM с самодостаточным брифом от base-commit. Дизайн этапа 5.
Acceptor (этап 7) - ролевой судья артефактов в независимом контексте (deterministic / role-review / both), с corpus-runner и confusion-matrix для калибровки. Harvester (этап 7b) - операторские правки (revise/reject) превращаются в кандидаты правил ролей: collect -> propose -> digest -> approve.
Каждые 15 минут снимает остаток подписочных лимитов Claude/Codex (метаданные квоты, не inference - саму квоту не тратит) и шлёт панель в Telegram только при изменении цифр (дедуп по сигнатуре процентов/статусов, время сброса не считается изменением). Runbook.
Этап 6 (веб-панель управления парком) - пока дизайн, не реализация.
Модуль claude-control-backup: клиентски-шифрованный дедуплицированный бэкап произвольных путей в два независимых S3-репозитория через restic. Ставится флагом --with-backup (Linux).
- Клиентское шифрование - провайдер видит только шифртекст, поэтому бэкап можно держать у хостинга, которому не доверяешь plaintext.
- Два независимых провайдера - два
backup(неcopy); падение или бан одного не мешает второму, восстановление возможно из любого. - Дедуп + zstd-сжатие - на текстовых данных обычно 5-10x экономии.
- systemd-таймер (ежедневно) + restore-drill - непроверенный бэкап не считается бэкапом.
Пути, URL репозиториев и креды - в ~/.config/claude-control/backup-env (вне git, chmod 600); в скриптах ничего машино-специфичного. Настройка и восстановление - в docs/runbook-backup.md.
Что делает это не "скриптами на коленке":
- Детерминизм в data-plane. Канон-раскатка - чистый дельта-движок над immutable release-дескриптором; LLM поднимается только on-demand на разрешение конфликтов. Метрика: 0 вызовов LLM на no-op проходе.
- Транзакционная безопасность. WAL с crash-матрицей (prepare/commit/recovery roll-forward/back), CAS перед rename, no-clobber на чужие файлы, containment записи в пределах проекта. Доказывается fault-injection тестами, а не "на бумаге".
- Автономность с тормозами. Бюджеты по запускам, circuit breaker с durable-защёлкой, кольца раскатки, observe-first, kill-switch. Автономный агент не может уйти в разнос молча.
- Состязательная верификация. Каждый крупный слой проходит несколько раундов adversarial-ревью второй моделью (другой класс ошибок, чем у основного агента); каждая находка закрывается фиксом плюс регресс-тестом. Стек этапов накопил десятки закрытых blocker'ов; канон-движок toolkit'а - ~100 stdlib-тестов и 4 раунда adversarial до GO.
- Модель угроз явная. Доверенная VM, durable-state наш, канон из нашего git-зеркала; границы (TOCTOU под flock, symlink-родители, secret-handling) отработаны и задокументированы, остаточные риски приняты письменно.
- Ноль зависимостей, user-level. Только stdlib Python + shell, только пользовательские launchd/systemd-юниты, идемпотентные install/uninstall.
Дизайн-доки по этапам - в docs/; архитектура обоих слоёв (со схемой контура задач V2) - docs/architecture.md.
- Linux с
systemd --user(Ubuntu 22.04+, Debian 12+) - оба слоя. На macOS доступен CLI (claude-rc sessions/up/down), но держатель сессий - транзиентный systemd-юнит, поэтому подъём сессий там не работает. - Claude Code CLI ≥ 2.1.51, залогинен через
claude /login(Claude-подписка). yqот mikefarah, v4 -brew install yq(macOS); на Linux бинарник с GitHub releases (пакетyqиз apt - другой проект).install.shпроверит версию.- Linux: включённый lingering (
loginctl enable-linger $USER), иначе user-сервисы гибнут при logout.install.shпроверит и предупредит.
git clone https://github.com/dewil/claude-control.git
cd claude-control
./install.sh
$EDITOR ~/.claude-control/projects.yaml # вписать свои проектыГотово. Управление сессиями живёт в Telegram-боте: /sessions -> проект -> сессия -> поднять; бот, reconciler, canon-maintainer и limits-digest поднимаются тем же install.sh при наличии ~/.config/claude-control/env с нужными переменными (см. runbook'и в docs/). Без бота те же действия доступны с машины: claude-rc sessions <проект> --porcelain, claude-rc up <проект> <uuid>.
Правишь сам репо - ставь ./install.sh --link (скрипты в ~/.local/bin/ станут симлинками на bin/, git pull сразу обновляет рабочий код).
projects.yaml- доверенный файл.claude-rcпарсит пути черезyqкак данные, без shell-интерполяции, валидирует имя проекта; содержимое под твоим контролем. Не редактируй его по запросу LLM из чата.- Бот не запускает ничего сам. Тап уходит в
claude-rc up/down/new; имя проекта и короткий id сессии изcallback_dataотбиваются строгой формой до вызова, в shell не попадают. Доступ - приватный чат плюс whitelist поfrom.id. - Поднятые сессии наследуют твои
~/.claude/settings.json.claude-rcничего не пробрасывает поверх - если стоитbypassPermissions, удалённая сессия молча сделает что попросят. Хочешь иначе - добавь в проект.claude/settings.local.jsonс явным allow-списком. - prompt-injection. Текст из README, имён веток и чужих файлов - данные, не инструкции. Имя сессии приходит из транскрипта и тоже считается данными: в кнопку и карточку оно уходит экранированным, а в командную строку - через
%q. - Агентный слой - приватные чаты + whitelist в Telegram, бюджеты и circuit breaker против разгона, секреты только в env-файлах (не в репо/чате).
- Task-агенты (V2) не имеют git. Работают в worktree со строгим поясом прав из шаблона (fail-closed: нет валидного шаблона - задача не заводится); коммитит рантайм, в default-ветку проекта результат попадает только после явной приёмки человеком.
Слой 1 (сессии):
bin/claude-rc-sessions --porcelain,up,new,down,live: список сессий с именами и подъём транзиентным юнитом.bin/claude-agent-tgbot- экран/sessions(он же дашборд агентов, см. слой 2).bin/claude-control-session,claude-control-watchdog,claude-control-project-watchdog- legacy-диспетчер на вечной control-сессии и tmux. Установщик их больше не включает и снимает с уже установленных машин; файлы оставлены для отката.
Слой 2 (агентный):
-
bin/claude-agent-reconciler- reconciler автономных агентов. -
bin/claude-agent-run,claude-agent-io,claude-agent-session- исполнение/spool/сессии агентов. -
bin/claude-agent-tgbot- Telegram-дашборд (/agents,/new,/task,/limits, карточки вопросов и приёмки). Ответив реплаем один раз, дальше можно писать (или наговаривать) просто в чат: адресат запоминается на 30 минут и продолжение уходит в ту же сессию. Молчание тут намеренное - если запомненного адресата нет или его инкарнация сменилась, сообщение обрабатывается как раньше, а не подхватывается наугад. Голосовой ответ владельца бот расшифровывает локально (GigaAM,transcribe-meeting; переопределяетсяCLAUDE_AGENT_VOICE_ASR) и дальше ведет его теми же путями, что печатную реплику - reply на карточку агента попадает в ту же сессию. Расшифровку он показывает ответным сообщением: распознавание ошибается, и человек должен видеть, что получил агент. Режимvoiceотправляет итог голосом: синтез делаетbin/voice-reportэтого же репозитория (путь резолвится рядом с ботом, переопределяетсяCLAUDE_AGENT_VOICE_SYNTH), доставка - своя. Текст доходит всегда: выключен тумблер, нет синтезатора или упала отправка файла - уходит обычное сообщение, вместе с привязкой ответа к агенту.Голос из сессий - канал в одну сторону. Слово "сессия" в этом разделе означает живую сессию АГЕНТА в tmux: reply доезжает до нее через
mission-inboxиtmux send-keys. У сессий слоя 1 (ccsession-<uuid>, глаголыclaude-rc) обратного канала нет вовсе - юнит поднимается голымscript, stdin/dev/null, pty держит самscript. Итог голосом такая сессия шлет флагом--session <имя>; reply на него получает внятный отказ с названием сессии, а не совет про карточку задачи. Промпт пишется внутри самой сессии. Решение владельца 24.08.2026: так и оставить, пока не появится машинный канал ввода.Как итог представляется. Карточка в Telegram подписывается именем сессии (иначе все итоги приходят одинаковым "Итог работы" и подряд неразличимы), а сама озвучка открывается отбивкой
Новое сообщение. Сессия <имя>.- при автовоспроизведении карточку никто не видит, телефон в кармане. Имя берется из транскрипта (custom-title), а не из юнита: переименованная сессия должна называться так же, как в браузере. Латиница перед синтезом транслитерируется - русский Silero ее не произносит вовсе, на месте имени была пауза; подпись карточки и роутинг при этом работают с исходным именем.Кто зовет озвучку.
bin/claude-agent-voice-hook- хукStopв~/.claude/settings.json: сессия договорила ответ, хук берет его и шлет голосом. Без него транспорт есть, а зовущего нет - голос приходит только оттуда, где команду набрали руками. Четыре фильтра, каждый нужен: тумблер выключен - молчим; ответ короче 400 знаков - это "готово" и уточнения, слушать нечего; с прошлой озвучки этой сессии меньше 5 минут - телефон не радио; сессия без юнитаccsession-*(локальный терминал, агент) - не наш случай. Отправка уходит отвязанным процессом: хук не ждет синтез. Причины отказа -~/.cache/voice-hook.log, наружу хук молчит всегда.Озвучка под тумблером - кнопка
Голос: вкл/выклво втором ряду клавиатуры, состояние прямо в подписи (в дороге видно режим, не нажимая). Режим общий для всех сессий, живет в~/.claude-control/tgbot.voice.json, по умолчанию ВЫКЛЮЧЕН. Входящий голос расшифровывается всегда независимо от тумблера: распознавание локальное, а реплай голосом нужен и в текстовом режиме. -
bin/claude-agent-done,claude-agent-ask,claude-agent-answer,claude-agent-permit- протокол задачи V2: заявка "готово", вопрос из прогона, доверенный писатель ответов, гейт подтверждений. -
bin/claude-agent-canon-maintainer- fleet-reconciler канона. -
bin/claude-agent-limits-digest- дайджест лимитов LLM. -
bin/claude-agent-harvest,claude-agent-review,claude-agent-checkrun- приёмка/ревью/проверки. -
bin/claude-rc-takeover,claude-rc-agent- кросс-машинный takeover.
Опциональный модуль (--with-backup):
bin/claude-control-backup,claude-control-backup-init,claude-control-backup-restore-test- restic-бэкап в два S3 (см. runbook).
Общее:
launchd//systemd/- шаблоны юнитов;install.shих рендерит.examples/- стартовыеprojects.yaml,CLAUDE.md,settings.local.json.docs/-architecture.md, дизайн-доки этапов, runbook'и (canon-maintainer, limits-digest), troubleshooting.tests/- offline-тесты компонентов агентного слоя.install.sh/uninstall.sh; что именно ставится и снимается - вscripts.manifest, общем на оба скрипта (модуль бэкапа -scripts.manifest.backup).
- Идемпотентность -
install.shгоняется повторно;projects.yaml,CLAUDE.md, логи не трогаются. - Runtime отдельно от репо - код где удобно (
~/Work/claude-control/), данные в~/.claude-control/. - Только user-level супервизор - launchd user agent /
systemctl --user, никакогоsudo. - Никакой магии в надзоре - watchdog читает лог и пинает супервизор; всё видно глазами в
~/.claude-control/*.log.
./uninstall.sh # снять агентов, удалить скрипты из ~/.local/bin/
./uninstall.sh --purge # дополнительно снести ~/.claude-control/MIT. Бери, дорабатывай, используй у себя - оставь copyright-уведомление в производных копиях.