Claude Code Agent-Teams深度解析:多智能体协作系统的技术实现原理

从团队创建、队友产卵、邮箱通信到权限同步的多智能体协作全链路剖析

Posted by 爱折腾的工程师 on Wednesday, April 8, 2026

一、引言:从单 Agent 到多 Agent 协作

前文 中,我们分析了 Claude Code 的 Sub-Agent 系统——父子关系、fork 缓存、内存快照等机制。但 Sub-Agent 本质上是层级式的:父 Agent 同步等待子 Agent 完成。当面对复杂的多步骤任务(如"前端、后端、测试并行开发")时,这种模式效率有限。

Agent Teams(内部代号 “Tengu”)是 Claude Code 的下一步进化——一个真正的多智能体协作系统。它的核心设计思想是:

一个 Team Lead 创建并协调多个 Teammates,每个 Teammate 是一个独立的 Agent 实例,通过基于文件的邮箱系统进行异步通信,共同完成复杂任务。

本文基于 2026 年 3 月 31 日的源码快照,从源码层面深入剖析 Agent Teams 的完整实现。

全文架构

Agent Teams 架构总览

章节 主题 核心文件
功能开关与团队创建 agentSwarmsEnabled.ts, TeamCreateTool.ts
队友产卵与三种后端 spawnMultiAgent.ts, backends/types.ts
邮箱通信协议 teammateMailbox.ts, SendMessageTool.ts
队友生命周期与主循环 inProcessRunner.ts, teammateInit.ts
权限同步模型 permissionSync.ts, leaderPermissionBridge.ts
状态持久化与上下文隔离 teamHelpers.ts, teammateContext.ts
任务列表协调模式 tasks.ts, TaskCreateTool.ts
计划审批模式 ExitPlanModeTool.ts
总结与设计哲学

二、功能开关与团队创建

2.1 功能开关:谁能使用 Agent Teams?

Agent Teams 功能由 isAgentSwarmsEnabled() 集中控制:

// utils/agentSwarmsEnabled.ts
export function isAgentSwarmsEnabled(): boolean {
  // Anthropic 内部用户:始终启用
  if (process.env.USER_TYPE === 'ant') {
    return true
  }
  // 外部用户需要同时满足两个条件:
  // 1. 环境变量 CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=true 或 --agent-teams CLI 标志
  if (!isEnvTruthy(process.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS) && !isAgentTeamsFlagSet()) {
    return false
  }
  // 2. GrowthBook 远程开关 tengu_amber_flint 启用(作为 killswitch)
  if (!getFeatureValue_CACHED_MAY_BE_STALE('tengu_amber_flint', true)) {
    return false
  }
  return true
}

这个双重门控设计确保了:

  • 内部用户(Anthropic 工程师)可以随时使用进行开发和测试
  • 外部用户需要显式 opt-in,同时 Anthropic 保留远程 killswitch 能力

2.2 团队创建流程

TeamCreateTool 是创建团队的入口,其 call() 方法执行以下步骤:

// tools/TeamCreateTool/TeamCreateTool.ts
async call(input, context) {
  // 1. 唯一性检查 — 每个 Leader 只能管理一个团队
  if (existingTeam) {
    throw new Error(`Already leading team "${existingTeam}".`)
  }

  // 2. 生成唯一团队名 — 冲突时使用 word slug
  const finalTeamName = generateUniqueTeamName(team_name)

  // 3. 创建确定性 Leader Agent ID
  const leadAgentId = formatAgentId(TEAM_LEAD_NAME, finalTeamName)
  // 结果: "team-lead@my-project"

  // 4. 写入 TeamFile (config.json)
  const teamFile: TeamFile = {
    name: finalTeamName,
    createdAt: Date.now(),
    leadAgentId,
    leadSessionId: getSessionId(),
    members: [{ agentId: leadAgentId, name: TEAM_LEAD_NAME, ... }],
  }
  await writeTeamFileAsync(finalTeamName, teamFile)

  // 5. 注册会话清理 — 确保退出时清理孤立目录
  registerTeamForSessionCleanup(finalTeamName)

  // 6. 创建对应的任务列表目录(Team = TaskList,1:1 对应)
  await resetTaskList(taskListId)
  await ensureTasksDir(taskListId)

  // 7. 更新 AppState
  setAppState(prev => ({
    ...prev,
    teamContext: { teamName, teamFilePath, leadAgentId, teammates: {...} },
  }))
}

关键设计决策:

  • 确定性 Agent ID:格式为 agentName@teamName(如 researcher@my-project),可重复、人类可读、可预测,无需查询即可计算出队友的 ID
  • Team = TaskList:每个团队自动创建一个同名的任务列表目录,实现 1:1 对应
  • 会话清理注册:解决了 GitHub issue #32730 中团队目录永远残留在磁盘上的问题

2.3 TeamFile 数据结构

// utils/swarm/teamHelpers.ts
export type TeamFile = {
  name: string
  description?: string
  createdAt: number
  leadAgentId: string
  leadSessionId?: string     // Leader 的 Session UUID
  hiddenPaneIds?: string[]   // 隐藏的窗格 ID
  teamAllowedPaths?: TeamAllowedPath[]  // 团队级路径权限
  members: Array<{
    agentId: string          // "researcher@my-team"
    name: string             // "researcher"
    agentType?: string
    model?: string
    color?: string
    planModeRequired?: boolean
    joinedAt: number
    tmuxPaneId: string
    cwd: string
    backendType?: BackendType   // 'tmux' | 'iterm2' | 'in-process'
    isActive?: boolean          // false=idle, true/undefined=active
    mode?: PermissionMode       // 当前权限模式
  }>
}

存储路径:~/.claude/teams/{team-name}/config.json


三、队友产卵与三种后端

3.1 三种执行后端

Claude Code 支持三种队友执行后端,每种适用于不同场景:

后端类型 进程模型 通信方式 适用场景
in-process 同一 Node.js 进程 AsyncLocalStorage 隔离 默认模式,资源共享
tmux 独立 CLI 进程 tmux 分屏 + 文件邮箱 终端用户
iterm2 独立 CLI 进程 iTerm2 原生分屏 + 文件邮箱 macOS iTerm2 用户
// utils/swarm/backends/types.ts
export type BackendType = 'tmux' | 'iterm2' | 'in-process'

3.2 产卵流程

spawnTeammate() 是产卵的主入口,根据后端类型分发到不同处理器:

// tools/shared/spawnMultiAgent.ts
async function handleSpawn(input, context) {
  // 优先检查 in-process 模式
  if (isInProcessEnabled()) {
    return handleSpawnInProcess(input, context)
  }
  // Pre-flight: 确保 pane 后端可用
  try {
    await detectAndGetBackend()
  } catch (error) {
    // auto 模式下自动回退到 in-process
    if (getTeammateModeFromSnapshot() !== 'auto') throw error
    markInProcessFallback()
    return handleSpawnInProcess(input, context)
  }
  // 使用分屏或独立窗口
  return input.use_splitpane !== false
    ? handleSpawnSplitPane(input, context)
    : handleSpawnSeparateWindow(input, context)
}

3.3 进程内产卵详解

进程内模式是最核心的后端,其产卵流程:

1. resolveTeammateModel()     → 解析模型('inherit' → 跟随 Leader)
2. generateUniqueTeammateName() → 生成唯一名称(冲突时加后缀 -2, -3)
3. sanitizeAgentName()         → 清理 @ 字符防止 ID 冲突
4. formatAgentId()             → 生成确定性 ID: "name@team"
5. assignTeammateColor()       → 分配唯一 UI 颜色
6. spawnInProcessTeammate()    → 创建 TeammateContext + AbortController
7. startInProcessTeammate()    → fire-and-forget 启动执行循环
8. 注册到 TeamFile + AppState

关键差异:进程内队友的初始 prompt 不通过邮箱传递(避免重复消息),而是直接传递给 startInProcessTeammate()

3.4 Tmux/iTerm2 分屏产卵

分屏模式的队友是完全独立的 Claude Code CLI 进程:

cd /path/to/cwd && env CLAUDECODE=... \
  claude --agent-id "researcher@my-team" \
         --agent-name "researcher" \
         --team-name "my-team" \
         --agent-color "blue" \
         --parent-session-id "xxx" \
         --model "claude-sonnet-4-20250514"

初始 prompt 通过邮箱发送,队友启动后通过 useInboxPoller 轮询收到第一条消息。

3.5 权限继承

队友继承 Leader 的权限配置:

function buildInheritedCliFlags(options) {
  // plan_mode_required 时不继承 bypassPermissions(安全优先)
  if (planModeRequired) {
    // 不继承 --dangerously-skip-permissions
  } else if (permissionMode === 'bypassPermissions') {
    flags.push('--dangerously-skip-permissions')
  } else if (permissionMode === 'auto') {
    flags.push('--permission-mode auto')
  }
  // 继承 --model, --settings, --plugin-dir, --chrome
}

四、邮箱通信协议

4.1 基于文件的邮箱系统

Agent Teams 的通信核心是一个基于文件的邮箱系统——每个队友有一个 JSON 文件作为收件箱:

通信协议

~/.claude/teams/{team_name}/inboxes/{agent_name}.json

消息格式:

// utils/teammateMailbox.ts
export type TeammateMessage = {
  from: string       // 发送者名称
  text: string       // 消息内容(可以是纯文本或 JSON 结构化消息)
  timestamp: string  // ISO 时间戳
  read: boolean      // 是否已读
  color?: string     // 发送者颜色
  summary?: string   // 5-10 字摘要(UI 预览用)
}

4.2 并发控制

多个 Agent 同时写入同一个邮箱时,使用 proper-lockfile 库实现文件锁:

const LOCK_OPTIONS = {
  retries: {
    retries: 10,      // 10 次重试
    minTimeout: 5,     // 最小等待 5ms
    maxTimeout: 100,   // 最大等待 100ms
  },
}

export async function writeToMailbox(recipientName, message, teamName) {
  const lockFilePath = `${inboxPath}.lock`
  const release = await lockfile.lock(inboxPath, { lockfilePath, ...LOCK_OPTIONS })
  try {
    const messages = await readMailbox(recipientName, teamName)
    messages.push({ ...message, read: false })
    await writeFile(inboxPath, JSON.stringify(messages, null, 2), 'utf-8')
  } finally {
    await release()
  }
}

4.3 消息类型全景

邮箱中传递的消息类型远不止纯文本,完整的结构化协议消息包括:

类型 方向 用途
permission_request Worker → Leader 请求工具使用权限
permission_response Leader → Worker 权限审批结果
sandbox_permission_request Worker → Leader 沙箱网络访问权限
sandbox_permission_response Leader → Worker 沙箱权限结果
shutdown_request Leader → Worker 请求队友关闭
shutdown_approved Worker → Leader 确认关闭
shutdown_rejected Worker → Leader 拒绝关闭(附原因)
plan_approval_request Worker → Leader 请求计划审批
plan_approval_response Leader → Worker 计划审批结果
idle_notification Worker → Leader 队友空闲通知
task_assignment Leader → Worker 任务分配
team_permission_update Leader → All 广播权限更新
mode_set_request Leader → Worker 设置权限模式

4.4 SendMessageTool

SendMessageTool 是 LLM 使用的通信接口,支持多种路由方式:

// tools/SendMessageTool/SendMessageTool.ts
async call(input, context) {
  // 1. UDS/Bridge 地址 → 跨会话发送
  if (feature('UDS_INBOX') && parseAddress(input.to).scheme === 'bridge') { ... }

  // 2. 进程内 subagent(通过 agentNameRegistry 查找)
  if (typeof input.message === 'string' && input.to !== '*') {
    const registered = appState.agentNameRegistry.get(input.to)
    if (registered) {
      // 运行中 → 直接加入 pendingMessages 队列
      if (task.status === 'running') {
        queuePendingMessage(agentId, input.message, ...)
      }
      // 已停止 → 自动恢复 (auto-resume)
      else { await resumeAgentBackground(...) }
    }
  }

  // 3. "*" → 广播到所有队友
  if (input.to === '*') return handleBroadcast(...)

  // 4. 名称 → 写入目标邮箱
  return handleMessage(input.to, input.message, ...)

  // 5. 结构化消息 → 分发到对应 handler
  switch (input.message.type) {
    case 'shutdown_request': return handleShutdownRequest(...)
    case 'shutdown_response': ...
    case 'plan_approval_response': ...
  }
}

五、队友生命周期与主循环

队友生命周期

5.1 进程内队友主循环

runInProcessTeammate() 是进程内队友的核心执行引擎:

// utils/swarm/inProcessRunner.ts
export async function runInProcessTeammate(config) {
  while (!abortController.signal.aborted && !shouldExit) {
    // 1. 创建 per-turn AbortController
    //    Escape 键可中断当前轮次但不杀死队友
    const currentWorkAbortController = createAbortController()

    // 2. 检查是否需要自动压缩
    if (tokenCount > getAutoCompactThreshold(model)) {
      const compactedSummary = await compactConversation(allMessages, ...)
      contextMessages = buildPostCompactMessages(compactedSummary)
      allMessages.length = 0
      allMessages.push(...contextMessages)
    }

    // 3. 在双重上下文中执行 runAgent()
    await runWithTeammateContext(teammateContext, async () => {
      return runWithAgentContext(agentContext, async () => {
        for await (const message of runAgent({
          agentDefinition: iterationAgentDefinition,
          promptMessages,
          canUseTool: createInProcessCanUseTool(identity, ...),
          forkContextMessages,
          ...
        })) {
          iterationMessages.push(message)
          allMessages.push(message)
          // 更新进度、追踪工具调用
        }
      })
    })

    // 4. 标记为 idle 并通知 Leader
    updateTaskState(taskId, task => ({ ...task, isIdle: true }))
    await sendIdleNotification(identity.agentName, identity.color, ...)

    // 5. 等待唤醒(轮询邮箱 + 任务列表)
    const waitResult = await waitForNextPromptOrShutdown(...)

    switch (waitResult.type) {
      case 'shutdown_request':
        // 传递给 LLM 模型决策
        currentPrompt = formatAsTeammateMessage(waitResult.request.from, ...)
        break
      case 'new_message':
        currentPrompt = formatAsTeammateMessage(waitResult.from, waitResult.message, ...)
        break
      case 'aborted':
        shouldExit = true
        break
    }
  }
}

5.2 等待唤醒的优先级策略

waitForNextPromptOrShutdown() 的轮询策略经过精心设计:

优先级从高到低:
① pendingUserMessages — 进程内直接注入的用户消息(最高优先)
② shutdown_request   — Leader 的关闭请求(扫描全部未读消息)
③ team-lead 消息     — Leader 的普通消息(代表用户意图)
④ 其他队友消息       — Peer DM(FIFO 顺序)
⑤ 任务列表          — 自动认领 pending + 无 owner 的任务
⑥ 无消息            — sleep(500ms) → 继续轮询

这个优先级设计确保了:

  • 用户输入永远被最先处理
  • 关闭请求不会被 peer-to-peer 消息洪水淹没
  • Leader 消息优先于队友间通信
  • 空闲队友自动认领任务,无需 Leader 手动分配

5.3 Tmux 队友初始化

Tmux 模式下的队友在启动时通过 initializeTeammateHooks() 初始化:

// utils/swarm/teammateInit.ts
export function initializeTeammateHooks(setAppState, sessionId, teamInfo) {
  // 1. 读取 TeamFile 获取 Leader 信息
  const teamFile = readTeamFile(teamName)

  // 2. 应用团队级路径权限
  for (const allowedPath of teamFile.teamAllowedPaths) {
    setAppState(prev => ({
      ...prev,
      toolPermissionContext: applyPermissionUpdate(prev.toolPermissionContext, {
        type: 'addRules',
        rules: [{ toolName: allowedPath.toolName, ruleContent: `/${allowedPath.path}/**` }],
        behavior: 'allow',
        destination: 'session',
      }),
    }))
  }

  // 3. 注册 Stop 钩子 — 停止时自动发送 idle_notification
  addFunctionHook(setAppState, sessionId, 'Stop', '', async (messages) => {
    void setMemberActive(teamName, agentName, false)
    const notification = createIdleNotification(agentName, { idleReason: 'available' })
    await writeToMailbox(leadAgentName, { from: agentName, text: JSON.stringify(notification), ... })
    return true
  })
}

这段初始化代码只覆盖了“变成 idle 时写回 isActive = false”的下半程;对应的上半程在 REPL.tsx 中完成:每当队友开始新一轮 query,onQuery() 会调用 setMemberActive(teamName, agentName, true)。也就是说,config.json 里的 isActive 字段并不是一次性元数据,而是被运行时持续刷新,Teams UI 读取的 running/idle 状态,本质上就是这套文件级心跳机制的结果。

5.4 优雅关闭流程

关闭流程是协商式的——Leader 发请求,但最终由 LLM 模型决策:

1. Leader 调用 SendMessage(shutdown_request) → 写入队友邮箱
2. 队友在 waitForNextPromptOrShutdown() 中检测到请求
3. 请求被格式化为 <teammate-message> 传递给 LLM
4. LLM 决策:
   - approve → 发送 shutdown_approved → abortController.abort() 或 gracefulShutdown()
   - reject  → 发送 shutdown_rejected (附原因) → 继续工作

强制杀死路径:

  • 进程内:killInProcessTeammate()abortController.abort()
  • tmux/iTerm2:getBackendByType(backendType).killPane(paneId)

六、权限同步模型

权限同步

6.1 路径一:Leader UI 队列(首选)

进程内队友的权限请求直接加入 Leader 的 ToolUseConfirm 队列:

// utils/swarm/inProcessRunner.ts — createInProcessCanUseTool()
const setToolUseConfirmQueue = getLeaderToolUseConfirmQueue()

if (setToolUseConfirmQueue) {
  return new Promise(resolve => {
    setToolUseConfirmQueue(queue => [...queue, {
      tool, description, input, toolUseContext, toolUseID,
      permissionResult: result,
      // Worker 标识 — 在 Leader UI 中显示名称和颜色
      workerBadge: { name: identity.agentName, color: identity.color },
      onAllow(updatedInput, permissionUpdates, feedback) {
        persistPermissionUpdates(permissionUpdates)
        // 权限更新回写到 Leader 的共享上下文
        if (permissionUpdates.length > 0) {
          setToolPermissionContext(updatedContext, { preserveMode: true })
        }
        resolve({ behavior: 'allow', updatedInput })
      },
      onReject(feedback) {
        resolve({ behavior: 'ask', message: SUBAGENT_REJECT_MESSAGE })
      },
    }])
  })
}

特点:实时 UI 交互、工具特定组件(Bash/FileEdit 各有专用 UI)、权限更新双向同步。

6.2 路径二:邮箱系统(回退)

当 UI 桥接不可用时,使用邮箱系统:

// Worker 侧:发送请求
const request = createPermissionRequest({ toolName, toolUseId, input, description, ... })
registerPermissionCallback({ requestId: request.id, onAllow, onReject })
await sendPermissionRequestViaMailbox(request)

// Worker 轮询响应 (每 500ms)
const pollInterval = setInterval(async () => {
  const allMessages = await readMailbox(identity.agentName, identity.teamName)
  for (const msg of allMessages) {
    const parsed = isPermissionResponse(msg.text)
    if (parsed && parsed.request_id === request.id) {
      processMailboxPermissionResponse(parsed)
      return
    }
  }
}, PERMISSION_POLL_INTERVAL_MS)

6.3 Bash 分类器优化

进程内队友对 Bash 命令有特殊优化——先等分类器结果,再决定是否弹出审批:

if (feature('BASH_CLASSIFIER') && tool.name === BASH_TOOL_NAME && result.pendingClassifierCheck) {
  const classifierDecision = await awaitClassifierAutoApproval(
    result.pendingClassifierCheck,
    abortController.signal,
  )
  if (classifierDecision) {
    return { behavior: 'allow', updatedInput: input, decisionReason: classifierDecision }
  }
}

七、状态持久化与上下文隔离

7.1 文件系统持久化

~/.claude/
├── teams/{team-name}/
│   ├── config.json                  # TeamFile — 团队配置
│   ├── inboxes/
│   │   ├── team-lead.json           # Leader 的收件箱
│   │   ├── researcher.json          # 队友的收件箱
│   │   └── tester.json
│   └── permissions/
│       ├── pending/                 # 待审批的权限请求
│       └── resolved/               # 已处理的权限请求
└── tasks/{team-name}/              # 任务列表(与团队 1:1 对应)

7.2 内存状态 (AppState)

// AppState.teamContext
teamContext: {
  teamName: string
  teamFilePath: string
  leadAgentId: string
  teammates: {
    [agentId: string]: {
      name: string
      agentType?: string
      color?: string
      tmuxSessionName: string
      tmuxPaneId: string
      cwd: string
      spawnedAt: number
    }
  }
}

// AppState.tasks[taskId] — InProcessTeammateTaskState
{
  type: 'in_process_teammate'
  identity: TeammateIdentity        // agentId, agentName, teamName, color
  prompt: string
  abortController?: AbortController // 生命周期控制
  currentWorkAbortController?: AbortController // 当前轮次控制
  permissionMode: PermissionMode
  messages?: Message[]              // UI 镜像,上限 50 条
  pendingUserMessages: string[]     // 待传递的用户消息
  isIdle: boolean
  shutdownRequested: boolean
  progress?: AgentProgress
}

7.3 AsyncLocalStorage 上下文隔离

进程内队友使用 Node.js 的 AsyncLocalStorage 实现上下文隔离:

// utils/teammateContext.ts
const teammateContextStorage = new AsyncLocalStorage<TeammateContext>()

export type TeammateContext = {
  agentId: string           // "researcher@my-team"
  agentName: string         // "researcher"
  teamName: string
  color?: string
  planModeRequired: boolean
  parentSessionId: string
  isInProcess: true         // 始终为 true
  abortController: AbortController
}

// 在特定上下文中运行
export function runWithTeammateContext<T>(context: TeammateContext, fn: () => T): T {
  return teammateContextStorage.run(context, fn)
}

身份解析优先级teammate.ts):

1. AsyncLocalStorage(进程内队友)→ getTeammateContext()
2. dynamicTeamContext(tmux 队友通过 CLI 参数)
3. 传入的 teamContext(Leader 通过 AppState)

八、任务列表协调模式

8.1 任务驱动协调

团队通过共享的任务列表进行工作协调:

1. Leader 创建任务 → TaskCreate → ~/.claude/tasks/{team}/
2. 队友自动认领   → 空闲时扫描 pending + 无 owner 的任务
3. 按 ID 顺序优先 → 低 ID 先认领(保证依赖顺序)
4. 完成后标记     → TaskUpdate → status: completed
5. 检查依赖       → blockedBy 解除后才能认领

这里需要特别注意一个容易写错的实现细节:当前版本的任务列表解析优先级,已经明确偏向“团队名统一寻址”getTaskListId() 的优先级是:CLAUDE_CODE_TASK_LIST_ID → 进程内队友的 teamName → 进程型队友的 CLAUDE_CODE_TEAM_NAME → Leader 侧的 leaderTeamNamesessionId 回退。因此 TeamCreateTool 中那次 setLeaderTeamName() 调用并不是装饰性代码,而是一个关键补丁:它确保 Team Lead 不会继续把任务写进自己的 session 目录,而是与所有队友共享同一个团队任务目录。

// utils/swarm/inProcessRunner.ts
function findAvailableTask(tasks: Task[]): Task | undefined {
  const unresolvedTaskIds = new Set(
    tasks.filter(t => t.status !== 'completed').map(t => t.id)
  )
  return tasks.find(task => {
    if (task.status !== 'pending') return false
    if (task.owner) return false
    // 所有前置依赖必须已完成
    return task.blockedBy.every(id => !unresolvedTaskIds.has(id))
  })
}

8.2 空闲通知机制

每个队友完成一轮执行后,自动发送 idle_notification

const notification = createIdleNotification(agentName, {
  idleReason: workWasAborted ? 'interrupted' : 'available',
  summary: getLastPeerDmSummary(allMessages),  // 最后一条 peer DM 的摘要
  completedTaskId,
  completedStatus: 'resolved' | 'blocked' | 'failed',
})

Leader 收到通知后可以:

  • 分配新任务
  • 发送后续消息
  • 忽略(队友会自动认领任务列表中的工作)

九、计划审批模式

plan_mode_required 为 true 时,队友必须先制定计划再实施:

1. 队友进入计划模式 → 制定实施方案
2. 发送 plan_approval_request → Leader 邮箱
   { planFilePath, planContent, requestId }
3. Leader 侧收到请求后,当前默认实现会在 useInboxPoller 中自动批准:
   - approved = true
   - permissionMode 继承 Leader 当前模式(如果 Leader 自己仍是 plan,则降级成 default)
4. 队友收到 plan_approval_response 后退出计划态,进入实施阶段

这里有一个很值得点出的实现现状:协议层保留了“手工批准 / 手工拒绝”的完整能力,但默认运行路径已经改成了 auto-approve。一方面,SendMessageTool 仍然实现了 plan_approval_response 的 approve / reject 处理逻辑,并且通过 isTeamLead() 限制只有 Team Lead 才能发送这类审批结果;另一方面,useInboxPoller 在 Leader 侧检测到 plan_approval_request 时,会直接构造批准消息写回队友邮箱,并把原消息继续作为普通消息透传给模型。

这意味着当前版本更像是一个**“协议先完整、产品策略后收敛”**的过渡态:底层消息协议已经支持严格的人工 gate,但默认 UX 为了减少阻塞,选择了自动批准。写文章时如果只说“Leader 手工审阅后批准”,就会和现状实现有偏差;更准确的描述应该是:手工审批是协议能力,自动批准是当前默认策略。


十、队友系统提示

所有队友在标准系统提示之后附加 TEAMMATE_SYSTEM_PROMPT_ADDENDUM

# Agent Teammate Communication

IMPORTANT: You are running as an agent in a team. To communicate with anyone on your team:
- Use the SendMessage tool with `to: "<name>"` to send messages to specific teammates
- Use the SendMessage tool with `to: "*"` sparingly for team-wide broadcasts

Just writing a response in text is not visible to others on your team —
you MUST use the SendMessage tool.

进程内队友还始终注入以下团队必需工具(即使 agent 定义限制了工具列表):

  • SendMessage — 通信
  • TeamCreate / TeamDelete — 团队管理
  • TaskCreate / TaskGet / TaskList / TaskUpdate — 任务协调

十一、总结与设计哲学

核心设计原则

原则 实现
基础设施简单 文件系统作为消息总线,无需额外中间件
确定性 ID name@team 格式,可预测、人类可读
协商式关闭 shutdown 由 LLM 决策而非强制终止
权限安全 计划模式下不继承 bypassPermissions
自动回退 in-process 可自动降级到 tmux/iTerm2
会话清理 退出时自动清理孤立的团队目录和窗格

架构层次

┌──────────────────────────────────────────┐
│              Coordination Layer           │  TeamCreateTool / TaskListTool
├──────────────────────────────────────────┤
│              Communication Layer          │  SendMessageTool / Mailbox
├──────────────────────────────────────────┤
│              Execution Layer              │  inProcessRunner / tmux / iTerm2
├──────────────────────────────────────────┤
│              Permission Layer             │  permissionSync / workerBadge
├──────────────────────────────────────────┤
│              Identity Layer               │  agentId / teammateContext / ALS
├──────────────────────────────────────────┤
│              Storage Layer                │  TeamFile / AppState / TaskList
└──────────────────────────────────────────┘

与传统多 Agent 框架的对比

特性 Claude Code Agent Teams AutoGen / CrewAI
通信方式 文件邮箱 + 进程内队列 内存消息传递
进程模型 同进程 ALS / tmux 分屏 通常同进程
权限控制 两路径审批 + 计划模式 通常无权限隔离
任务协调 文件系统任务列表 + 自动认领 框架级编排
关闭方式 协商式(LLM 决策) 通常编程式
状态持久化 文件 + 内存双写 通常仅内存

Agent Teams 的设计体现了 Claude Code 一贯的工程哲学:用最简单的基础设施(文件系统)实现最可靠的分布式协调,同时保持对安全性和用户体验的极致关注。

「真诚赞赏,手留余香」

爱折腾的工程师

真诚赞赏,手留余香

使用微信扫描二维码完成支付