s09
Times de Agentes
ColaboracaoTeammates + Mailboxes
348 LOC10 ferramentasTeammateManager + 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
"Quando a tarefa é grande demais para um, delegue para companheiros" -- companheiros persistentes + caixas de correio assíncronas.
Problema
Subagentes (s04) são descartáveis: spawn, trabalha, retorna resumo, morre. Sem identidade, sem memória entre invocações. Tarefas em segundo plano (s08) executam comandos shell mas não conseguem tomar decisões guiadas por LLM.
Trabalho em equipe real precisa de: (1) agentes persistentes que sobrevivem a um único prompt, (2) identidade e gerenciamento de ciclo de vida, (3) um canal de comunicação entre agentes.
Solução
Ciclo de vida do companheiro:
spawn -> WORKING -> IDLE -> WORKING -> ... -> SHUTDOWN
Comunicação:
.team/
config.json <- roster da equipe + statuses
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 ---------+
Como Funciona
- TeammateManager mantém config.json com o roster da equipe.
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()cria um companheiro e inicia seu loop de agente em uma 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: caixas de entrada JSONL append-only.
send()appenda uma linha JSON;read_inbox()lê tudo e 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 companheiro verifica sua caixa de entrada antes de cada chamada LLM, injetando mensagens recebidas no 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"
O Que Mudou Desde s08
| Componente | Antes (s08) | Depois (s09) |
|---|---|---|
| Ferramentas | 6 | 9 (+spawn/send/read_inbox) |
| Agentes | Único | Lead + N companheiros |
| Persistência | Nenhum | config.json + JSONL inboxes |
| Threads | Comandos em background | Loops de agente completos por thread |
| Ciclo de vida | Fire-and-forget | idle -> working -> idle |
| Comunicação | Nenhum | message + broadcast |
Experimente
python agents/s09_agent_teams.py
Spawn alice (coder) e bob (tester). Faça alice enviar uma mensagem para bob.Broadcast "status update: phase 1 complete" para todos os companheirosVerifique a caixa de entrada do lead por quaisquer mensagens- Digite
/teampara ver o roster da equipe com statuses - Digite
/inboxpara verificar manualmente a caixa de entrada do lead
