Реверс-инжиниринг Claude Code Agent Teams: архитектура и протокол

Машинный перевод Оригинал статьи.

Banner for Реверс-инжиниринг Claude Code Agent Teams: архитектура и протокол

содержание

Введение

В Claude Code (v2.1.47) есть экспериментальная функция Agent Teams: несколько сессий Claude Code координируют совместную работу по схеме «лидер и участники команды». Я разрабатываю Hive — мультиагентный оркестратор для работы с кодом со сходными целями, но совсем другой архитектурой. Поэтому мне захотелось разобраться, как подход Anthropic работает изнутри.

В этой статье я описываю, что выяснил:

  1. Прочитав официальную документацию
  2. Изучив реальные артефакты, оставшиеся на диске после предыдущих сессий команд
  3. Поручив Claude проанализировать бинарный файл Claude Code (v2.1.47), чтобы разобраться в деталях реализации (ха!)

Оглавление

1. Обзор архитектуры

Команда агентов состоит из четырёх компонентов:

Компонент Роль
Лидер команды Основная сессия Claude Code, которая создаёт команду и запускает участников
Участники команды Отдельные экземпляры Claude Code, каждый со своим контекстным окном
Список задач Общие задачи, хранящиеся в отдельных JSON-файлах на диске
Почтовый ящик Файлы входящих сообщений для каждого агента

Весь слой координации основан на файлах. Файловая система в ~/.claude/ — единственная основа координации:

~/.claude/
├── teams/{team-name}/
│   ├── config.json                  # team membership registry
│   └── inboxes/{agent-name}.json    # per-agent mailbox
└── tasks/{team-name}/
    ├── .lock                        # flock() for concurrent task claiming
    ├── .highwatermark               # auto-increment counter
    ├── 1.json                       # individual task files
    ├── 2.json
    └── ...

Это принципиально децентрализованная архитектура. Лидер — просто ещё одна сессия Claude с дополнительными инструментами (TeamCreate, TeamDelete, SendMessage). Фонового процесса нет. Координация возникает благодаря совместному доступу к файлам.

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

watch -n 0.5 'tree ~/.claude/teams/ 2>/dev/null; echo "---"; tree ~/.claude/tasks/ 2>/dev/null'

Например, с таким промптом:

can you spawn an agent team to examine this codebase?
  - have one look for bugs
  - have one look for complexity
  - have one look for good things to call out and play devil's advocate against the other two agents

Я наблюдал следующее:

teams
└── code-review
    ├── config.json
    └── inboxes
        ├── bug-hunter.json
        ├── complexity-analyst.json
        ├── devils-advocate.json
        └── team-lead.json

Конфигурация команды

Конфигурация команды в ~/.claude/teams/{team-name}/config.json содержит массив members, который участники читают, чтобы узнавать друг о друге:

{
  "members": [
    { "name": "team-lead", "agentId": "abc-123", "agentType": "leader" },
    {
      "name": "researcher",
      "agentId": "def-456",
      "agentType": "general-purpose"
    }
  ]
}

Имена — основной механизм адресации (UUID существуют, но для маршрутизации не используются). При обмене сообщениями и назначении задач всегда используется поле name.

2. Общий список задач

Формат файлов

Каждая задача хранится в отдельном JSON-файле в ~/.claude/tasks/{team-name}/. Вот реальный пример из предыдущей сессии:

{
  "id": "1",
  "subject": "Hunt for bugs across the codebase",
  "description": "...",
  "activeForm": "Hunting for bugs",
  "owner": "bug-hunter",
  "status": "completed",
  "blocks": [],
  "blockedBy": []
}

Схема задачи:

Поле Тип Описание
id string Числовой ID, автоматически увеличиваемый через .highwatermark
subject string Заголовок в повелительном наклонении (например, «Запусти тесты»)
description string Подробные требования и критерии приёмки
activeForm string Формулировка в Present Continuous для индикатора загрузки («Выполняются тесты»)
status string pending → in_progress → completed (или deleted)
blocks string[] ID задач, которые блокирует эта задача
blockedBy string[] ID задач, которые должны завершиться до начала этой задачи

Управление конкурентным доступом

Координацию обеспечивают два специальных файла:

  • .lock: файл размером 0 байт для взаимного исключения на уровне файловой системы (flock()). Присутствует во всех 42 каталогах задач, которые я обнаружил на своём компьютере.
  • .highwatermark: содержит одно целое число (например, "3", "13"). Это следующий доступный ID задачи для автоматической нумерации.

Взятие задач в работу

При взятии задач в работу используются файловые блокировки, чтобы предотвратить гонки. Участники предпочитают сначала брать задачи с наименьшим ID. Задачу с непустым массивом blockedBy нельзя взять в работу, пока все блокирующие задачи не перейдут в конечное состояние.

Наблюдение: большинство каталогов задач пусты

Из 42 каталогов задач на моём компьютере только 5 содержали реальные JSON-файлы задач. В остальных 37 были только .lock и .highwatermark. Вероятно, это означает, что задачи удаляются после завершения, либо это были сессии, в которых Claude использовал внутренний список задач (доступный с момента появления этой функции), не разбивая работу на файлы подзадач.

3. Взаимодействие агентов

Схема с почтовыми ящиками

У каждого агента есть файл с JSON-массивом в ~/.claude/teams/{team-name}/inboxes/{agent-name}.json. Вот реальный почтовый ящик из предыдущей сессии, в которой лидер команды поручил работу controlplane-agent:

[
  {
    "from": "team-lead",
    "text": "{\"type\":\"task_assignment\",\"taskId\":\"1\",\"subject\":\"Phase 2: Control-plane - remove participants/presence\",\"description\":\"Remove multiplayer code from the control-plane package...\",\"assignedBy\":\"team-lead\",\"timestamp\":\"2026-02-18T02:37:16.890Z\"}",
    "timestamp": "2026-02-18T02:37:16.890Z",
    "read": false
  }
]

Обратите внимание на кодирование JSON внутри JSON: поле text — это JSON-строка, содержащая сериализованный объект сообщения. Внешняя оболочка содержит поля from, text, timestamp и read.

Типы сообщений

Поле type внутри полезной нагрузки text поддерживает следующие значения:

Тип Направление Назначение
task_assignment лидер → участник Назначить задачу со всеми подробностями
message любой → любой Личное сообщение одному получателю
broadcast лидер → все Одно и то же сообщение каждому участнику
shutdown_request лидер → участник Запросить корректное завершение работы
shutdown_response участник → лидер Одобрить или отклонить завершение работы
plan_approval_request участник → лидер Отправить план на проверку
plan_approval_response лидер → участник Одобрить или отклонить с пояснениями
idle_notification участник → лидер Автоматически отправляется по завершении хода участника

Механизм доставки

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

Чтение: получатель периодически опрашивает собственный файл входящих сообщений. Новые сообщения вставляются в диалог как искусственные ходы (они выглядят так, будто их отправил пользователь).

Широковещательная рассылка: буквально записывает одно и то же сообщение в файл входящих сообщений каждого участника. Расход токенов растёт линейно с размером команды.

Всё взаимодействие сводится к добавлению записей в файл + чтению файла. Задержка между отправкой и получением зависит от интервала опроса у получателя.

Видимость личных сообщений между участниками

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

4. Запуск агентов и жизненный цикл

Как создаются участники команды

Каждый участник — это отдельный процесс CLI claude. Лидер запускает их через инструмент Task с параметрами team_name и name. Для запускаемого процесса задаются переменные окружения:

  • CLAUDE_CODE_TEAM_NAME: автоматически задаётся для запускаемых участников
  • CLAUDE_CODE_PLAN_MODE_REQUIRED: получает значение true, если требуется одобрение плана

Инициализация контекста

Участники загружают тот же контекст проекта, что и любая новая сессия:

  • Файлы CLAUDE.md из рабочего каталога
  • Серверы MCP
  • Навыки
  • Стартовый промпт от лидера

История диалога лидера НЕ передаётся. Каждый участник начинает с нуля, имея в контексте только стартовый промпт.

Внутренняя реализация

Анализ бинарного файла Claude Code v2.1.47 показал, что контекст участника управляется через AsyncLocalStorage со следующими полями:

  • agentId, agentName, teamName
  • parentSessionId, color
  • planModeRequired

Ключевые внутренние функции:

  • isTeammate() / isTeamLead(): определение роли
  • waitForTeammatesToBecomeIdle(): примитив синхронизации для лидера
  • getTeammateContext() / setDynamicTeamContext(): управление контекстом во время выполнения

Определение состояния ожидания

После каждого хода LLM участник автоматически переходит в ожидание и отправляет idle_notification лидеру. Это штатное состояние ожидания, а не ошибка или признак устаревшего состояния. Сообщение участнику в состоянии ожидания пробуждает его (при следующем опросе он обнаруживает сообщение в почтовом ящике).

Протокол завершения работы

  1. Лидер отправляет shutdown_request участнику
  2. Участник может одобрить запрос (корректно завершает работу) или отклонить его (продолжает работать, сообщив причину)
  3. Очистка команды через TeamDelete удаляет ~/.claude/teams/{team-name}/ и ~/.claude/tasks/{team-name}/
  4. Очистка завершается ошибкой, если какие-либо участники ещё активны; сначала нужно завершить их работу

Наследование разрешений

При запуске участники наследуют режим разрешений лидера. Если лидер запускается с --dangerously-skip-permissions, все участники тоже. Режимы отдельных участников можно изменить после запуска, но задать их для каждого участника при запуске нельзя.

5. Проверки качества и хуки

Agent Teams интегрируется с системой хуков Claude Code для обеспечения качества:

Хук TeammateIdle

Срабатывает, когда участник собирается перейти в ожидание. Код выхода 2 передаёт содержимое stderr в качестве обратной связи и блокирует переход в ожидание, чтобы участник продолжал работу.

{
  "hook_event_name": "TeammateIdle",
  "teammate_name": "researcher",
  "team_name": "my-project"
}

Хук TaskCompleted

Срабатывает при попытке отметить задачу как завершённую. Код выхода 2 не даёт завершить задачу и передаёт содержимое stderr в качестве обратной связи.

{
  "hook_event_name": "TaskCompleted",
  "task_id": "task-001",
  "task_subject": "Implement user authentication",
  "task_description": "Add login and signup endpoints",
  "teammate_name": "implementer",
  "team_name": "my-project"
}

Этот хук срабатывает в двух случаях: (1) когда любой агент явно отмечает задачу как завершённую через TaskUpdate или (2) когда участник команды агентов завершает свой ход, имея задачи в работе.

Типы обработчиков хуков

Тип Описание
command Скрипт оболочки. JSON на stdin, решения определяются кодами выхода.
prompt Оценка с помощью LLM за один ход. Возвращает {ok, reason}.
agent Субагент с инструментами чтения, работающий в несколько ходов. До 50 ходов.

6. Экономика токенов

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

Базовый ориентир

  • Средние расходы на Claude Code: ~$6 на разработчика в день
  • Команды агентов: затраты сверх базового уровня примерно пропорциональны размеру команды

7. Сводка по архитектуре

Аспект Claude Code Agent Teams
Основа координации Плоские файлы (~/.claude/tasks/, ~/.claude/teams/)
Формат задач Отдельный JSON-файл на задачу + .lock для взятия в работу
Обмен сообщениями JSON-файлы входящих сообщений (добавление + опрос)
Жизненный цикл агентов Самоуправляемые CLI-процессы
Изоляция работы Общий рабочий каталог
Стратегия слияния Отсутствует (агенты редактируют файлы напрямую)
Повторные попытки/эскалация Вручную (решает лидер или вмешивается пользователь)
Топология Лидер + участники одного уровня, прямой обмен сообщениями между участниками
Планирование Самостоятельное взятие задач в работу (участники берут следующую задачу)
Сохранность состояния Только файлы; возобновление работы участников внутри процесса не поддерживается
Проверки качества Хуки оболочки (TeammateIdle, TaskCompleted)
Учёт токенов Только по сессиям, без суммирования по агентам
Обнаружение зависаний Вручную (пользователь замечает, что участник остановился)
Управление параллелизмом Неявное (размер команды = число участников)
Модель зависимостей blocks/blockedBy в файлах задач

Источники

Официальная документация

Артефакты на диске (Claude Code v2.1.47)

Обнаружены в /Users/tau/.claude/:

  • Каталоги команд с файлами config.json и inboxes/{agent-name}.json
  • Каталоги задач с .lock, .highwatermark и отдельными JSON-файлами задач
  • Пример сообщения о назначении задачи от team-lead к cp-agent с временной меткой 2026-02-18T02:37:16.890Z

Анализ бинарного файла

Бинарный файл Claude Code v2.1.47. Внутренние функции, выявленные при анализе строк: getTeamName, getAgentName, getAgentId, isTeammate, isTeamLead, waitForTeammatesToBecomeIdle, getTeammateContext, setDynamicTeamContext, createTeammateContext. Поля контекста AsyncLocalStorage: agentId, agentName, teamName, parentSessionId, color, planModeRequired.

Кодовая база Hive