s09
Команды агентов
КоллаборацияTeammates + Mailboxes
348 LOC10 инструментовTeammateManager + 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
"Когда задача слишком большая для одного, делегируйте товарищам по команде" -- постоянные товарищи + асинхронные почтовые ящики.
Проблема
Под-агенты (s04) одноразовые: сгенерировать, поработать, вернуть резюме, умереть. Нет идентичности, нет памяти между вызовами. Фоновые задачи (s08) выполняют shell-команды, но не могут принимать решения на основе LLM.
Настоящая командная работа требует: (1) постоянные агенты, которые живут дольше одного промпта, (2) управление идентичностью и жизненным циклом, (3) канал связи между агентами.
Решение
Жизненный цикл товарища:
spawn -> WORKING -> IDLE -> WORKING -> ... -> SHUTDOWN
Коммуникация:
.team/
config.json <- ростер команды + статусы
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 ---------+
Как Это Работает
- TeammateManager поддерживает config.json с ростером команды.
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()создаёт товарища и запускает его цикл агента в потоке.
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: почтовые ящики JSONL append-only.
send()добавляет строку JSON;read_inbox()читает всё и освобождает.
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)
- Каждый товарищ проверяет свой почтовый ящик перед каждым вызовом LLM, внедряя полученные сообщения в контекст.
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"
Что Изменилось с s08
| Компонент | До (s08) | После (s09) |
|---|---|---|
| Инструменты | 6 | 9 (+spawn/send/read_inbox) |
| Агенты | Один | Lead + N товарищей |
| Персистентность | None | config.json + JSONL inboxes |
| Потоки | Команды в фоне | Полные циклы агента в потоках |
| Жизненный цикл | Fire-and-forget | idle -> working -> idle |
| Коммуникация | None | message + broadcast |
Попробуйте
python agents/s09_agent_teams.py
Создать alice (кодер) и bob (тестировщик). Заставить alice отправить сообщение bob.Отправить "status update: phase 1 complete" всем товарищамПроверить почтовый ящик lead на сообщения- Введите
/team, чтобы увидеть ростер команды со статусами - Введите
/inbox, чтобы вручную проверить почтовый ящик lead
