cover

一、引言与背景

1.1 什么是多 Agent 协作?

在传统的 AI 编程助手中,一个 Agent(AI 助手)独立处理所有任务——读代码、写方案、改代码、跑测试,全部串行执行。当任务复杂度上升(如大型代码审查、多模块重构),单 Agent 模式面临两个核心瓶颈:

  • 上下文窗口有限:单个 Agent 的对话历史长度受限,无法同时持有整个大型项目的全部信息
  • 串行效率低下:安全审查、性能分析、测试编写等任务相互独立,串行处理浪费时间

多 Agent 协作的核心思路:将一个复杂任务分解为多个子任务,由多个专职 Agent 并行处理,通过协调机制整合结果

1.2 Agent Teams 在 Claude Code 中的定位

Agent Teams 是 Claude Code 中实现多 Agent 协作的核心系统,它让一个 “Leader”(主 Agent)可以创建并管理一组 “Teammates”(子 Agent),协同完成复杂任务。

核心价值

  • 并行化:多个 Teammate 同时工作,缩短总耗时
  • 专业化:每个 Teammate 可以有不同角色(安全审查员、测试工程师等)
  • 隔离性:每个 Teammate 至少拥有独立执行上下文;在 pane backend 模式下还能获得更强的终端/进程隔离。当前源码对 worktreePath 有 schema 与清理逻辑兼容,但未看到它作为 Teammate 标准派生参数公开暴露

1.3 Agent Teams 开启条件

CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 claude

二、示例场景与时间线

创建一个3人代码评审团队来 review 这个 PR: https://github.com/cloudwego/eino/pull/xxx

  • 一个专门找安全漏洞
  • 一个关注性能影响
  • 一个检查测试覆盖率

2.1 TeamLead 完整时间线

下面这条时间线更适合理解为一次可能的运行轨迹,而不是源码保证的固定顺序。实际运行中,Team 创建、任务规划分配都依赖于模型的主张规划能力。

用户: "创建一个3人代码评审团队来 review 这个 PR"
  │
  ▼
┌─ Step 1~3 ── 理解 PR────────────────────────────────────────────────────┐
│  Bash("gh pr view 852 --json ...")  → 获取 PR 元信息                     │
│  Bash("gh pr diff 852")             → 获取完整 diff (60.9KB)             │
│  Read(agentsmd.go)  ┐                                                    │
│  Read(loader.go)    ├── 并行读取 3 个源文件                              │
│  Read(test.go)      ┘   (test.go 52.3KB 自动截断到 persisted-output)     │
└──────────────────────────────────────────────────────────────────────────┘
  │
  ▼  "Now I have all the code. Let me create the review team."
┌─ Step 4 ─────────────────────────────────────────────────────────────────┐
│  TeamCreate("pr-852-review")                                             │
│    → 名称未冲突时直接使用: "pr-852-review"                               │
│    → lead_agent_id: "team-lead@pr-852-review"                            │
└──────────────────────────────────────────────────────────────────────────┘
  │
  ▼
┌─ Step 5 ─────────────────────────────────────────────────────────────────┐
│  TaskCreate("安全漏洞审查")  ──→ 创建任务                                 │
│  TaskCreate("性能影响分析")  ──→ 创建任务     ← 3 个任务可并行创建        │
│  TaskCreate("测试覆盖率检查") ──→ 创建任务                                │
│  注:任务 ID 由运行时分配,源码不保证固定编号                              │
└──────────────────────────────────────────────────────────────────────────┘
  │
  ▼
┌─ Step 6 ─────────────────────────────────────────────────────────────────┐
│  Agent(name="security-reviewer", team_name="pr-852-review",         ┐   │
│        prompt="审查安全风险...", model="sonnet")                        ├─ 并行 │
│  Agent(name="perf-reviewer", team_name="pr-852-review",             │   │
│        prompt="分析性能影响...", model="sonnet")                        │   │
│  Agent(name="test-reviewer", team_name="pr-852-review",             ┘   │
│        prompt="检查测试覆盖率...", model="sonnet")                          │
│  每个 Agent 附带完整任务 Prompt: 角色定义+文件清单+审查维度+报告模板      │
│                                                                          │
│  输出: "三个审查员正在同时工作,完成后我会汇总报告给你。"                │
└──────────────────────────────────────────────────────────────────────────┘
  │
  │  ════════════ Leader 进入被动等状态 ════════════
  │
  ▼
┌─ Step 7 ─────────────────────────────────────────────────────────────────┐
│  ◀── <teammate-message perf-reviewer>  性能审查报告(某个 teammate 先完成)│
│                                                                          │
│  输出: "安全和性能审查已完成,等待测试覆盖率审查员完成报告。"            │
└──────────────────────────────────────────────────────────────────────────┘
  │
  ▼
┌─ Step 8 ─────────────────────────────────────────────────────────────────┐
│  ◀── <teammate-message security-reviewer>  idle_notification(常见为 1 次;某些 Stop/生命周期路径可能额外出现) │
│                                                                          │
│  输出: "安全和性能审查员已完成工作进入空闲状态"                          │
└──────────────────────────────────────────────────────────────────────────┘
  │
  ▼
┌─ Step 9 ─────────────────────────────────────────────────────────────────┐
│  ◀── <teammate-message test-reviewer>  测试覆盖率报告(另一个 teammate 完成)│
└──────────────────────────────────────────────────────────────────────────┘
  │
  ▼  "三份报告全部到齐。先关闭团队,然后汇总。"
┌─ Step 10 ────────────────────────────────────────────────────────────────┐
│  SendMessage(shutdown_request → security-reviewer)  ┐                    │
│  SendMessage(shutdown_request → perf-reviewer)      ├── 并行关闭         │
│  SendMessage(shutdown_request → test-reviewer)      ┘                    │
│                                                                          │
│  输出: 📋 综合评审报告                                                   │
│    ├── 🔴 高风险 × 4 (提示注入/路径穿越/缓存key/缓存未测试)             │
│    ├── 🟡 中风险 × 4 (maxBytes/I-O/Builder/nil防御)                     │
│    └── 🟢 亮点   × 3 (并发安全/ReDoS安全/95.3%覆盖率)                   │
└──────────────────────────────────────────────────────────────────────────┘
  │
  ▼
┌─ Step 11 ────────────────────────────────────────────────────────────────┐
│  ◀── shutdown_approved ✓  (security-reviewer / perf-reviewer /           │
│                           test-reviewer 返回关闭决策)                    │
│  Leader 处理批准结果后,UI/AppState 侧还可能生成 `teammate_terminated`    │
│  之类的系统通知用于展示,但这不是主关闭协议本身                           │
└──────────────────────────────────────────────────────────────────────────┘
  │
  ▼
┌─ Step 12 ────────────────────────────────────────────────────────────────┐
│  TeamDelete()(前提:team file 中已无 `isActive !== false` 的活跃成员)   │
│    → 成功后返回类似 "Cleaned up directories and worktrees for team ..." │
│      但 worktree 清理仅在成员实际带有 `worktreePath` 时才会发生            │
└──────────────────────────────────────────────────────────────────────────┘

2.2 Teammate 完整时间线

三个 Teammate 同时启动、并行工作、各自独立。以下只列一个 Teammate 的一次可能运行轨迹;另外两个的具体任务与顺序也会随运行时调度变化。

收到任务 Prompt: "你是一个专业的安全审计员..."
  │
  ▼
┌─ Step 1  读取与探索源码 ───────────────────────────────┐
│  Read(agentsmd.go, loader.go, agentsmd_test.go)         │
│  ↳ test 文件 52.3KB 截断 → 多次重试(Read/Bash/head)    │
│  Glob("adk/filesystem/*.go", "adk/internal/*.go")       │
│  Read(filesystem/backend.go, internal/config.go)        │
└─────────────────────────────────────────────────────────┘
  │
  ▼  分析完毕,整理报告
┌─ Step 2 ───────────────────────────────────────────────┐
│  TaskUpdate(当前已认领任务, in_progress → completed)     │
└─────────────────────────────────────────────────────────┘
  │
  ▼
┌─ Step 3 ───────────────────────────────────────────────┐
│  📤 SendMessage(report → team-lead)                     │
│    → 安全审查报告: 2 高危 + 2 中危 + 安全亮点          │
└─────────────────────────────────────────────────────────┘
  │
  ▼  idle_notification(通常 1 次;部分生命周期路径可能额外出现)
     → 收到 shutdown_request → shutdown_approved ✓

2.3 功能分析

  • 模型驱动(WithTool)
    • 团队创建
    • 任务规划
    • 生成 Teammate
    • 指派/认领任务
    • Agent 之间通信
    • 汇总与关闭
  • 程序驱动
    • 显式 TaskUpdate(owner=...),自动投递消息到 owner 邮箱
    • task_reminder 任务提醒
    • Teammate 空闲后,程序自动投递 idle_notification 消息到 TeamLead 邮箱
    • shutdown_approved,自动退出 Teammate run loop

三、架构全景

Agent Teams 系统全景架构

3.1 文件系统

Agent Teams 的本地协作主链路(team / task / inbox 等关键协作状态)不依赖中央数据库或额外队列基础设施,而是直接落到本地文件系统;

~/.claude/
├── teams/{team-name}/
│   ├── config.json              # 团队配置(成员列表、模型、颜色等)
│   └── inboxes/                 # 消息收件箱
│       ├── team-lead.json       # Leader 的收件箱
│       ├── {agent-name}.json    # 各 Teammate 的收件箱
│       └── *.json.lock          # 并发写入锁
├── tasks/{team-name}/
│   ├── *.json                   # 任务文件
│   └── .lock                    # 任务并发锁
└── projects/{project}/{session}/subagents/
    ├── agent-*.jsonl            # Agent 对话记录
    └── agent-*.meta.json        # Agent 元数据

设计优势

  • 本地协作链路零额外基础设施依赖:team / task / inbox 主链路不需要 Redis/MQ/DB,开箱即用
  • 天然持久化:进程崩溃后文件仍在,支持恢复
  • 可调试性强:直接查看 JSON 文件即可排查问题

3.2 三种运行后端

Agent Teams 运行时一共有三类执行后端:In-ProcesstmuxiTerm2 pane

后端 原理 通信方式 启动速度 隔离级别 适用场景
In-Process Teammate 在 Leader 同一进程内运行,用 AsyncLocalStorage 隔离上下文 初始 prompt 直传;后续消息以内存队列 + 文件信箱为主 毫秒级 逻辑隔离 teammateMode=in-process,或 auto 模式下无 pane backend 时;非交互会话也会强制走此模式
Tmux 通过 tmux 后端运行;可表现为 split pane,也可在 use_splitpane=false 时进入独立 tmux window 仅文件信箱 秒级 进程隔离 需要完全隔离
iTerm2 使用 iTerm2 原生 split pane 仅文件信箱 秒级 进程隔离 auto / tmux 进入 pane backend 后,在 macOS + iTerm2 环境下可能被检测为该后端

实际解析逻辑

teammateMode = in-process
  → 总是使用 In-Process

teammateMode = tmux
  → 优先走 pane backend(tmux / iTerm2 检测与启动逻辑)
  → 若 pane backend 检测失败,会直接报错,不会静默回退到 In-Process

teammateMode = auto(默认)
  → 在 tmux / iTerm2 环境中优先尝试 pane backend
  → 不在 pane 环境时直接使用 In-Process
  → 若处于 auto 且 pane backend 检测失败,则回退到 In-Process

non-interactive session(如 `-p`)
  → 无论配置为何,都会强制走 In-Process

四、Teammate 派生与执行机制

4.1 spawnMultiAgent —— Teammate 派生路由

spawnMultiAgent 是整个 Agent Teams 的 Teammate 工厂模块,被 AgentTool 调用;当 AgentTool 能解析出 teamName,且同时提供了 name 参数,并且当前调用者不是 teammate 时,不走普通 SubAgent 路径,而是调用 spawnTeammate() 进入多 Agent 派生流程;

AgentTool 的主分叉

AgentTool 接收到调用
    │
    ├─ 能解析出 teamName 且提供了 name,且当前不是 teammate
    │    → spawnTeammate()(spawnMultiAgent.ts)
    │    → 创建长期运行的 Teammate,fire-and-forget
    │    → 返回 { status: "teammate_spawned", agent_id, color, ... }
    │
    ├─ 当前已经是 teammate,且再次命中 team 上下文并提供了 name
    │    → 直接报错,禁止 teammate 再派生 teammate
    │
    └─ 其余情况
         → 继续走普通 AgentTool 分支
         → 可能进入同步 runAgent()、fork 路径,或 run_in_background 异步路径

每次派生 Teammate 的核心步骤(Pane / In-Process 两条路径顺序并不完全相同):

步骤 操作 关键函数/逻辑
名称去重 generateUniqueTeammateName() — 重复则追加 -2, -3
清洗名称并生成 Agent ID sanitizeAgentName(),再按 {name}@{teamName} 生成
选择执行路径 Pane 路径会先做 backend 检测;In-Process 路径则直接进入 spawnInProcessTeammate()
分配颜色 assignTeammateColor() — 8 色轮转
注册运行态 Pane 路径会先更新 AppState、注册后台任务,再写团队 config.json;In-Process 路径在 spawn 阶段就会先注册 task 到 AppState,启动后再补写 team context / config.json
投递初始 Prompt Pane 后端通过 mailbox 文件信箱投递;In-Process 仅初始 prompt 不走 mailbox,而是通过 startInProcessTeammate() 直接传入,避免重复消息;后续协作消息仍会走 mailbox / 内存队列

4.2 InProcessRunner —— Teammate 执行主循环

inProcessRunner 是 In-Process 模式下 Teammate 的执行引擎。SpawnMultiAgent 负责"创建 Teammate",而 InProcessRunner 负责 Teammate 创建之后的整个生命周期运行

主循环的完整流程

while (!aborted && !shouldExit) {
    │
    ├─ ① Token 检查 — 超过 autoCompactThreshold → compactConversation() 压缩历史
    │
    ├─ ② runAgent() — 调用与 AgentTool/SubAgent 相同的 API 推理循环
    │    ├── LLM 推理 → 工具调用 → 结果返回 → 循环
    │    ├── 每条消息实时更新 AppState(进度、transcript)
    │    └── 支持 Escape 中断当前 Turn(不杀死整个 Teammate)
    │
    ├─ ③ Turn 结束 → 标记 isIdle = true
    │
    ├─ ④ sendIdleNotification() → 写入 Leader 的文件信箱
    │    idleReason: available | interrupted | failed
    │
    └─ ⑤ waitForNextPromptOrShutdown() — 阻塞等待
         每 500ms 轮询,按优先级检查:
         ├─ pendingUserMessages[](内存队列,来自 UI 直接注入)
         ├─ shutdown_request(最高优先级,优先于普通消息)
         ├─ team-lead 消息(Leader 消息优先于 Peer 消息)
         ├─ 其他 Teammate 消息(FIFO 顺序)
         └─ tryClaimNextTask()(自动认领未分配的 pending 任务)
              │
              ├─ shutdown_request → 传给模型决策(批准/拒绝)
              ├─ new_message → 唤醒,回到循环顶部开始新 Turn
              └─ aborted → 退出循环
}

关键设计点

设计 说明
消息优先级 pendingUserMessages > shutdown > Leader 消息 > Peer 消息 > 任务认领,避免用户/Leader 指令被对等消息淹没
权限桥接 createInProcessCanUseTool() — 当 Teammate 需要执行危险操作时,优先通过 Leader 的 UI confirm queue 弹确认框(带 Teammate 颜色标识);若这条路径不可用,则回退到 mailbox 的 permission_request / permission_response 协议
自动任务认领(仅 In-Process) 不仅 Idle 等待期间会调用 tryClaimNextTask() 自动认领任务
独立 contentReplacementState 每个 Teammate 维护独立的内容替换状态,compact 后重置,保证缓存前缀一致性

五、任务协调机制

5.1 任务模型设计

任务系统支持通过 blocks / blockedBy 表达有向依赖关系;当前源码中可以建立任务依赖边,但没有看到显式的“无环校验”,因此更准确地说是“支持依赖关系建模”,而不是严格保证 DAG。每个任务包含:

Task {
  id: "1"
  subject: "OAuth 安全审查"          # 简要标题
  description: "审查所有 OAuth 相关..."  # 详细描述
  status: "pending | in_progress | completed"
  owner: "sec-reviewer"              # 当前负责人(实现里可能是 agentName,也可能是 agentId)
  blocks: ["2"]                      # 此任务完成后解锁任务 #2
  blockedBy: ["3"]                   # 此任务被任务 #3 阻塞
}

依赖关系示例

Task #1 (OAuth 审查) [pending]
    ↓ blocks
Task #2 (登录功能) [pending] ← blockedBy: ["1"]
    ↓ blocks
Task #3 (集成测试) [pending] ← blockedBy: ["2"]

Team 模式下的额外自动化

能力 说明
owner 写入时机 TaskCreate 默认 owner 为空;后续可能在三种情况下写入:显式 TaskUpdate(owner=...)、In-Process 自动认领 pending 任务,或 Teammate 将任务标记为 in_progress 且原任务还没有 owner 时由 TaskUpdateTool 自动补写当前 agentName
任务分配通知 显式 TaskUpdate(owner=...) 分配任务时,会尝试按 owner 这个键写入 task_assignment 通知;
完成验证 在任务被真正标记为 completed 之前执行 Hook;Hook 可阻止不合格的完成
完成后引导 提示 Teammate 调用 TaskList 查找下一个任务

5.2 任务退回机制

当前源码里,部分 Teammate 关闭/移除路径会调用任务退回逻辑;但不宜把它概括成“所有退出/故障场景都会统一自动退回”,也不宜把它理解成“所有已认领任务都会被稳定覆盖”:

命中已接入 `unassignTeammateTasks()` 的关闭/移除路径
    ↓
在当前这条 `teamName` 对应的任务列表里,查找该 Teammate 名下所有未完成任务
    ↓
将每个任务的 owner 清空,status 重置为 "pending"
    ↓
命中并成功退回的任务会回到"待领取"状态,可被其他 Teammate 认领

因此更准确地说:系统具备任务退回能力,但这是若干生命周期清理路径提供的能力,而不是对所有故障场景的绝对保证。并且当前实现里 taskListId 仍存在分叉(尤其 In-Process 自动认领显式使用 parentSessionId),所以也不能把“所有已认领任务都会被这条退回路径覆盖”视为强保证。


六、通信系统设计

6.1 异步消息投递

核心设计:异步消息投递(包含部分异步请求/响应协议)

SendMessage 整体上采用异步消息投递:发送方不会同步等待对方业务回复,但各路由仍会等待本次投递动作本身完成(如写 mailbox、发 UDS/bridge、恢复本地 agent)。同时,shutdown / permission / plan approval 等场景又在协议层体现为异步 request/response,只是它们并不是同步 RPC。这个设计避免了:

  • 死锁:A 等 B 回复,B 等 A 回复
  • 阻塞:发送慢消息拖慢整个系统

6.2 消息类型概览

系统内部会识别多类消息/协议;但要注意,SendMessageTool.message 本身只接受字符串,以及 shutdown_request / shutdown_response / plan_approval_response 三种结构化输入。下面的表格按“工具输入语义 / mailbox 内部协议”来汇总,避免把两者混为一谈:

类别 消息类型 说明
协作消息 message 普通字符串直发语义,不是固定的 JSON type
broadcast 普通字符串广播语义,不是固定的 JSON type
生命周期 shutdown_request Leader → Teammate 关闭请求
shutdown_response SendMessageTool 的结构化输入类型;工具内部会进一步落到 mailbox 协议消息
shutdown_approved / shutdown_rejected Teammate → Leader 的实际 mailbox 生命周期消息
idle_notification Teammate → Leader 空闲状态通知
任务相关 task_assignment 当前主要出现在 TaskUpdate(owner=...) 这类显式 owner 变更通知路径;不应概括为所有任务分配/认领都会自动发送
审批流程 plan_approval_request Teammate → Leader 计划审批请求
plan_approval_response Leader → Teammate 批准/拒绝计划
权限管理 permission_request Teammate → Leader 权限申请
permission_response Leader → Teammate 权限决策
sandbox_permission_request Teammate → Leader 的沙箱权限申请
sandbox_permission_response Leader → Teammate 的沙箱权限决策
mode_set_request 运行时模式变更
team_permission_update 团队级权限同步

6.3 Idle/Wakeup 机制

In-Process Teammate 可抽象为 "工作-空闲-唤醒"循环;而 pane / external teammate 在源码中更多通过 inbox poller + 生命周期 hook 实现近似语义:

Teammate 主循环:
while (未终止) {
    ① 处理消息 / 执行任务(一个 Turn)
    ② Turn 结束 → 进入 Idle 状态 → 发送 idle_notification 给 Leader
    ③ 等待新消息(阻塞在 `waitForNextPromptOrShutdown()`)
    ④ 新消息到达 → 唤醒 → 回到 ①
    ⑤ 收到 `shutdown_request` → 交给模型决定批准/拒绝;批准后才进入退出流程
}

消息送达时机取决于 Teammate 状态

Teammate 状态 消息送达方式 何时处理
Idle(等待中) 写入 inbox;随后在下一次 wait / poll 周期发现 在下一次检查周期开启新 Turn
执行中(In-Process) 普通 SendMessage 仍写入文件 inbox;少数来自 UI/转录视图的直接注入会进入 pendingUserMessages 内存队列 文件 inbox 在下一次 wait/poll 时消费;内存队列在下一次安全边界消费
执行中(External) 写入磁盘信箱 下一个 Turn 开始时注入

关键设计点:消息不会中断正在执行的 Turn,只在安全边界(Turn 间或工具调用间)被消费,保证了执行的原子性。

6.4 Leader 的消息感知

Leader 主要通过两种机制感知消息;但这个总结默认针对交互式/UI 场景

  1. Inbox Poller(交互式主路径):UI 层每 1 秒轮询一次 inbox;空闲时直接把消息提交成新 turn,忙时先暂存到 AppState.inbox
  2. Attachment 注入(补充路径 / 非交互补位路径)teammate_mailbox 会把 mailbox 中未读、且未被 isStructuredProtocolMessage() 识别为需交给 useInboxPoller 专门处理的消息作为附件注入上下文;这不是简单等同于“所有非协议消息”。在 Leader 视角下它还会合并 AppState.inbox 中待处理消息。需要注意:在 -p 等非交互模式下,useInboxPoller 不运行,因此 attachment 路径还会额外承担部分原本由 poller 处理的收尾逻辑(例如处理 shutdown_approved


七、其他机制

  • 动态 Attachment 系统 ,Agent Teams 在每个 Turn 的 API 调用前,会并行计算一组动态 Attachment(附件);具体数量取决于当前会话形态、工具集、feature flag 与用户输入等条件。
    • task_reminder,提醒使用任务工具(默认阈值为连续 10 个 assistant turns 未进行任务管理,且还受 Todo V2、TaskUpdateTool 可用性、Brief 场景、用户类型等条件约束)
  • 隔离机制
    • 支持不同的 Teammate 使用 Worktree 隔离文件系统

八、Eino 实现

Eino 实现架构

九、总结

Agent Teams 实现了一套较完整的多 Agent 协作系统,核心设计理念可以概括为:

  1. 扁平架构,简洁可控:Leader/Teammate 两层结构,避免层级递归的复杂性
  2. 文件系统即基础设施:零外部依赖,用 JSON 文件实现消息队列、配置中心和持久化
  3. 异步消息投递:不走同步阻塞 RPC;同时部分场景会通过异步 request/response 协议协作,状态变化主要靠轮询和附件机制感知
  4. 多层隔离:当前稳定可见的是 AsyncLocalStorage(上下文级)+ 进程隔离(Tmux/iTerm2);另外源码对 worktreePath 有兼容与清理逻辑,但不宜直接概括为 Teammate 的公开标准能力
  5. 自愈容错:提供任务退回(部分路径)、自动清理、后端回退等机制,提升系统韧性
  6. 成本优化:启用相关 feature 时的缓存分界、Fork 上下文继承、动态 Attachment 按需注入

在 Agent 协作这个方向上,Agent Teams 的"用最简单的技术解决最复杂的协调问题"的哲学,值得在我们自己的多 Agent 系统设计中借鉴。