Learn Claude Code
s09

Agent Teams

Collaboration

Teammates + Mailboxes

345 LOC10 toolsTeammateManager + file-based mailbox
Overview

Claude Code 可以同时跑几个独立的 agent,每个有自己的人设、自己的对话历史。它们之间通过一个共享邮箱互相发消息,能互相看见、互相回应。模拟评审小组、跨学科咨询这类需要多视角讨论的场景就靠这套机制。

Running example
Research project
耐心资本对企业 ESG 表现的实证项目 phase 2 收尾后
What happens in this section

组一支 4 人审稿队读 07_论文写作/01_主表/PC_ESG_主表汇总.docx 与 docs/01_实证研究设计.md,针对"A2 主口径 OLS β=0.0004 不显著 + IV1 0.0052** / IV3 0.0021** / PSM 0.0032*** / Placebo p_perm=0.000 全部显著"这种 OLS 与 IV 系数相差 10 倍的事实做四类视角的评审:方法学审稿人盯识别策略、计量审稿人盯标准误聚类、写作审稿人盯叙事一致性、新意审稿人盯相对唐亮 2025 与李思飞 2025 的边际贡献。

s01 > s02 > s03 > s04 > s05 > s06 | s07 > s08 > [ s09 ] s10 > s11 > s12

"When the task is too big for one, delegate to teammates" -- persistent teammates + async mailboxes.

Harness layer: Team mailboxes -- multiple models, coordinated through files.

Problem

Subagents (s04) are disposable: spawn, work, return summary, die. No identity, no memory between invocations. Background tasks (s08) run shell commands but can't make LLM-guided decisions.

Real teamwork needs: (1) persistent agents that outlive a single prompt, (2) identity and lifecycle management, (3) a communication channel between agents.

Solution

Teammate lifecycle:
  spawn -> WORKING -> IDLE -> WORKING -> ... -> SHUTDOWN

Communication:
  .team/
    config.json           <- team roster + 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 ---------+

How It Works

  1. TeammateManager maintains config.json with the team roster.
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 = {}
  1. spawn() creates a teammate and starts its agent loop in a 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})"
  1. MessageBus: append-only JSONL inboxes. send() appends a JSON line; read_inbox() reads all and drains.
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)
  1. Each teammate checks its inbox before every LLM call, injecting received messages into context.
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>"})
        response = client.messages.create(...)
        if response.stop_reason != "tool_use":
            break
        # execute tools, append results...
    self._find_member(name)["status"] = "idle"

What Changed From s08

ComponentBefore (s08)After (s09)
Tools69 (+spawn/send/read_inbox)
AgentsSingleLead + N teammates
PersistenceNoneconfig.json + JSONL inboxes
ThreadsBackground cmdsFull agent loops per thread
LifecycleFire-and-forgetidle -> working -> idle
CommunicationNonemessage + broadcast

Try It

cd claude-code-for-researchers
python agents/s09_agent_teams.py
  1. Spawn method_reviewer and stat_reviewer. method_reviewer sends stat_reviewer a note about IV3 (PC lag).
  2. Broadcast "main.tex v2 ready for mock review — IV/PSM done, Myopia mechanism added" to all reviewers
  3. Check the author inbox for review summaries
  4. Type /team to see the team roster with statuses
  5. Type /inbox to manually check the lead's inbox