Ingeniería inversa de Agent Teams de Claude Code: arquitectura y protocolo

Traducción automática Artículo original.

Banner for Ingeniería inversa de Agent Teams de Claude Code: arquitectura y protocolo

contenido

Introducción

Claude Code (v2.1.47) incluye una función experimental llamada Agent Teams: varias sesiones de Claude Code se coordinan para realizar un trabajo compartido mediante una topología con un líder y miembros del equipo. He estado desarrollando Hive, un orquestador de agentes de programación con objetivos similares pero una arquitectura muy diferente, así que quería entender cómo funciona por dentro el enfoque de Anthropic.

Este artículo documenta lo que descubrí mediante:

  1. La lectura de la documentación oficial
  2. El examen de artefactos reales que dejaron en disco sesiones anteriores del equipo
  3. El análisis del binario de Claude Code (v2.1.47) que dejé en manos de Claude para conocer los detalles de implementación (¡ja!)

Índice

1. Visión general de la arquitectura

Un equipo de agentes consta de cuatro componentes:

Componente Función
Líder del equipo La sesión principal de Claude Code que crea el equipo e inicia a sus miembros
Miembros del equipo Instancias independientes de Claude Code, cada una con su propia ventana de contexto
Lista de tareas Elementos de trabajo compartidos almacenados como archivos JSON individuales en disco
Buzón Archivos de bandeja de entrada por agente para la entrega de mensajes

Toda la capa de coordinación está basada en archivos. El sistema de archivos en ~/.claude/ es la única base de coordinación:

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

Se trata de un diseño fundamentalmente descentralizado. El líder es simplemente otra sesión de Claude con herramientas adicionales (TeamCreate, TeamDelete, SendMessage). No hay ningún proceso en segundo plano. La coordinación surge del acceso a archivos compartidos.

En una sesión activa, si le pides a Claude que cree un equipo para realizar algún tipo de tarea y luego ejecutas lo siguiente en otra ventana, puedes observar cómo se actualiza el sistema de archivos en tiempo real.

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

Por ejemplo, con el siguiente 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

Observé esto:

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

Configuración del equipo

La configuración del equipo en ~/.claude/teams/{team-name}/config.json contiene un array members que los miembros leen para descubrir a los demás:

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

Los nombres son el principal mecanismo de direccionamiento (existen UUID, pero no se usan para el enrutamiento). Todos los mensajes y las asignaciones de tareas utilizan el campo name.

2. La lista de tareas compartida

Formato de los archivos

Cada tarea se almacena como un archivo JSON individual en ~/.claude/tasks/{team-name}/. Este es un ejemplo real de una sesión anterior:

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

Esquema de las tareas:

Campo Tipo Descripción
id string ID numérico, con incremento automático mediante .highwatermark
subject string Título en imperativo (p. ej., «Ejecuta las pruebas»)
description string Requisitos detallados y criterios de aceptación
activeForm string Forma progresiva para el indicador de actividad («Ejecutando las pruebas»)
status string pending → in_progress → completed (o deleted)
blocks string[] ID de las tareas que esta tarea bloquea
blockedBy string[] ID de las tareas que deben completarse antes de que esta tarea pueda empezar

Control de concurrencia

Dos archivos especiales permiten la coordinación:

  • .lock: Un archivo de 0 bytes utilizado para la exclusión mutua a nivel del sistema de archivos (flock()). Presente en los 42 directorios de tareas observados en mi ordenador.
  • .highwatermark: Contiene un único número entero (p. ej., "3", "13"). Es el siguiente ID de tarea disponible para el incremento automático.

Reserva de tareas

La reserva de tareas utiliza bloqueos de archivos para evitar condiciones de carrera. Los miembros prefieren empezar por las tareas con el ID más bajo. Una tarea con un array blockedBy no vacío no puede reservarse hasta que todas las tareas que la bloquean estén en un estado final.

Observación: la mayoría de los directorios de tareas están vacíos

De los 42 directorios de tareas de mi ordenador, solo 5 contenían archivos JSON de tareas. Los otros 37 solo tenían .lock y .highwatermark. Esto probablemente significa que las tareas se eliminan tras completarse, o que se trataba de sesiones en las que Claude utilizó la lista de tareas interna (disponible desde el lanzamiento de la función de lista de tareas) sin descomponer el trabajo en archivos de subtareas.

3. Comunicación entre agentes

Patrón de buzón

Cada agente tiene un archivo que contiene un array JSON en ~/.claude/teams/{team-name}/inboxes/{agent-name}.json. Esta es una bandeja de entrada real de una sesión anterior en la que el líder del equipo asignó trabajo a un 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
  }
]

Fíjate en la codificación JSON dentro de JSON: el campo text es una cadena JSON que contiene un objeto de mensaje serializado. El sobre externo tiene los campos from, text, timestamp y read.

Tipos de mensajes

El campo type dentro de la carga útil text admite:

Tipo Dirección Propósito
task_assignment líder → miembro Asignar una tarea con todos los detalles
message cualquiera → cualquiera Mensaje directo a un destinatario
broadcast líder → todos El mismo mensaje para todos los miembros
shutdown_request líder → miembro Solicitar un cierre ordenado
shutdown_response miembro → líder Aprobar o rechazar el cierre
plan_approval_request miembro → líder Enviar un plan para su revisión
plan_approval_response líder → miembro Aprobar o rechazar con comentarios
idle_notification miembro → líder Se envía automáticamente cuando termina el turno del miembro

Mecanismo de entrega

Ruta de escritura: El remitente añade una entrada al final del array JSON del archivo de bandeja de entrada del destinatario.

Ruta de lectura: El destinatario consulta periódicamente su propio archivo de bandeja de entrada. Los mensajes nuevos se inyectan como turnos de conversación sintéticos (aparecen como si los hubiera enviado un usuario).

Difusión: Literalmente escribe el mismo mensaje en el archivo de bandeja de entrada de cada miembro. El coste en tokens aumenta linealmente con el tamaño del equipo.

La comunicación consiste simplemente en añadir datos a un archivo y leerlo. La latencia entre el envío y la recepción depende del intervalo de sondeo del destinatario.

Visibilidad de los mensajes directos entre miembros

Cuando un miembro envía un mensaje directo a otro, se incluye un breve resumen en la notificación de inactividad que recibe el líder. Esto permite al líder conocer la colaboración entre miembros sin recibir el contenido completo del mensaje.

4. Creación y ciclo de vida de los agentes

Cómo se crean los miembros del equipo

Cada miembro es un proceso independiente de la CLI claude. El líder los inicia mediante la herramienta Task con los parámetros team_name y name. Se establecen variables de entorno en el proceso creado:

  • CLAUDE_CODE_TEAM_NAME: se establece automáticamente en los miembros creados
  • CLAUDE_CODE_PLAN_MODE_REQUIRED: se establece en true si se requiere la aprobación del plan

Inicialización del contexto

Los miembros cargan el mismo contexto del proyecto que cualquier sesión nueva:

  • Archivos CLAUDE.md del directorio de trabajo
  • Servidores MCP
  • Skills
  • El prompt de creación del líder

El historial de conversación del líder NO se transfiere. Cada miembro empieza de cero, con el prompt de creación como único contexto.

Implementación interna

Según el análisis del binario de Claude Code v2.1.47, el contexto de los miembros se gestiona mediante AsyncLocalStorage con estos campos:

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

Funciones internas clave:

  • isTeammate() / isTeamLead(): detección de roles
  • waitForTeammatesToBecomeIdle(): primitiva de sincronización para el líder
  • getTeammateContext() / setDynamicTeamContext(): gestión del contexto en tiempo de ejecución

Detección de inactividad

Después de cada turno del LLM, un miembro pasa automáticamente a estar inactivo y envía un idle_notification al líder. Este es el estado normal de reposo, no una condición de error ni una señal de que se haya quedado sin actualizar. Enviar un mensaje a un miembro inactivo lo reactiva (el siguiente ciclo de sondeo recoge el mensaje de la bandeja de entrada).

Protocolo de cierre

  1. El líder envía shutdown_request a un miembro
  2. El miembro puede aprobarlo (termina de forma ordenada) o rechazarlo (continúa trabajando y ofrece una explicación)
  3. La limpieza del equipo mediante TeamDelete elimina ~/.claude/teams/{team-name}/ y ~/.claude/tasks/{team-name}/
  4. La limpieza falla si todavía hay miembros activos; primero hay que cerrarlos

Herencia de permisos

Los miembros heredan el modo de permisos del líder al crearse. Si el líder se ejecuta con --dangerously-skip-permissions, todos los miembros también. Los modos individuales pueden cambiarse después de la creación, pero no pueden configurarse por miembro en el momento de crearlos.

5. Controles de calidad y hooks

Agent Teams se integra con el sistema de hooks de Claude Code para exigir el cumplimiento de los criterios de calidad:

Hook TeammateIdle

Se activa cuando un miembro está a punto de pasar a estar inactivo. El código de salida 2 envía el contenido de stderr como comentarios e impide que pase a estar inactivo, por lo que sigue trabajando.

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

Hook TaskCompleted

Se activa cuando una tarea se está marcando como completada. El código de salida 2 impide que se complete y devuelve el contenido de stderr como comentarios.

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

Esto ocurre en dos situaciones: (1) cuando cualquier agente marca explícitamente una tarea como completada mediante TaskUpdate, o (2) cuando un miembro de un equipo de agentes termina su turno con tareas en curso.

Tipos de manejadores de hooks

Tipo Descripción
command Script de shell. JSON en stdin y códigos de salida para las decisiones.
prompt Evaluación con un LLM en un solo turno. Devuelve {ok, reason}.
agent Subagente de varios turnos con herramientas de lectura. Hasta 50 turnos.

6. Economía de tokens

Los equipos de agentes utilizan aproximadamente 7 veces más tokens que las sesiones estándar cuando los miembros se ejecutan en modo de planificación. Cada miembro mantiene su propia ventana de contexto completa como una instancia independiente de Claude.

Referencia de base

  • Uso medio de Claude Code: ~6 $/desarrollador/día
  • Equipos de agentes: el consumo adicional sobre la base es aproximadamente proporcional al tamaño del equipo

7. Resumen de la arquitectura

Dimensión Agent Teams de Claude Code
Base de coordinación Archivos planos (~/.claude/tasks/, ~/.claude/teams/)
Formato de las tareas Un archivo JSON por tarea + .lock para reservarlas
Mensajería Archivos JSON de bandeja de entrada (añadir al final + sondear)
Ciclo de vida de los agentes Procesos de CLI que se gestionan por sí mismos
Aislamiento del trabajo Directorio de trabajo compartido
Estrategia de fusión Ninguna (los agentes editan los archivos directamente)
Reintentos/escalado Manual (el líder decide o interviene el usuario)
Topología Líder + miembros al mismo nivel, mensajería entre pares
Planificación Reserva autónoma (los miembros toman la siguiente tarea)
Persistencia del estado Solo archivos; sin reanudación de miembros dentro del proceso
Controles de calidad Hooks de shell (TeammateIdle, TaskCompleted)
Seguimiento de tokens Solo por sesión, sin agregación entre agentes
Detección de bloqueos Manual (el usuario se da cuenta de que un miembro se ha detenido)
Control de concurrencia Implícito (tamaño del equipo = número de miembros)
Modelo de dependencias blocks/blockedBy en los archivos de tareas

Fuentes

Documentación oficial

Artefactos en disco (Claude Code v2.1.47)

Observados en /Users/tau/.claude/:

  • Directorios de equipos con archivos config.json y inboxes/{agent-name}.json
  • Directorios de tareas con .lock, .highwatermark y archivos JSON individuales de tareas
  • Mensaje de ejemplo de asignación de tarea de team-lead a cp-agent, con la marca de tiempo 2026-02-18T02:37:16.890Z

Análisis del binario

Binario de Claude Code v2.1.47. Funciones internas identificadas mediante el análisis de cadenas: getTeamName, getAgentName, getAgentId, isTeammate, isTeamLead, waitForTeammatesToBecomeIdle, getTeammateContext, setDynamicTeamContext, createTeammateContext. Campos del contexto de AsyncLocalStorage: agentId, agentName, teamName, parentSessionId, color, planModeRequired.

Código fuente de Hive