s09
Team di Agenti
CollaborazioneTeammates + Mailboxes
348 LOC10 strumentiTeammateManager + 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 il task è troppo grande per uno, delega ai compagni di team" -- compagni persistenti + mailbox asincrone.
Problema
I sotto-agenti (s04) sono usa e getta: spawn, lavora, ritorna riassunto, muore. Nessuna identità, nessuna memoria tra invocazioni. I task in background (s08) eseguono comandi shell ma non possono prendere decisioni guidate dal LLM.
Il vero lavoro di squadra necessita di: (1) agenti persistenti che vivano più di un singolo prompt, (2) gestione identità e ciclo di vita, (3) un canale di comunicazione tra agenti.
Soluzione
Ciclo di vita del compagno:
spawn -> WORKING -> IDLE -> WORKING -> ... -> SHUTDOWN
Comunicazione:
.team/
config.json <- roster team + stati
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 ---------+
Come Funziona
- TeammateManager mantiene config.json con il roster del team.
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 compagno e avvia il suo loop agente in 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: inbox JSONL append-only.
send()aggiunge una riga JSON;read_inbox()legge tutto e scarica.
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)
- Ogni compagno controlla la sua inbox prima di ogni chiamata LLM, iniettando i messaggi ricevuti nel contesto.
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"
Cosa è Cambiato da s08
| Componente | Prima (s08) | Dopo (s09) |
|---|---|---|
| Strumenti | 6 | 9 (+spawn/send/read_inbox) |
| Agenti | Singolo | Lead + N compagni |
| Persistenza | Nessuna | config.json + inbox JSONL |
| Thread | Comandi background | Loop agente completi per thread |
| Ciclo di vita | Fire-and-forget | idle -> working -> idle |
| Comunicazione | Nessuna | message + broadcast |
Provalo
python agents/s09_agent_teams.py
Spawn alice (coder) e bob (tester). Fai mandare un messaggio da alice a bob.Broadcast "status update: phase 1 complete" a tutti i compagniControlla la inbox del lead per messaggi- Digita
/teamper vedere il roster del team con gli stati - Digita
/inboxper controllare manualmente la inbox del lead
