s09
Équipes d'agents
CollaborationTeammates + Mailboxes
348 LOC10 outilsTeammateManager + 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
"Quand la tâche est trop grande pour un seul, déléguez aux co-équipiers" -- co-équipiers persistants + boites aux lettres asynchrones.
Problème
Les sous-agents (s04) sont jetables : générer, travailler, retourner le résumé, mourir. Pas d'identité, pas de mémoire entre les invocations. Les tâches en arrière-plan (s08) exécutent des commandes shell mais ne peuvent pas prendre de décisions guidées par LLM.
Le vrai travail d'équipe nécessite : (1) des agents persistants qui vivent au-delà d'un seul prompt, (2) gestion d'identité et de cycle de vie, (3) un canal de communication entre agents.
Solution
Cycle de vie du co-équipier:
spawn -> WORKING -> IDLE -> WORKING -> ... -> SHUTDOWN
Communication:
.team/
config.json <- roster d'équipe + statuts
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 ---------+
Comment Ça Marche
- TeammateManager maintient config.json avec le roster d'équipe.
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()crée un co-équipier et démarre sa boucle d'agent dans 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 : boites aux lettres JSONL append-only.
send()ajoute une ligne JSON ;read_inbox()lit tout et draine.
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)
- Chaque co-équipier vérifie sa boite appel LLM, aux lettres avant chaque injectant les messages reçus dans le contexte.
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'est-ce qui a Changé par Rapport à s08
| Composant | Avant (s08) | Après (s09) |
|---|---|---|
| Outils | 6 | 9 (+spawn/send/read_inbox) |
| Agents | Seul | Lead + N co-équipiers |
| Persistance | None | config.json + JSONL inboxes |
| Threads | Commandes arrière-plan | Boucles d'agent complètes par thread |
| Cycle de vie | Fire-and-forget | idle -> working -> idle |
| Communication | None | message + broadcast |
Essayer
python agents/s09_agent_teams.py
Générer alice (codeur) et bob (testeur). Faire envoyer un message d'alice à bob.Diffuser "status update: phase 1 complete" à tous les co-équipiersVérifier la boite aux lettres du lead pour des messages- Tapez
/teampour voir le roster d'équipe avec les statuts - Tapez
/inboxpour vérifier manuellement la boite aux lettres du lead
