golang

Перенос сессий между консольными ИИ-агентами без потери контекста

  • суббота, 31 октября 2026 г. в 00:00:10
https://habr.com/ru/companies/ruvds/articles/1101674/
Команда baton помогает передать сессию со всеми деталями другому агенту
Команда baton помогает передать сессию со всеми деталями другому агенту

Когда я разрабатывал конвейер для перевода книг целиком, для его тестирования мне понадобилось использовать консольных агентов разных производителей: Claude Code, Codex и Antigravity CLI (agy). Поскольку ведущие нейросети у каждого поставщика сейчас обладают примерно одинаковым уровнем интеллекта, возникла мысль задействовать их все и для самой разработки. Но так как все мои тарифы дешёвые, по 20 долларов, пятичасовые квоты на них быстро кончаются — обычно в самый разгар отладки. Вместе с этой тройкой всё чаще используются открытые консольные агенты Pi и Hermes — через них разработчики подключают модели DeepSeek, GLM, Grok и локальные нейросети, когда заканчиваются лимиты основных подписок. Поэтому потребовалось организовать качественную передачу сессий между агентами без размытия смысла.

При наличии второго оплаченного аккаунта Claude Code можно перелогиниться и продолжить диалог штатным ключом --resume. Сессия подхватится с диска, хотя серверный кеш промптов при смене учётной записи сбросится и повторная прокачка длинной истории сразу откусит заметный кусок новой квоты. Но когда лимиты выгорели на всех аккаунтах Claude и нужно пересесть в Codex, agy, Pi или Hermes, штатного моста нет.

Обычный совет для такой ситуации — попросить агента перед уходом записать текущее состояние задачи, сделанные шаги и план в файл HANDOFF.md. На практике этот способ неоптимален по трём причинам:

  1. Внезапная остановка. Когда пятичасовой лимит исчерпан, агент перестаёт отвечать на запросы. Попросить его подвести итоги и записать файл уже нельзя.

  2. Искажения при пересказе. Даже если уловить момент за пару шагов до блокировки, мы заставляем нейросеть по памяти пересказать собственную историю. Входное контекстное окно современных моделей вмещает от двухсот тысяч до миллиона токенов, тогда как максимальная длина одного ответа жёстко ограничена несколькими десятками тысяч токенов. Из-за этого потолка неизбежно происходит агрессивный компактизирующий пересказ: пропадают точные аргументы команд запуска, номера строк, отвергнутые гипотезы и мелкие правки, а качество работы в следующем агенте заметно падает.

  3. Высокая стоимость генерации. Выходные токены у провайдеров обычно в пять раз дороже входных и впятеро быстрее сжигают остаток пятичасовой квоты. Заставлять модель генерировать длинную сводку на излёте лимитов накладно по деньгам и по квоте.

Вторая задача возникает, когда лимиты ещё есть, но нужно встать из-за домашнего компьютера с Calculate Linux, взять ноутбук и продолжить работу с того же места в дороге. Заливать ради этого в основную ветку репозитория сырой некомпилирующийся код неудобно, а во многих рабочих папках со скриптами или конфигами системы контроля версий нет вовсе — либо используется SVN или Fossil.

Чтобы забирать контекст напрямую из локальных файлов одного агента и без потерь передавать его другому на одной машине или через сеть на другой компьютер, я написал на Go открытую консольную утилиту и навык baton. Инструмент работает на Linux, macOS и Windows и поддерживает пять консольных агентов: claude, codex, agy, pi и hermes.

Как агенты хранят историю на диске

Каждый консольный агент ведёт на диске подробный журнал сессии в своём формате. При выполнении команды /compact старые реплики из файла не удаляются: журналы работают на дозапись, а в момент сжатия контекста в поток добавляется запись со сводкой предыдущего разговора.

Агент

Где лежат сессии (Linux, macOS, Windows)

Формат хранения

Как отмечено сжатие контекста

Claude Code (claude)

~/.claude/projects/<путь-проекта>/<uuid>.jsonl

Строки JSON с блоками text, thinking, tool_use, tool_result

Запись summary или флаг isCompactSummary

Codex CLI (codex)

~/.codex/sessions/ГГГГ/ММ/ДД/rollout-*.jsonl и session_index.jsonl

Строки JSON с событиями диалога и вызовами оболочки

Событие compaction или блок <context_summary>

Antigravity CLI (agy)

~/.gemini/antigravity-cli/brain/<uuid>/.../transcript.jsonl и conversations/*.pb

Журнал шагов в JSONL и бинарный слепок Protobuf

Узел CHECKPOINT или текстовый блок сводки в потоке Protobuf

Pi (pi)

~/.pi/agent/sessions/--<путь>--/<дата>_<id>.jsonl

Древовидный журнал JSONL v3 с полями id, parentId, session_info

Запись compaction с полем firstKeptEntryId и списком modifiedFiles

Hermes (hermes)

~/.hermes/state.db (%LOCALAPPDATA%\hermes\state.db на Windows) и sessions/*.json

База SQLite (таблицы sessions и messages) и файлы JSON

Цепочка parent_session_id при сжатии и флаг _compressed_summary = 1

Попытка прочитать такой журнал целиком и вывалить его содержимое в окно чата новому агенту приведёт к сбою. За полчаса активной отладки лог разрастается до десятков мегабайт за счёт распечаток прочитанных файлов и выводов компилятора. Такой объём сразу забьёт контекстное окно новой модели.

Поэтому baton воспроизводит в памяти нового агента ровно тот же активный контекст, который находился в оперативной памяти (KV-кеше) прежнего агента:

  1. Точка отсчёта от последнего сжатия. Парсер проходит журнал с первой записи (в Pi — по активной ветке дерева parentId, а в Hermes — по цепочке parent_session_id), собирая список всех изменённых файлов и выполненных команд за всё время работы. При встрече маркера /compact буфер старых реплик сбрасывается — в активный контекст попадает сводка последнего сжатия и все последующие шаги диалога целиком (реплики, блоки рассуждений thinking, правки кода и выводы команд).

  2. Обход лимита stdout, хитрости моделей и фильтров промпт-инъекций. У консольных агентов инструмент запуска команд (Bash / run_command) принудительно обрезает стандартный вывод уже на границе 8–30 КБ. Если просто сложить большой контекст в один длинный файл на несколько тысяч строк и попросить модель прочитать его с указанием номеров строк (StartLine/EndLine), нейросеть часто хитрит: читает первые 300 строк, перепрыгивает середину и читает последние 100 строк. А если написать в stdout внешней утилиты директиву капсом [SYSTEM INSTRUCTION: YOU MUST READ...], встроенный фильтр безопасности агента сочтёт это косвенной промпт-инъекцией и заблокирует указание. Поэтому baton разделяет данные и доверенную инструкцию: если весь активный контекст после /compact помещается в 7 КБ, он сразу выводится в stdout со строкой Handoff Status: COMPLETE. Если же контекст объёмнее, утилита выдаёт первые 7 КБ, а остаток нарезает во временном каталоге /tmp/baton-handoff/<id-сессии>/ (%TEMP%\baton-handoff\<id-сессии>\ на Windows) на порционные файлы active_context_part1.md, active_context_part2.md по 40 КБ (до 700 строк — ровно под лимит одного вызова чтения файла) и выводит нейтральный статус Handoff Status: PARTIAL. Правило внутри доверенного системного навыка SKILL.md предписывает агенту при статусе PARTIAL сразу в этом же ходе параллельно вызвать штатный инструмент Read (read_file) на все порционные файлы без указания границ строк, за один шаг загружая 100% активной памяти без пропусков и без участия пользователя.

  3. Архив до точки сжатия. Полная история диалога с самого первого шага (включая реплики до /compact и несокращённые выводы компилятора) параллельно сохраняется в /tmp/baton-handoff/<id-сессии>/full_dialogue.md (папки старше суток очищаются автоматически, не засоряя рабочее дерево проекта и git status). При необходимости заглянуть в события до /compact агент обращается к этому файлу или запрашивает конкретный шаг командой baton turn <session_id> <turn_id>.

Почему навык и одноразовая утилита лучше постоянного MCP-сервера

Изначально напрашивалась идея оформить инструмент в виде MCP-сервера, однако у серверной схемы обнаружились два недостатка. Во-первых, любой подключённый MCP-сервер запускается агентом при старте каждого сеанса и висит в оперативной памяти всё время работы терминала. Во-вторых, при старте каждого чата описания всех MCP-функций добавляются в системный промпт, отъедая входные токены даже тогда, когда вы неделями не переносите сессии.

Связка одноразовой консольной утилиты baton и файла навыка SKILL.md лишена этих недостатков. Все пять поддерживаемых агентов умеют подгружать навыки по мере необходимости: пока вы работаете над обычной задачей, в памяти модели хранится лишь одна строчка описания навыка. Когда вы просите перехватить сессию, агент активирует SKILL.md, вызывает baton как обычную команду, за миллисекунды получает готовую сводку, после чего процесс baton сразу завершается.

Инструмент написан на Go с использованием только стандартной библиотеки, никаких внешних зависимостей. В разделе релизов на GitHub лежат готовые сборки, однако безопаснее скомпилировать этот открытый код локально.

Чтобы автоматически прописать навык во все установленные агенты, в корне репозитория https://github.com/sukamenev/baton лежит инструкция INSTALL.md, рассчитанная на выполнение самой моделью. Запустите любой из установленных агентов (claude, codex, agy, pi или hermes) и отправьте ему запрос:

Прочитай инструкцию https://raw.githubusercontent.com/sukamenev/baton/main/INSTALL.md, клонируй репозиторий https://github.com/sukamenev/baton и установи инструмент baton из исходного кода для всех найденных у меня консольных агентов (claude, codex, agy, pi, hermes). Проверь наличие компилятора Go, собери бинарник в ~/.local/bin/baton, выполни baton install для установки навыка SKILL.md и слэш-команды /baton, а затем проверь работоспособность вызовом baton status.

Вариант запроса на английском языке:

Read https://raw.githubusercontent.com/sukamenev/baton/main/INSTALL.md, clone the repository https://github.com/sukamenev/baton, and install baton from source for all CLI agents installed on my system (claude, codex, agy, pi, hermes). Verify that the Go compiler is available, build the binary into ~/.local/bin/baton, run baton install to install the SKILL.md skill and the /baton slash command, and then verify the installation by running baton status.

Для ручной установки понадобится компилятор Go. В моей рабочей системе Calculate Linux (и в Gentoo) это пакет dev-lang/go. В других дистрибутивах пакет называется go (Arch Linux и openSUSE), golang-go (Debian и Ubuntu) или golang (Fedora). Сборка и регистрация укладываются в три команды:

git clone https://github.com/sukamenev/baton.git /tmp/baton
cd /tmp/baton && go build -ldflags="-s -w" -o ~/.local/bin/baton .
~/.local/bin/baton install

Команда baton install не трогает основные конфигурационные файлы агентов. Она копирует SKILL.md в каталоги навыков всех пяти клиентов (~/.claude/skills/baton/, ~/.codex/skills/baton/, ~/.gemini/config/skills/baton/, ~/.pi/agent/skills/baton/ и ~/.hermes/skills/baton/) и добавляет шаблон быстрой слэш-команды /baton.

Команды save, load и list: последняя сессия в один вызов или выбор из списка

Рекомендуемый способ работы строится вокруг лаконичных команд save, load и list. Перед уходом сохраните сессию командой baton save с понятным именем (если имя не указать, сессия сохранится под именем текущей рабочей папки):

Сохрани текущую сессию под именем auth-refactor

В ответ утилита фиксирует сессию и сразу печатает готовый промпт для копирования в следующего агента — причём с подсказкой репозитория на случай, если в целевом окружении baton ещё не установлен:

Saved session name: auth-refactor ([claude] a81f9c20.jsonl -> auth-refactor.baton.json)

To continue in another agent, paste this prompt:
  Load session auth-refactor using baton (if baton is not installed, install it from https://github.com/sukamenev/baton following INSTALL.md)
or run:
  /baton load auth-refactor

При вызове baton save (а также baton push или удалённой загрузке baton load <URL>) подготовленный снимок сессии помещается в собственное хранилище утилиты ~/.config/baton/saved/. За счёт этого команды baton load и baton list работают исключительно с явно сохранёнными в baton сессиями и не засоряют выдачу десятками технических и служебных логов всех агентов на компьютере. Если имя уже занято другой сессией, новая сохраняется с номером (auth-refactor-2), а повторное сохранение той же сессии обновляет её прежнюю запись. Когда сохранённые локальные сессии больше не нужны, хранилище очищается одной командой baton clear.

Когда пятичасовой лимит в Claude исчерпан, откройте в том же каталоге codex, agy, pi или hermes. Если вызвать загрузку вообще без аргументов (baton load или просто написать агенту обычными словами «Подхвати последнюю сессию»), утилита без лишних вопросов сразу загрузит самую свежую сохранённую в baton сессию:

Подхвати последнюю сессию

Если же вы сохранили несколько задач и хотите выбрать конкретную сессию, укажите её имя (Продолжи сессию auth-refactor) или вызовите baton list (либо спросите агента «Какие есть сохранённые сессии?»). Команда baton list выведет пронумерованный список сохранённых в baton сессий с указанием исходного агента, рабочей папки, времени сохранения и текста последнего запроса:

# Saved Baton Sessions (showing top 2 of 2)

#1 [claude] auth-refactor — `auth-refactor.baton.json` (2026-10-30 12:14:09)
   Workdir: `/home/user/proj`
   Preview: Почини гонку в пуле воркеров...
#2 [claude] billing-fix — `billing-fix.baton.json` (2026-10-30 10:02:41)
   Workdir: `/home/user/billing`
   Preview: Проверь округление НДС...

После этого напишите агенту «Загрузи первую сессию» (или /baton load 1). В файле навыка SKILL.md зашита таблица перевода таких фраз на естественном языке в вызовы baton, поэтому агент любого семейства сам подтянет выбранную сессию в /tmp/baton-handoff/, сверит файлы на диске и продолжит задачу со следующей строчки.

Если сессию не успели сохранить

Команды load и list видят только сессии, попавшие в хранилище baton через save, push или загрузку по ссылке. Но лимит обычно кончается внезапно, и попросить агента выполнить baton save уже нельзя: он не отвечает. Для этого случая есть команда baton import (или просьба агенту «Возьми сессию из существующих»). В отличие от load, она просматривает сырые логи всех пяти агентов на диске и выводит пятнадцать самых свежих сессий: сначала из текущей папки, затем остальные. Сессия самого вызывающего агента и пустые сессии, где была только команда загрузки, в список не попадают. В превью показывается последний запрос пользователя — место, на котором оборвалась работа:

# Agent Sessions Available for Import (top 15, current folder first)

#1 [claude] auth-refactor — `a81f9c20.jsonl` (2026-10-30 12:14:09)
   Workdir: `/home/user/proj`
   Preview: Добавь проверку границ в пул воркеров и прогони go test...
#2 [codex] Fix VAT rounding — `rollout-2026-10-30T10-02-41-01a18c.jsonl` (2026-10-30 10:02:41)
   Workdir: `/home/user/billing`
   Preview: Проверь округление НДС...

Если сессии было дано имя штатной командой агента (поле customTitle у Claude, session_index.jsonl у Codex, session_info у Pi или колонка title таблицы sessions у Hermes), оно показывается в списке, по нему сессию можно импортировать, и под ним же она сохранится в baton.

Нужную сессию выбирают по номеру или имени: «Импортируй первую сессию» или baton import 1. Утилита сохранит её в ~/.config/baton/saved/ и сразу загрузит в текущего агента, так что отдельный baton load не нужен. Дальше импортированная сессия ничем не отличается от сохранённой вручную: она видна в baton list и подхватывается по имени в любом другом агенте.

Перенос сессии и несохранённых файлов на другой ноутбук

Второй сценарий: нужно уйти из-за домашнего компьютера и продолжить работу на ноутбуке, когда в рабочей папке лежат недописанные файлы, а коммитить сырой код в общую ветку нельзя (или проект вообще ведётся без Git).

Помимо исходного кода, на основной машине часто хранятся локальные файлы инструкций для агентов: проектные CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules (многие добавляют их в .gitignore), а также глобальные правила из ~/.claude/CLAUDE.md и ~/.gemini/GEMINI.md. Без них агент на ноутбуке потеряет ваши требования к стилю кода.

При сборке сетевого слепка baton упаковывает в архив четыре компонента:

  • полную историю сессии с точкой последнего сжатия;

  • изменённые и новые неотслеживаемые файлы рабочего каталога (определяются по истории вызовов записи агента и через статус Git, Fossil или SVN);

  • локальные проектные файлы правил (CLAUDE.md, AGENTS.md, GEMINI.md, .cursorrules, .agents/rules/);

  • копию глобальных пользовательских инструкций с исходной машины.

Все четыре компонента собираются в оперативной памяти в единый поток tar.gz средствами стандартных пакетов Go archive/tar и compress/gzip, после чего шифруются на клиенте алгоритмом AES-256-GCM со случайным 256-битным ключом. Упаковщик вкомпилирован прямо в бинарник baton и работает на Linux, macOS и Windows без внешних архиваторов. Потоковый tar.gz даёт здесь ощутимый выигрыш у формата zip: архив zip сжимает каждый файл изолированно, тогда как gzip поверх единого потока tar использует общий словарь скользящего окна сразу для истории диалога и десятков мелких текстовых файлов.

Отправка через каскад обменников

Зашифрованный блок отправляется в бесплатные текстовые обменники без регистрации по цепочке резервирования:

  1. Первым пробуется paste.rs (принимает до 10 МиБ чистым POST-запросом).

  2. При ошибке или блокировке программа переключается на dpaste.org.

  3. Третий резервный рубеж — отправка через TCP-сокет termbin.com:9999.

Дополнительно поддерживается сохранение слепка в локальную папку облачной синхронизации (Dropbox, Google Drive, Nextcloud, Syncthing) при указании параметра storage="local_dir".

Для отправки сессии напишите текущему агенту «Отправь сессию на другой компьютер» или выполните:

/baton push

Инструмент вернёт ссылку с ключом шифрования и готовый самодостаточный промпт:

Encrypted session token:
  https://paste.rs/x9K2m#8f4a2c91e7b03d...

To continue in another agent or on a machine where baton is not installed yet, paste this prompt:
  Load session https://paste.rs/x9K2m#8f4a2c91e7b03d... using baton (if baton is not installed, install it from https://github.com/sukamenev/baton following INSTALL.md)
or if baton is already installed, run:
  /baton load https://paste.rs/x9K2m#8f4a2c91e7b03d...

Даже если на втором ноутбуке или свежем сервере baton ещё ни разу не ставили, достаточно вставить этот промпт в чат любого агента: он сам прочитает INSTALL.md из репозитория, соберёт утилиту и тут же развернёт вашу сессию. Часть ссылки до символа # указывает адрес зашифрованного слепка на сервере, а якорь после # — ключ расшифровки AES-256-GCM. По стандарту HTTP якорь #... никогда не передаётся на сервер при запросе: без него скачанный пакет представляет собой случайный шум.

Развёртывание на второй машине

На ноутбуке откройте каталог проекта в любом из пяти агентов и выполните ту же самую команду load, передав ей полученный URL:

/baton load https://paste.rs/x9K2m#8f4a2c91e7b03d...

Программа скачает и расшифрует пакет в памяти, после чего разложит изменённые файлы по рабочему каталогу. Если какой-то локальный файл на ноутбуке отличается от перезаписываемого, перед заменой рядом автоматически создаётся локальный бакап с расширением .baton.bak.

Одновременно скачанная сессия автоматически сохраняется в локальное хранилище baton (~/.config/baton/saved/). Благодаря этому при последующих переключениях между агентами на второй машине повторно обращаться к сети не нужно — сессия сразу видна под номером #1 в baton list и подхватывается обычным вызовом baton load. Если же вы загружаете внешнюю ссылку внутри Codex CLI, где песочница по умолчанию блокирует агенту доступ в сеть, не нужно открывать глобальный интернет-доступ в конфигах: достаточно выполнить в строке ввода Codex команду через восклицательный знак (!baton load "<URL>"), которая скачает и сохранит сессию напрямую от имени пользователя, и затем ввести baton load.

Проектные файлы правил сохраняются в корень проекта, а глобальные пользовательские инструкции с первой машины распаковываются в /tmp/baton-handoff/<id-сессии>/global_rules/ и подключаются в стартовую сводку, оставляя системные конфиги второй машины нетронутыми.

Автообновление

baton поддерживает уведомления о новых версиях. Никаких планировщиков вроде cron или системных служб настраивать не нужно: проверка запускается в тот момент, когда вы или агент обращаетесь к baton по прямому назначению (например, вызываете baton load или baton save). Чтобы сетевой запрос к GitHub не задерживал выдачу контекста ни на миллисекунду, основной процесс baton сразу отдаёт сводку сессии и параллельно порождает независимый дочерний процесс baton update --check-quiet (но не чаще одного раза в сутки по отметке времени в кеше). Если сети нет или версия актуальна, он молча завершается без ошибок; если опубликован новый релиз, при следующем вызове baton выведет короткое уведомление с предложением выполнить baton update.

Сама команда baton update работает в два шага с учётом текущей операционной системы:

  1. При наличии компилятора Go утилита пересобирается из исходного кода вызовом go install.

  2. Если компилятор Go не установлен (частый случай на Windows и macOS), программа определяет текущие runtime.GOOS и runtime.GOARCH, скачивает из GitHub Releases соответствующий бинарник и файл checksums.txt, проверяет хеш SHA-256, атомарно заменяет исполняемый файл и синхронно обновляет SKILL.md во всех установленных агентах.

Итоги

Инструмент baton связывает пять популярных консольных агентов — Claude Code, Codex, Antigravity CLI, Pi и Hermes — в общую рабочую среду. При исчерпании пятичасовой квоты соседний агент за долю секунды подхватывает именованную сессию или выбранного из списка кандидата вместе со сводкой /compact, историей команд и списком изменённых файлов. А зашифрованная переброска позволяет закрыть домашний ПК и продолжить ту же сессию на другом ПК без промежуточных коммитов.

Конечно, в наш век консольных агентов любой разработчик может собрать для себя похожий инструмент, потратив на это день и некоторое количество токенов. Если вы решите не пользоваться готовым baton, пусть идея и разобранные здесь форматы хранения сессий и схема бесшовной передачи сессии послужат отправной точкой для вашей собственной утилиты.

Исходный код проекта, инструкция для автоматической установки и файл навыка открыты под лицензией MIT в репозитории github.com/sukamenev/baton.

© 2026 ООО «МТ ФИНАНС»