Engenharia reversa do Agent Teams do Claude Code: arquitetura e protocolo

Tradução automática Artigo original.

Banner for Engenharia reversa do Agent Teams do Claude Code: arquitetura e protocolo

conteúdo

Introdução

O Claude Code (v2.1.47) traz um recurso experimental chamado Agent Teams: várias sessões do Claude Code coordenam o trabalho compartilhado por meio de uma topologia com um líder e integrantes da equipe. Venho desenvolvendo o Hive, um orquestrador de programação com múltiplos agentes que tem objetivos semelhantes, mas uma arquitetura muito diferente, então eu queria entender como a abordagem da Anthropic funciona por dentro.

Este artigo documenta o que descobri ao:

  1. Ler a documentação oficial
  2. Examinar artefatos reais deixados em disco por sessões anteriores de equipes
  3. Deixar o Claude analisar o binário do Claude Code (v2.1.47) em busca de detalhes de implementação (haha!)

Índice

1. Visão geral da arquitetura

Uma equipe de agentes consiste em quatro componentes:

Componente Função
Líder da equipe A sessão principal do Claude Code que cria a equipe e inicia seus integrantes
Integrantes Instâncias separadas do Claude Code, cada uma com sua própria janela de contexto
Lista de tarefas Itens de trabalho compartilhados armazenados como arquivos JSON individuais em disco
Caixa de mensagens Arquivos de caixa de entrada por agente para entrega de mensagens

Toda a camada de coordenação é baseada em arquivos. O sistema de arquivos em ~/.claude/ é a única base de coordenação:

~/.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
    └── ...

Essa arquitetura é fundamentalmente descentralizada. O líder é apenas mais uma sessão do Claude com ferramentas extras (TeamCreate, TeamDelete, SendMessage). Não há nenhum processo em segundo plano. A coordenação surge do acesso compartilhado aos arquivos.

Em uma sessão ativa, se você pedir ao Claude para criar uma equipe para realizar algum tipo de tarefa e depois executar o seguinte em outra janela, poderá observar o sistema de arquivos sendo atualizado em tempo real.

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

Por exemplo, com o seguinte prompt:

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

Observei isto:

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

Configuração da equipe

A configuração da equipe em ~/.claude/teams/{team-name}/config.json contém um array members que os integrantes leem para descobrir uns aos outros:

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

Os nomes são o principal mecanismo de endereçamento (há UUIDs, mas eles não são usados para roteamento). Toda a troca de mensagens e atribuição de tarefas usa o campo name.

2. A lista de tarefas compartilhada

Formato dos arquivos

Cada tarefa é armazenada como um arquivo JSON individual em ~/.claude/tasks/{team-name}/. Aqui está um exemplo real de uma sessão anterior:

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

Esquema das tarefas:

Campo Tipo Descrição
id string ID numérico, com incremento automático via .highwatermark
subject string Título no imperativo (por exemplo, "Execute os testes")
description string Requisitos detalhados e critérios de aceitação
activeForm string Forma no gerúndio para exibição no indicador de atividade ("Executando testes")
status string pending → in_progress → completed (ou deleted)
blocks string[] IDs das tarefas que esta tarefa bloqueia
blockedBy string[] IDs das tarefas que precisam ser concluídas antes que esta tarefa possa começar

Controle de concorrência

Dois arquivos especiais permitem a coordenação:

  • .lock: Um arquivo de 0 bytes usado para exclusão mútua no nível do sistema de arquivos (flock()). Presente em todos os 42 diretórios de tarefas observados na minha máquina.
  • .highwatermark: Contém um único número inteiro (por exemplo, "3", "13"). É o próximo ID de tarefa disponível para incremento automático.

Como assumir tarefas

O processo de assumir tarefas usa bloqueio de arquivos para evitar condições de corrida. Os integrantes dão preferência às tarefas com os menores IDs. Uma tarefa com um array blockedBy não vazio não pode ser assumida até que todas as tarefas que a bloqueiam estejam em um estado terminal.

Observação: a maioria dos diretórios de tarefas está vazia

Dos 42 diretórios de tarefas na minha máquina, apenas 5 continham arquivos JSON de tarefas propriamente ditos. Os outros 37 tinham apenas .lock e .highwatermark. Isso provavelmente significa que as tarefas são removidas após a conclusão, ou que essas foram sessões em que o Claude usou a lista de tarefas interna (disponível desde o lançamento do recurso de lista de tarefas) sem decompor o trabalho em arquivos de subtarefas.

3. Comunicação entre agentes

Padrão de caixas de mensagens

Cada agente tem um arquivo contendo um array JSON em ~/.claude/teams/{team-name}/inboxes/{agent-name}.json. Aqui está uma caixa de entrada real de uma sessão anterior em que um líder de equipe enviou trabalho para um 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
  }
]

Observe a codificação JSON dentro de JSON: o campo text é uma string JSON que contém um objeto de mensagem serializado. O envelope externo tem os campos from, text, timestamp e read.

Tipos de mensagens

O campo type dentro do conteúdo de text aceita:

Tipo Direção Finalidade
task_assignment líder → integrante Atribuir uma tarefa com todos os detalhes
message qualquer agente → qualquer agente Mensagem direta para um destinatário
broadcast líder → todos A mesma mensagem para todos os integrantes
shutdown_request líder → integrante Solicitar encerramento controlado
shutdown_response integrante → líder Aprovar ou rejeitar o encerramento
plan_approval_request integrante → líder Enviar um plano para revisão
plan_approval_response líder → integrante Aprovar ou rejeitar com feedback
idle_notification integrante → líder Enviada automaticamente quando o turno do integrante termina

Mecanismo de entrega

Fluxo de escrita: O remetente adiciona uma nova entrada ao final do array JSON no arquivo de caixa de entrada do destinatário.

Fluxo de leitura: O destinatário consulta periodicamente seu próprio arquivo de caixa de entrada. Novas mensagens são injetadas como turnos sintéticos da conversa (elas aparecem como se um usuário as tivesse enviado).

Broadcast: Literalmente grava a mesma mensagem no arquivo de caixa de entrada de cada integrante. O custo de tokens cresce linearmente com o tamanho da equipe.

A comunicação se resume a adicionar dados ao final de um arquivo + ler um arquivo. A latência entre o envio e o recebimento depende do intervalo de consulta do destinatário.

Visibilidade das mensagens diretas entre pares

Quando um integrante envia uma mensagem direta para outro integrante, um breve resumo é incluído na notificação de inatividade enviada ao líder. Isso permite que o líder acompanhe a colaboração entre pares sem receber o conteúdo completo da mensagem.

4. Criação e ciclo de vida dos agentes

Como os integrantes são criados

Cada integrante é um processo separado da CLI claude. O líder os inicia por meio da ferramenta Task com os parâmetros team_name e name. Variáveis de ambiente são definidas no processo criado:

  • CLAUDE_CODE_TEAM_NAME: definida automaticamente nos integrantes criados
  • CLAUDE_CODE_PLAN_MODE_REQUIRED: definida como true se a aprovação do plano for necessária

Inicialização do contexto

Os integrantes carregam o mesmo contexto do projeto que qualquer nova sessão:

  • Arquivos CLAUDE.md do diretório de trabalho
  • Servidores MCP
  • Skills
  • O prompt de criação enviado pelo líder

O histórico da conversa do líder NÃO é transferido. Cada integrante começa do zero, tendo apenas o prompt de criação como contexto.

Implementação interna

A análise do binário do Claude Code v2.1.47 mostra que o contexto dos integrantes é gerenciado via AsyncLocalStorage com estes campos:

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

Principais funções internas:

  • isTeammate() / isTeamLead(): detecção de papéis
  • waitForTeammatesToBecomeIdle(): primitiva de sincronização para o líder
  • getTeammateContext() / setDynamicTeamContext(): gerenciamento do contexto em tempo de execução

Detecção de inatividade

Após cada turno do LLM, um integrante entra automaticamente em estado de inatividade e envia uma idle_notification ao líder. Esse é o estado normal de repouso, e não uma condição de erro ou de falta de atualização. Enviar uma mensagem para um integrante inativo o desperta (o próximo ciclo de consulta detecta a mensagem na caixa de entrada).

Protocolo de encerramento

  1. O líder envia shutdown_request a um integrante
  2. O integrante pode aprovar (encerra de forma controlada) ou rejeitar (continua trabalhando e apresenta uma explicação)
  3. A limpeza da equipe via TeamDelete remove ~/.claude/teams/{team-name}/ e ~/.claude/tasks/{team-name}/
  4. A limpeza falha se algum integrante ainda estiver ativo; eles precisam ser encerrados primeiro

Herança de permissões

Os integrantes herdam o modo de permissão do líder no momento da criação. Se o líder executa --dangerously-skip-permissions, todos os integrantes também executam. Os modos individuais podem ser alterados após a criação, mas não podem ser configurados por integrante no momento da criação.

5. Controles de qualidade e hooks

O Agent Teams se integra ao sistema de hooks do Claude Code para garantir a qualidade:

Hook TeammateIdle

É acionado quando um integrante está prestes a entrar em estado de inatividade. O código de saída 2 envia o conteúdo de stderr como feedback e impede a inatividade, mantendo o integrante trabalhando.

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

Hook TaskCompleted

É acionado quando uma tarefa está sendo marcada como concluída. O código de saída 2 impede a conclusão e retorna o conteúdo de stderr como feedback.

{
  "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"
}

Isso ocorre em duas situações: (1) quando qualquer agente marca explicitamente uma tarefa como concluída via TaskUpdate, ou (2) quando um integrante de uma equipe de agentes termina seu turno com tarefas em andamento.

Tipos de handlers de hooks

Tipo Descrição
command Script de shell. JSON em stdin, códigos de saída para decisões.
prompt Avaliação por LLM em um único turno. Retorna {ok, reason}.
agent Subagente com múltiplos turnos e ferramentas de leitura. Até 50 turnos.

6. Economia de tokens

Equipes de agentes usam aproximadamente 7× mais tokens do que sessões padrão quando os integrantes operam no modo de planejamento. Cada integrante mantém sua própria janela de contexto completa como uma instância separada do Claude.

Valor de referência

  • Uso médio do Claude Code: ~US$ 6/desenvolvedor/dia
  • Equipes de agentes: custo aproximadamente proporcional ao tamanho da equipe, além do valor de referência

7. Resumo da arquitetura

Dimensão Agent Teams do Claude Code
Base de coordenação Arquivos simples (~/.claude/tasks/, ~/.claude/teams/)
Formato das tarefas Um arquivo JSON por tarefa + .lock para assumir tarefas
Troca de mensagens Arquivos JSON de caixa de entrada (adição ao final + consulta periódica)
Ciclo de vida dos agentes Processos de CLI com autogerenciamento
Isolamento do trabalho Diretório de trabalho compartilhado
Estratégia de merge Nenhuma (os agentes editam os arquivos diretamente)
Novas tentativas/escalonamento Manual (o líder decide, ou o usuário intervém)
Topologia Líder + pares no mesmo nível, mensagens entre pares
Agendamento Autoatribuição (os integrantes assumem a próxima tarefa)
Persistência do estado Apenas arquivos; sem retomada de integrantes dentro do processo
Controles de qualidade Hooks de shell (TeammateIdle, TaskCompleted)
Contabilização de tokens Apenas por sessão, sem agregação entre agentes
Detecção de paralisação Manual (o usuário percebe que um integrante parou)
Controle de concorrência Implícito (tamanho da equipe = número de integrantes)
Modelo de dependências blocks/blockedBy nos arquivos de tarefas

Fontes

Documentação oficial

Artefatos em disco (Claude Code v2.1.47)

Observados em /Users/tau/.claude/:

  • Diretórios de equipes com arquivos config.json e inboxes/{agent-name}.json
  • Diretórios de tarefas com .lock, .highwatermark e arquivos JSON individuais de tarefas
  • Exemplo de mensagem de atribuição de tarefa de team-lead para cp-agent, com registro de data e hora 2026-02-18T02:37:16.890Z

Análise do binário

Binário do Claude Code v2.1.47. Funções internas identificadas por meio da análise de strings: getTeamName, getAgentName, getAgentId, isTeammate, isTeamLead, waitForTeammatesToBecomeIdle, getTeammateContext, setDynamicTeamContext, createTeammateContext. Campos de contexto do AsyncLocalStorage: agentId, agentName, teamName, parentSessionId, color, planModeRequired.

Base de código do Hive