Ты спрашивал голосом про удалённый формат работы с агентами и про то, как я запихнул туда несколько аккаунтов Клода. Отвечаю по порядку, с кодом и граблями.
Первое разочарование для тех, кто ждёт архитектуру: её почти нет. Это Hetzner, на нём tmux, в каждой панели живёт обычный Claude Code. Тот же бинарник, что у тебя в терминале, с теми же флагами.
Веб-морда это стекло: xterm.js поверх вебсокета плюс поток кадров с tmux capture-pane, чтобы можно было смотреть на десяток сессий сеткой одновременно. Ничего из логики агента в браузере не живёт. Закрыл вкладку, уехал в метро, открыл с телефона: сессия всё это время работала, потому что она никогда и не была во вкладке.
Из этого следует главное свойство, ради которого всё и затевалось. Агент не знает, что он «удалённый». У него настоящий шелл, настоящий git, настоящие права и настоящий деплой. Он не в песочнице с урезанным API, где половина задач упирается в «я не могу выполнить эту команду».
Воркtree на сессию. Это единственная причина, по которой можно запускать пять задач в одном репозитории и не собирать потом фарш. Ветку после смерти сессии удаляю только тогда, когда git доказал, что она влита. Обычный git branch -d тут врёт: он считает влитой ветку, которую просто запушили.
Статусы из хуков. Claude Code умеет дёргать скрипт на своих событиях. Хук стучится на localhost, и сервер знает, кто сейчас работает, кто задал вопрос и кто закончил. Плюс эвристика поверх панели, потому что часть состояний хуками не выражается: спиннер, живой вопрос-пикер, баннер про упёршийся лимит.
Пуш в телефон. Правило одно и оно про уважение к себе: пушить только вопросы и финиши. Никаких «я начал», никаких «всё идёт хорошо». Уведомление, на которое не надо реагировать, обесценивает те, на которые надо.
У Claude Code нет параметра «аккаунт». Он читает конфиг и креды из одной директории. Значит, чтобы получить второй аккаунт, надо дать ему вторую директорию. Всё остальное следствия.
# запуск сессии под конкретной подпиской: это весь секрет CLAUDE_CONFIG_DIR=/root/.claude-accounts/liveceo IS_SANDBOX=1 \ claude --dangerously-skip-permissions
Именно так Chief собирает командную строку перед тем, как отправить её в панель tmux. Это путь, а не секрет, поэтому его не страшно оставить в скроллбеке.
/root/.claude-accounts/ ├── personal/ │ ├── .credentials.json свой логин │ ├── .claude.json свой конфиг и oauthAccount │ ├── settings.json → /root/.claude/settings.json │ ├── hooks/ → /root/.claude/hooks/ │ └── projects/ → /root/.claude/projects/ ← транскрипты общие ├── liveceo/ то же самое, свой логин └── client/ то же самое, свой логин
Своего в папке ровно два файла. Всё остальное симлинки в общий /root/.claude. Это выглядит как мелочь по уборке, но на этом держится вся пересадка сессий: раз projects/ общая, то диалог, начатый под одним аккаунтом, целиком виден процессу claude --resume, поднятому под другим. Я не переношу историю, её просто некуда переносить.
Это стоило мне отдельного вечера. CLAUDE_CODE_OAUTH_TOKEN и ANTHROPIC_API_KEY в окружении процесса перебивают credentials.json и молча переводят сессию на API-тарификацию. Не ошибка, не предупреждение: просто в баннере вместо Max появляется Claude API, а деньги начинают течь мимо подписки.
# /usr/local/bin/claude, стоит в PATH раньше настоящего бинарника
exec env -u ANTHROPIC_API_KEY -u CLAUDE_CODE_OAUTH_TOKEN \
/usr/bin/claude "$@"
Шим нужен потому, что на коробке эти переменные легально живут для других инструментов. Он снимает их только у claude. CLAUDE_CONFIG_DIR проходит насквозь.
Скрипт готовит папку заведомо без кред, поэтому claude в ней сразу падает в экран логина. Дальше вручную: /login, выбрать вход по подписке, забрать из панели ссылку на авторизацию, открыть её в браузере, залогиненном нужным аккаунтом, и вставить обратно строку code#state. Полминуты работы, зато у каждой папки собственная линия обновления токена.
Соблазн скопировать креды с ноутбука есть у всех, и он наказуем. Про это в граблях ниже.
Тут три отдельных вопроса, и их полезно не смешивать: кто платит за новый запуск, что делать с живой сессией, которая упёрлась в лимит, и что делать, если упёрлись все.
Выбирать вслепую нельзя, значит нужен остаток топлива. Публичного API для этого нет, поэтому Chief читает ровно те же цифры, что читаешь ты. Раз в 15 минут он поднимает одноразовую панель tmux, запускает в ней настоящий CLI под нужным CLAUDE_CONFIG_DIR, отправляет /usage, разбирает экран на проценты по окнам и время сброса, и убивает панель.
Метод некрасивый, зато честный: цифра ровно та, которую увидел бы человек. На разборе экрана держатся тесты с настоящими снимками, потому что вёрстка этого экрана однажды поедет.
// то, ради чего всё: единственное место, где решается, чья подписка платит function candidatesFor(routing) { return routing === AUTO ? state.accounts.filter((a) => a.poolable) // только мои : state.accounts.filter((a) => a.client === routing); // только его }
Флаг poolable по умолчанию выключен. Новая подписка не может начать молча оплачивать чужую работу просто потому, что её зарегистрировали.
У проекта есть маршрут: либо конкретный клиент, либо AUTO. Маршрут даёт список кандидатов, из кандидатов выбрасываются остывающие и сломанные, оставшиеся сортируются по худшему из связывающих окон, пятичасового и недельного. Побеждает самый свободный.
Одно исключение: при возобновлении сессия липнет к своему аккаунту, пока он здоров. Пара процентов разницы не стоит того, чтобы дёргать живой контекст. И ещё одно: если я выбрал аккаунт руками, это пин, а не пожелание. Роутер не имеет права заменить его на более здоровый.
Забор между клиентами проходит именно через функцию кандидатов, и это сделано специально. Ротация, восстановление и ручной перенос ходят через неё же, поэтому подписка внешнего клиента не может утечь на мой проект, даже когда у неё самый жирный остаток.
/usage. Если баннер прочитан криво, проба снимет пометку через полминуты, а не через пять часов.
claude --resume под другим CLAUDE_CONFIG_DIR. Именно resume, а не новая сессия: транскрипты общие, поэтому новый процесс подхватывает тот же самый диалог со всей историей.
Пуша в телефон на этом пути нет. Сработавший фейловер это не событие для владельца: сессия не останавливалась, она просто сменила плательщика. Рассказывать надо про фейловер, который не сработал.
Сессия паркуется: останавливается, показывает отсчёт до сброса и уходит в телефон уведомлением. Каждые 30 секунд сервер проверяет, не появился ли остаток. Тонкость, на которой я один раз обжёгся: живой процесс намертво привязан к своей папке. Разбудить парковку на месте можно, только если восстановился именно его аккаунт. Если свободен другой, это снова kill и resume, а не «снять флажок».
На карточке каждой сессии висит чип с текущим аккаунтом, за ним шторка «переехать на». Это тот же примитив, что и автоматическая ротация, и тот же забор кандидатов: руками тоже нельзя пересесть на подписку другого клиента.
И последнее, что технически тоже переключение, хотя выглядит иначе. Если лучший аккаунт в маршруте уже за 80 процентами, новые задачи стартуют не на Клоде вообще, а на codex или grok, которые сидят на своих подписках. Живую сессию так не переносят: разговор не переезжает между разными CLI, там нет общего транскрипта. Это решение только про то, где начать.
Ни одна из них не выглядит проблемой, пока не наступишь. Четыре из пяти стоили мне от вечера до суток.
Однажды CLAUDE_CODE_OAUTH_TOKEN оказался в общем settings.json. Переменные из настроек применяет сам claude при старте, поэтому шим тут бессилен: они перебивают креды каждой папки, все сессии схлопываются на один токен и уезжают на API. Признак простой и заметный: датчики /usage отваливаются у всех аккаунтов одновременно.
У логина одна линия обновления токена. Когда её крутят с двух машин, она рано или поздно рвётся. Единственный аккаунт, который у меня умирал дважды, это ровно тот, чьи креды я скопировал с ноутбука. У остальных был собственный вход на коробке, и они живы.
У свежего входа окно порядка месяца, и оно не едет вперёд бесконечно. CLI заранее пишет в баннер предупреждение про обновление. У меня регулярка «это экран логина» ловила именно это предупреждение и отправляла на скамейку два совершенно живых аккаунта. Проверяется одним запросом профиля по токену.
Статус «сломан» у меня был односторонней защёлкой. Креды чинишь, проба проходит, текст ошибки обнуляется, а аккаунт всё равно считается непригодным, потому что снимать флаг было некому. Любой флаг, который ставит автоматика, обязан уметь сняться автоматикой.
Ротация по лимиту, подъём с парковки, ручной перенос и обычное возобновление это буквально одно действие: убить и поднять с resume. Пока их четыре штуки и мьютекса на сессию нет, они однажды встретятся в одной точке, и ты получишь два процесса на один воркtree.
Порядок важен: половина боли ниже возникает только тогда, когда шаги сделаны в другом порядке.