Я работаю с Claude Code параллельно на трёх подписках Pro. Плюс коллеги на своих мульти-аккаунтах, часто в тех же файлах, часто в один и тот же день.

Когда мне надоело каждый новый чат заново пересказывать Claude’у курс дел - я придумала handoff’ы: короткую сводку, которую сессия пишет в конце и которую следующая читает при старте.

Когда на долгих проектах handoff’ов накопилось по восемьдесят штук и новая сессия тратила полчаса на «как мы сюда вообще пришли» - я придумала rollup’ы: один handoff, который сворачивает двадцать предыдущих. Цепочка rollup’ов на длинной дистанции становится летописью проекта.

Когда мне надоело смотреть, что делают Claude’ы коллег, и вручную перекладывать их статусы через мессенджер - я придумала почту, работающую между нашими клодами.

Три шага, три слоя, и каждый повторяет паттерн из распределённых систем, которому сорок лет. Ниже - что именно и как.

Слой 1: Handoffs - “я закончил, подхватывай”

Первая попытка: писать всё в один HANDOFF.md при закрытии сессии. Ломается мгновенно, как только чатов становится больше одного - последняя запись побеждает, остальные перезаписаны.

Я перешла на append-only модель: каждая сессия пишет свой файл с уникальным именем.

.claude/handoffs/
├── 2026-04-09_14-32_373d1618_drift-validator.md
├── 2026-04-09_16-47_b858f500_dashboard-refactor.md
├── 2026-04-10_11-20_a1c2d3e4_auth-fix.md
└── INDEX.md  (append-only лог)

Имя файла кодирует дату, время, первые 8 символов session-id и slug задачи. Два чата не могут создать файл с одинаковым именем физически - даже если попадут в одну секунду, session-id разные. Гонка состояний невозможна в принципе, без блокировок, без брокера - значения не имеет.

INDEX.md тоже append-only: новая строка внизу, старые не правим. Нужно обновить статус с ACTIVE на CLOSED - дописываем новую запись, не редактируем предыдущую. Ровно так работает write-ahead log (журнал упреждающей записи) в базах данных, и по той же причине: если процесс умер посреди записи, журнал остаётся согласованным.

Handoff - это не дневник, а контракт со следующей сессией. Пять секций, каждая отвечает на один вопрос. Цель - зачем сессия вообще была. Сделано - конкретные артефакты с путями, чтобы не пересобирать. НЕ сработало - подходы которые провалились, с причинами. Текущее состояние - что работает, что сломано, что заблокировано. Следующий шаг - одно конкретное действие для продолжения.

Для примера - реальный handoff с одной из сессий по color-checker проекту, по строчке из каждой секции:

  • Цель: “Color checker: CNN sweep + diffusion, первые результаты”

  • Сделано: “CNN baseline median 1.99 deg, 11M params, 21 MB”

  • НЕ сработало: “lr=3e-4 - NaN после epoch 10-13, нет gradient clipping”

  • Сломано: “Diffusion training OOM на bs=16”

  • Следующий шаг: “Inference скрипт для diffusion + visual sheets”

Самая ценная секция - “НЕ сработало”. Именно она спасает следующую сессию от часа в тупике. За четыре дня накопилось 27 handoff’ов - и ни один тупик не повторился, потому что каждая новая сессия видела предупреждение.

Handoff помещается в 1500 токенов. Сырой контекст сессии - около 100K. Компрессия примерно 67-кратная, без потерь того, что важно для продолжения.

И ещё: хуки вместо напоминаний

Писать handoff руками в конце каждой сессии неудобно. Решение - Stop-hook Claude Code. Когда сессия собирается закрыться, хук проверяет длину работы и, если сессия была содержательная, печатает в контекст: «ты ничего не записала, хочешь сохранить handoff?» На старте новой сессии я сама пишу «подхвати handoff по color-checker» или кидаю ссылку на файл - Claude читает его и продолжает с того места.

Поверх хуков работает сама модель. В длинных сессиях Claude сам предлагает «давай запишем handoff перед продолжением» - не дожидаясь ни моего запроса, ни Stop-события. Замечаю это регулярно после завершения крупной задачи или когда контекст заполнен процентов на 70-80. Программной проверки длины у меня в хуках нет - это решение самой модели.

Запись - автоматическая: хук и сама модель напоминают. Чтение - одна фраза в начале новой сессии.

Rollups - когда handoff’ов становится больше

Пока проект короткий и укладывается в несколько чатов - проблемы нет. Но когда работаешь над чем-то месяц или два, накапливаются десятки handoff’ов с пройденными тупиками и опровергнутыми гипотезами. Новая сессия прочитывает последние, глубже не заглядывает - и спокойно предлагает подход, который я уже опровергла две недели назад. Возвращается тот же цикл перепроверки гипотез, от которого я уходила с самого начала.

Решение - сворачивать старое. Один rollup-handoff суммирует двадцать предыдущих, ставит точку «свёрнуто до вот этой даты» и проставляет каждому подчинённому handoff’у строку rolled_up_into: с именем rollup-файла. Новая сессия читает один rollup плюс только те handoff’ы, что датированы позже его границы. Остальное лежит для истории, но в контекст не затягивается.

Файлу-rollup-у в имени дают сегмент rollup_, чтобы он был виден в INDEX.md одним взглядом. Frontmatter содержит type: rollup, список covers: со всеми подчинёнными файлами и through: с датой-границей. Больше ничего не нужно: текст rollup’а - просто обычный handoff, только описывает не одну сессию, а два десятка.

Паттерн разработан не мной. У PavelMuntyan/MF0-1984 ровно то же самое решено в SQLite-схеме через колонку covered_until_message_id. Я скопировала идею, заменив таблицу на markdown-frontmatter. Не фокус - привычная бюрократия, «страница с кратким содержанием предыдущих глав».

Слой 2: Locks - «этот ресурс сейчас мой»

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

Handoff’ы только дописываются, каждый пишет своё - никто никого не затирает. Но реальные ресурсы изменяемы. Две сессии не могут одновременно рефакторить один модуль (получится конфликт слияния), не могут одновременно катить деплой на стейдж (вторая перезапишет первую), не могут одновременно тренировать модель на одной GPU (вторая получит OOM).

Нужны замки. Один файл на ресурс, внутри - кто взял, когда, для чего. Имя файла кодирует имя ресурса: auth-middleware.lock, staging-deploy.lock, gpu-host-a_gpu2.lock. Новая сессия видит по списку файлов, что занято, выбирает свободное - без моего вмешательства.

Фокус - в том, как файл создаётся. Python-вызов os.open(path, O_CREAT | O_EXCL) атомарный: если файл уже есть, вызов падает с ошибкой. Два процесса физически не могут создать один файл одновременно, эту гарантию даёт ядро. Это тот же примитив, на котором стоят /var/lock/ на Unix и .pid-файлы процессов ещё с 80-х.

Но lock без heartbeat работает только до первого сбоя сессии. Сессия упала, lock остался, следующая сессия ждёт вечность. Поэтому активная сессия обновляет отметку времени раз в 30-60 минут. Если lock старше четырёх часов - возможно, сессия мертва. Но «возможно» - это еще не «точно». Перед тем как забрать lock, я проверяю сам ресурс: SSH на сервер с ps, nvidia-smi на GPU, статус очереди деплоев, git-статус нужного модуля - что угодно, всё что он скажет: реально ли ресурс занят или просто не освободили за собой. Только после подтверждения - перехватываю. Внешняя проверка перед перехватом - тот же приём из Chubby и ZooKeeper: доверяй, но проверяй.

Рядом с lock-файлом лежит JSON с метаданными: slug задачи, session-id, время захвата, описание, список задействованных файлов. Новая сессия читает папку .claude/locks/ и за секунду видит: «auth-middleware занят ani под рефакторинг, второй час», «GPU 2 занята под тренировку эмбеддингов». Вопрос «что тут происходит?» больше не встаёт.

Первым моим инстинктом было написать хук на PreToolUse, который парсит команды и автоматически обновляет таблицу занятости: «пользователь запустил команду, касающуюся ресурса X, - пометить X занятым». Увы, тупик: команды слишком разнообразные. Regex-парсер превратился бы в машину ложноположительх срабатываний. Правильный порядок оказался обратным: сначала соглашение и файлы, автоматизация - потом, и только для паттернов, которые активно повторяются.

Слой 3: Почта - “Артём, посмотри мой код”

Handoffs и rollups решают передачу контекста во времени - одна сессия закрылась, следующая подхватила. Locks решают ресурсы - один процесс работает, остальные ждут. Но осталась третья задача, которая ни там, ни тут: синхронизация между живыми сессиями.

Типичный день: я поставила Claude’у коллеги задачу рефакторить auth-middleware, перешла на свой чат писать плагин. Через час хочу проверить: где он сейчас, упёрся во что-то? Раньше это решалось так: я открываю мессенджер, пишу коллеге - коллега смотрит в свой чат - копирует мне текст. Три перекладки текста через человека. Каждый вечер. В куче проектов проектах.

Нужна адресная коммуникация между сессиями напрямую. Чтобы я написала ему: «ты ещё делаешь middleware или переключился?», он ответил из своего чата, я получила ответ в своём. Между процессами, которые могут никогда не быть онлайн одновременно. С гарантиями доставки - если не дочитал сейчас, дочитает в следующей сессии.

Я задумалась - и поняла что описываю email.

Структура сразу собралась. Папка на каждого пользователя, внутри - inbox, sent, archive. Отдельная папка all/ для broadcast-сообщений.

.claude/mailbox/
├── ani/
│   ├── inbox/
│   ├── sent/
│   └── archive/
├── artem/
│   └── ...
└── all/          (broadcast)

Каждое сообщение - отдельный markdown-файл с YAML frontmatter: from, to, type, subject, message_id, in_reply_to, status. Сам текст письма - ниже, в markdown.

Простой пример: сессия ani пишет артёму question с темой “Re: cmake libsodium”, тело - “Ты менял линковку libsodium в CMakeLists? У меня build падает на target_link_libraries”. Через час Артём открывает Claude Code на своей машине, хук на UserPromptSubmit проверяет его inbox перед первым промптом, видит непрочитанное сообщение и подсовывает его в контекст. Артём отвечает тут же, reply попадает мне в inbox.

Цепочки через in_reply_to - как в RFC 822. Подтверждения доставки - отдельные файлы-квитанции. Рассылка: адрес to: * кладёт сообщение в папку all/, её проверяют все. Дедупликация: хук помнит в .watcher_state.json, какие письма уже показывал, одно и то же в контекст второй раз не попадёт. Ни опроса, ни брокера, ни отдельного фонового процесса - всё стоит на хуке, который срабатывает в нужный момент.

Каждая фича маппится 1:1 на классический email:

Email (RFC 822, 1982)

Mailbox агентов

To / Cc

Адресация конкретной сессии

From / Reply-To

Обратный адрес для ответа

Subject

Triage без чтения тела

In-Reply-To

Цепочки сообщений

Date

Свежесть запроса

Read/Unread

Видел или ещё нет

Inbox per user

Per-recipient очередь

Sent folder

Аудит отправителя

Delivery receipt

Подтверждение без “ты видел?”

Email решает ровно ту же задачу: асинхронная доставка с гарантиями между процессами которые могут быть неактивны. Claude Code параллельные сессии - та же задача в новой упаковке.

mclaude: всё вместе

Я оформила три слоя в open-source инструмент - mclaude. Python, без зависимостей в ядре, 193 теста, v0.6.0.

Шесть слоёв (три базовых + три дополнительных):

Слой

Что делает

Строк кода

Locks

Атомарный захват ресурсов + heartbeat

469

Handoffs

Per-session сводки, append-only INDEX

371

Memory Graph

Иерархическое хранилище решений и gotchas

~400

Identity Registry

Имена для сессий (ani, artem, nastya)

246

Messages + Active Mail

Межсессионная коммуникация с threading

400 + 236

Code Indexer

AST-сканер → code-map.md + llms.txt

v0.6.0

Шестой слой - индексатор кода - появился в v0.6.0. AST-сканер пробегает проект и собирает два артефакта, оба - для агента. code-map.md - карта модулей, классов и функций. llms.txt - компактный индекс для быстрой подгрузки. То, что code-map.md читается ещё и человеком как обычный документ, - побочный эффект: его формат взят из нашей общей базы знаний, которая изначально сделана человекочитаемой. Новая сессия не тратит первые минуты на перечитывание кода - карта уже лежит в репо, рядышком с handoff’ами.

Плюс 5 хуков - четыре на события Claude Code (SessionStart, PreToolUse, UserPromptSubmit, Stop) и git pre-commit guard, который блокирует коммит в заблокированный файл. MCP-сервер с 20+ инструментами отдаёт структурированный JSON прямо в Claude Code, без парсинга CLI-вывода.

Принцип: hub - ускоритель, не зависимость. Всё работает на файлах. Hub поднят - сообщения летают через WebSocket в реальном времени; hub упал - bridge пишет в тот же .claude/messages/inbox/ в том же формате, и получатель через git pull увидит ровно то же самое. Удали mclaude package - файлы в .claude/ останутся читаемыми. Markdown для нарратива, JSON для метаданных.

Чтобы увидеть паттерн глазами за 30 секунд: pip install mclaude && mclaude demo --no-pause. Скрипт прогоняет две симулированные сессии (ani и vasya) через все шесть слоёв и выдаёт Mermaid-диаграмму того, что случилось - её можно вставить в любой PR или пост.

Дыра в экосистеме

Тогда я пошла смотреть, кто ещё решает эту задачу.

Изоляция - много решений. Git worktrees (parallel-cc). Песочницы (Kmux). Отдельные порты на каждого агента с прокидыванием env. Anthropic Agent Teams через очередь задач с claim’ом. Всё решает “сессии не мешают друг другу” через то, что у них нет общего состояния.

Координация общего состояния - почти пусто. Только claude_code_agent_farm у Dicklesworthstone делает file-based locks. Но это оркестрация для 20+ агентов в tmux, не сценарий многих чатов над одним проектом.

GitHub issue #19364 “Add session lock file” - открытый feature request от сообщества. Не реализован. Issue #29217 - .claude.json получает повреждённое состояние при параллельных записях. У одного пользователя - 315 битых бэкапов за 7 дней.

Экосистема зрелая в избегании общего состояния. Пустая в его координации.

Распределённые системы, вид сбоку

Всё это - паттерны из 80-х. Heartbeat-таймауты для обнаружения мёртвых процессов. Lock-файлы с именами по ресурсу. Журнал, в который только дописывают. Внешняя проверка перед перехватом устаревшего замка. Отдельный файл на ресурс вместо одной общей таблицы (маленькое окно конфликта).

Chubby от Google, ZooKeeper, любая координационная система последних 40 лет использует именно это. AI-агенты - просто новый вид процессов в уже понятной распределённой системе.

И email-протокол (RFC 822, 1982) оказался готовым решением для межагентной коммуникации. Не потому что “ну, мы знаем email”. А потому что email решает ровно ту же абстрактную задачу: асинхронная доставка между процессами с ограниченным хранилищем, цепочками сообщений и подтверждением доставки.

Каждое поколение распределённых систем переоткрывает что email был прав. В 2010-х это были очереди сообщений (RabbitMQ, Kafka). В 2026-м - AI-агенты.

Реальное использование

Три человека, один C++ проект плагина, 6 параллельных сессий Claude Code. Каждый работает над своей частью: архитектура, inference pipeline, UI.

Один агент решил поменять key derivation в scramble-модуле. Отправил рассылку через mailbox/all/. Другой агент на следующей сессии увидел сообщение до того как начал писать код, и учёл изменение. Без mailbox - он бы написал код с несовместимым форматом. Конфликт слияния в лучшем случае, повреждение данных в худшем.

27 handoffs за 4 дня. 0 повторённых тупиков. Handoff между подписками (закончил в одной, продолжил в другой) сэкономил ~4 часа восстановления контекста.

Где сломается и чего не знаю

Масштаб. Всё тестировалось на 3-6 параллельных сессий, 2-3 людей, 1 C++ проект и бесчисленное множество сессий работы над сайтом. Что будет при 20+ сессий от 10 агентов - не проверяла. Подозреваю что .claude/messages/inbox/{user}/ станет узким местом: дедупликация через .watcher_state.json линейная по числу писем, на сотнях сообщений будет медленно. До этого ещё не добралась.

Между несколькими машинами без hub. Файловое ядро работает через общий репозиторий: закомиттил - другой увидел при git pull. Это добавляет задержку минутами вместо секунд. Hub поднят - секунды, WebSocket. Но hub - отдельный процесс который тоже надо поднимать и поддерживать. Пока использую локально и через общий репозиторий, hub в продакшне не испытывала.

Race при одновременном взятии одного lock. O_CREAT | O_EXCL атомарен на локальной файловой системе. На сетевой (NFS, SMB) гарантия теряется - зависит от реализации. Для моего случая (git repo + локальная ФС) проблемы нет. Кто положит .claude/locks/ на NFS - может словить race.

Проверка устаревшего lock - полуавтоматическая. Перед перехватом старого lock я описала проверку через nvidia-smi, ps, git-статус. Это ручные проверки для человека. Для полностью автоматического обнаружения нужен обработчик который знает типы ресурсов и умеет проверять каждый. Пока - человек читает метаданные lock и решает.

Почта без автоматического пробуждения. Сейчас письма показываются на UserPromptSubmit - то есть только когда активная сессия что-то пишет. Настоящая push-доставка (сессия получила письмо → сессия проснулась) пока не реализована. Описана в планах ниже.

Human-in-the-loop нужен. Система не заменяет человека-координатора полностью. Если 6 агентов одновременно решили что auth-middleware требует переделки - они договорятся через почту, но кто именно возьмёт в работу всё равно решает человек. Mclaude даёт инструменты для переговоров, не автономную оркестрацию.

Что дальше

mclaude - open source, MIT. 193 теста, Python 3.9+.

pip install mclaude
mclaude hooks install --apply

Репозиторий: github.com/AnastasiyaW/mclaude. Принципы отдельно от кода: claude-code-config/principles/18.

Чего системе сейчас не хватает и что я доделываю:

Автопробуждение сессии при новом письме. Про это уже говорила в секции про почту. Сейчас хук срабатывает на UserPromptSubmit - письма лежат в инбоксе, пока я что-то не напишу в свой чат. Правильная доставка должна будить сессию сама, в момент прихода письма. Собираю в ближайшем обновлении.

Наблюдение за перепиской команды. Небольшая программа-дашборд, которая смотрит на живой поток писем между моими Claude’ами и Claude’ами коллег: кто кого о чём спрашивает, где затыки, что уже ответили. Тот же файловый протокол, только с другой стороны - читает папку .claude/messages/ и показывает ленту. Использую как мониторинг того, что происходит в разработке, без открывания каждого чата по одному.

Трекер задач для агентской команды. Небольшой хаб поверх mclaude, где задача ставится не «в Jira на человека», а «конкретному Claude’у коллеги». Видно статус, можно переключить исполнителя, закрыть по handoff’у. Почему эта система должна быть отдельной от Jira или Linear, а не надстройкой над ними, - разговор отдельный, расскажу в другой статье.

Если работаете с параллельными AI-сессиями и сталкивались с гонками за ресурсы - как решали? Worktrees + изоляция, или тоже пришли к координации через общее состояние?

Комментарии (0)