How to Build an AI Agent
How to Build an AI Agent
s09

Equipos de Agentes

Colaboración

Teammates + Mailboxes

348 LOC10 herramientasTeammateManager + file-based mailbox
When one agent can't finish, delegate to persistent teammates via async mailboxes

s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > [ s09 ] s10 > s11 > s12

"Cuando la tarea es muy grande para uno, delega a compañeros de equipo" -- compañeros persistentes + buzones asíncronos.

Problema

Los subagentes (s04) son descartables: generar, trabajar, retornar resumen, morir. Sin identidad, sin memoria entre invocaciones. Las tareas en segundo plano (s08) ejecutan comandos shell pero no pueden tomar decisiones guiadas por LLM.

El verdadero trabajo en equipo necesita: (1) agentes persistentes que vivan más de un solo prompt, (2) gestión de identidad y ciclo de vida, (3) un canal de comunicación entre agentes.

Solución

Ciclo de vida del compañero:
  spawn -> WORKING -> IDLE -> WORKING -> ... -> SHUTDOWN

Comunicación:
  .team/
    config.json           <- roster de equipo + estados
    inbox/
      alice.jsonl         <- append-only, drain-on-read
      bob.jsonl
      lead.jsonl

              +--------+    send("alice","bob","...")    +--------+
              | alice  | -----------------------------> |  bob   |
              | loop   |    bob.jsonl << {json_line}    |  loop  |
              +--------+                                +--------+
                   ^                                         |
                   |        BUS.read_inbox("alice")          |
                   +---- alice.jsonl -> read + drain ---------+

Cómo Funciona

  1. TeammateManager mantiene config.json con el roster del equipo.
class TeammateManager:
    def __init__(self, team_dir: Path):
        self.dir = team_dir
        self.dir.mkdir(exist_ok=True)
        self.config_path = self.dir / "config.json"
        self.config = self._load_config()
        self.threads = {}
  1. spawn() crea un compañero y inicia su boucle de agente en un thread.
def spawn(self, name: str, role: str, prompt: str) -> str:
    member = {"name": name, "role": role, "status": "working"}
    self.config["members"].append(member)
    self._save_config()
    thread = threading.Thread(
        target=self._teammate_loop,
        args=(name, role, prompt), daemon=True)
    thread.start()
    return f"Spawned teammate '{name}' (role: {role})"
  1. MessageBus: buzones JSONL append-only. send() añade una línea JSON; read_inbox() lee todo y drena.
class MessageBus:
    def send(self, sender, to, content, msg_type="message", extra=None):
        msg = {"type": msg_type, "from": sender,
               "content": content, "timestamp": time.time()}
        if extra:
            msg.update(extra)
        with open(self.dir / f"{to}.jsonl", "a") as f:
            f.write(json.dumps(msg) + "\n")

    def read_inbox(self, name):
        path = self.dir / f"{name}.jsonl"
        if not path.exists(): return "[]"
        msgs = [json.loads(l) for l in path.read_text().strip().splitlines() if l]
        path.write_text("")  # drain
        return json.dumps(msgs, indent=2)
  1. Cada compañero verifica su bandeja de entrada antes de cada llamada LLM, inyectando mensajes recibidos en el contexto.
def _teammate_loop(self, name, role, prompt):
    messages = [{"role": "user", "content": prompt}]
    for _ in range(50):
        inbox = BUS.read_inbox(name)
        if inbox != "[]":
            messages.append({"role": "user",
                "content": f"<inbox>{inbox}</inbox>"})
            messages.append({"role": "assistant",
                "content": "Noted inbox messages."})
        response = client.messages.create(...)
        if response.stop_reason != "tool_use":
            break
        # execute tools, append results...
    self._find_member(name)["status"] = "idle"

Qué Cambió Respecto a s08

ComponenteAntes (s08)Después (s09)
Herramientas69 (+spawn/send/read_inbox)
AgentesSoloLead + N compañeros
PersistenciaNoneconfig.json + JSONL inboxes
ThreadsComandos en segundo planoBoucles de agente completas por thread
Ciclo de vidaFire-and-forgetidle -> working -> idle
ComunicaciónNonemessage + broadcast

Pruébalo

python agents/s09_agent_teams.py
  1. Generar alice (codificador) y bob (probador). Hacer que alice envíe un mensaje a bob.
  2. Transmitir "status update: phase 1 complete" a todos los compañeros
  3. Verificar la bandeja de entrada del lead para mensajes
  4. Escribir /team para ver el roster del equipo con estados
  5. Escribir /inbox para verificar manualmente la bandeja de entrada del lead