跳转到内容

Team & Swarm

除了简单的 sub-agent 派生和 coordinator pattern,Claude Code 还实现了完整的 team/swarm 系统——持久化的 multi-agent team,成员可以以不同的后端策略并发运行。这是代码库中最复杂的 multi-agent 模式。

team 是存储为 JSON 文件的持久化配置,其成员可以跨不同执行后端进行派生:

graph TD
    L[Leader Agent] --> TC[TeamContext in AppState]
    TC --> M1[Member: researcher<br/>InProcess Backend]
    TC --> M2[Member: implementer<br/>Tmux Backend]
    TC --> M3[Member: reviewer<br/>iTerm2 Backend]

    M1 ---|AsyncLocalStorage| P1[Same Node.js process]
    M2 ---|tmux session| P2[Separate terminal pane]
    M3 ---|iTerm2 tab| P3[Separate iTerm2 tab]

team 以 JSON 文件形式持久化,由 src/utils/swarm/teamHelpers.ts 管理:

// TeamFile 结构(来自 teamHelpers.ts)
type TeamFile = {
name: string
description: string
members: TeamMember[]
}
type TeamMember = {
agentId: string // 格式:"name@team"(如 "researcher@project-team")
name: string
agentType: string
model?: string
prompt: string
color?: string
planModeRequired: boolean
worktreePath?: string // 用于 isolation 的 git worktree
sessionId?: string
subscriptions?: string[]
backendType: 'tmux' | 'iterm2' | 'in_process'
isActive: boolean
mode?: string
}

agentId 遵循 name@team 的确定性格式,由 formatAgentId() 生成:

src/utils/agentId.ts
export function formatAgentId(name: string, teamName: string): string {
return `${name}@${teamName}`
}

in-process 后端使用 AsyncLocalStorage 进行 context isolation,在同一 Node.js 进程中运行队友:

src/utils/swarm/spawnInProcess.ts
export async function spawnInProcessTeammate(
config: InProcessSpawnConfig,
context: SpawnContext,
): Promise<InProcessSpawnOutput> {
const agentId = formatAgentId(name, teamName)
const taskId = generateTaskId('in_process_teammate')
// 独立的 AbortController——不与 leader 的 query 绑定
const abortController = createAbortController()
// 为 AsyncLocalStorage 创建队友 context
const teammateContext = createTeammateContext({
agentId, agentName: name, teamName, color,
planModeRequired, parentSessionId, abortController,
})
// 在 AppState 中创建并注册任务状态
const taskState: InProcessTeammateTaskState = {
...createTaskStateBase(taskId, 'in_process_teammate', description),
type: 'in_process_teammate',
status: 'running',
identity,
prompt,
model,
abortController,
awaitingPlanApproval: false,
permissionMode: planModeRequired ? 'plan' : 'default',
isIdle: false,
shutdownRequested: false,
messages: [],
// ...
}
registerTask(taskState, setAppState)
return { success: true, agentId, taskId, abortController, teammateContext }
}

Tmux 后端在 tmux pane 中以独立终端 session 的形式派生队友。每个队友作为独立的 Claude Code 进程在自己的 pane 中运行。

与 Tmux 类似,但使用 iTerm2 在 macOS 上的原生 tab/pane 管理,为 iTerm2 用户提供更集成的体验。

src/utils/swarm/spawnInProcess.ts
export type InProcessSpawnConfig = {
name: string // 显示名称,如 "researcher"
teamName: string // 该成员所属的 team
prompt: string // 初始任务/指令
color?: string // 队友的 UI 颜色
planModeRequired: boolean // 实现前必须进入 plan 模式
model?: string // 可选的模型覆盖
}

每个队友都携带一个在其生命周期内持久存在的 TeammateIdentity

// 以普通数据形式存储在 AppState 中
const identity: TeammateIdentity = {
agentId, // "researcher@project-team"
agentName: name, // "researcher"
teamName, // "project-team"
color,
planModeRequired,
parentSessionId, // 链接回 leader 的 session
}

killInProcessTeammate() 函数处理优雅关闭:

src/utils/swarm/spawnInProcess.ts
export function killInProcessTeammate(
taskId: string,
setAppState: SetAppStateFn,
): boolean {
setAppState((prev: AppState) => {
const task = prev.tasks[taskId] as InProcessTeammateTaskState
if (!task || task.status !== 'running') return prev
// 1. 中止 controller 以停止执行
task.abortController?.abort()
// 2. 调用清理处理函数
task.unregisterCleanup?.()
// 3. 调用待处理的 idle 回调以解除等待者的阻塞
task.onIdleCallbacks?.forEach(cb => cb())
// 4. 从 teamContext.teammates 中移除
const { [agentId]: _, ...remainingTeammates } = prev.teamContext.teammates
return {
...prev,
teamContext: { ...prev.teamContext, teammates: remainingTeammates },
tasks: {
...prev.tasks,
[taskId]: {
...task,
status: 'killed',
endTime: Date.now(),
messages: task.messages?.length
? [task.messages[task.messages.length - 1]!] // 仅保留最后一条消息
: undefined,
abortController: undefined, // 释放引用
unregisterCleanup: undefined,
currentWorkAbortController: undefined,
},
},
}
})
// 状态更新后的清理
if (teamName && agentId) {
removeMemberByAgentId(teamName, agentId) // 从 team 文件中移除
}
evictTaskOutput(taskId) // 清理磁盘输出
emitTaskTerminatedSdk(taskId, 'stopped') // SDK 通知
unregisterPerfettoAgent(agentId) // 释放追踪条目
return killed
}

team 解散或 session 结束时,会执行全面的清理:

flowchart TD
    A[cleanupTeamDirectories] --> B[Read team config.json]
    B --> C{For each member}
    C --> D[destroyWorktree]
    C --> E[Remove task directories]
    D --> F{Has .git file?}
    F -->|yes| G[git worktree remove --force]
    F -->|no| H[rm -rf worktree path]
    C --> I[Delete team config file]

Worktree 销毁(teamHelpers.ts 中的 destroyWorktree)尤为谨慎——它读取 .git 文件以找到主仓库,然后使用 git worktree remove --force。若该命令失败,则回退到 rm -rf

team 成员在 Perfetto trace 中注册,以实现层次结构可视化:

// 派生时
if (isPerfettoTracingEnabled()) {
registerPerfettoAgent(agentId, name, parentSessionId)
}
// 终止时
unregisterPerfettoAgent(agentId)

这使得在性能追踪工具中查看整个 team 层次结构——leader 和所有队友——成为可能。

team 系统与 Claude Code 的响应式状态深度集成:

  • AppState 上的 teamContext 保存实时 team 配置和队友映射
  • 每个队友的 InProcessTeammateTaskState 存储在 AppState.tasks
  • InProcessTeammateTask React 组件(Task #14)驱动实际的 agent 执行循环
  • 状态变更触发 UI 更新,显示队友状态、spinner 动词和 tool 调用计数

队友的状态机:

spawned → running → idle ↔ running → completed/killed
↑ |
└─ pending user messages

队友在 running(正在执行 tool)和 idle(等待指令)之间交替,leader 可以通过 SendMessage 向处于 idle 状态的队友发送消息。