s09
Equipos de Agentes
ColaboraciónTeammates + 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
- 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 = {}
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})"
- 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)
- 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
| Componente | Antes (s08) | Después (s09) |
|---|---|---|
| Herramientas | 6 | 9 (+spawn/send/read_inbox) |
| Agentes | Solo | Lead + N compañeros |
| Persistencia | None | config.json + JSONL inboxes |
| Threads | Comandos en segundo plano | Boucles de agente completas por thread |
| Ciclo de vida | Fire-and-forget | idle -> working -> idle |
| Comunicación | None | message + broadcast |
Pruébalo
python agents/s09_agent_teams.py
Generar alice (codificador) y bob (probador). Hacer que alice envíe un mensaje a bob.Transmitir "status update: phase 1 complete" a todos los compañerosVerificar la bandeja de entrada del lead para mensajes- Escribir
/teampara ver el roster del equipo con estados - Escribir
/inboxpara verificar manualmente la bandeja de entrada del lead
