Claude Code 的多智能体系统由三个递进层级构成:单次 Subagent(轻量委托)→ Fork Subagent(上下文克隆分身)→ Swarm / Team(多进程协作群)。它们共享同一个
runAgent()核心,但在隔离策略、通信方式和生命周期上各有不同。
1. 三种模式一览
| 特性 | 普通 Subagent | Fork Subagent | Swarm Teammate |
|---|---|---|---|
| 触发方式 | Agent(subagent_type=X) | Agent() 不带 subagent_type | Agent(name=X, team_name=Y) |
| 上下文继承 | 空 / 只从 prompt 开始 | 完整克隆父对话 + 工具结果 | 独立进程,仅通过 mailbox 通信 |
| 运行位置 | 父进程内(sync/async) | 父进程内,后台异步 | 子进程(tmux/iTerm2)或 in-process |
| AbortController | sync: 共享父的; async: 独立 | 独立 | 独立(不受领导者中断影响) |
| 权限提示 | async: 自动拒绝; sync: 气泡到父 | bubble → 气泡到父终端 | 通过 permissionSync 文件路由 |
| 通信机制 | 返回值 / Generator yield | 父 context 注入 + 返回值 | 文件信箱(mailbox) |
| sidechain 转录 | ✅ | ✅ | ✅ |
| 门控条件 | 默认启用 | feature('FORK_SUBAGENT') | isAgentSwarmsEnabled() |
2. Subagent(普通子代理)
2.1 实体:runAgent() 异步生成器
src/tools/AgentTool/runAgent.ts 的核心是一个 AsyncGenerator,外部通过 for await 消费其输出消息,实现流式透传。
关键设计决策:
parent → AgentTool.call()
├── 构建 agentToolUseContext(独立 AppState 视图)
├── resolveAgentTools() ← 按定义过滤工具集
├── getAgentSystemPrompt() ← 专属系统提示词
├── createSubagentContext() ← 上下文隔离
└── runAgent() → query() ← 流式游历 API 轮次
↓
yield messages → agentToolUtils.runAsyncAgentLifecycle()
权限模式继承规则(优先级从高到低):
bypassPermissions/acceptEdits/auto— 父级总是赢,子代理不可覆盖agentDefinition.permissionMode— 定义文件中的声明isAsync=true→shouldAvoidPermissionPrompts=true(不弹 UI)canShowPermissionPrompts=true→awaitAutomatedChecksBeforeDialog=true(先等自动化检查)
Claude.md 裁剪优化:
Explore / Plan 等只读代理默认剥除 claudeMd(省 ~5-15 Gtok/week)和 gitStatus(节省上下文),通过 GrowthBook 门控 tengu_slim_subagent_claudemd。
2.2 工具集解析 resolveAgentTools()
agentDefinition.tools: ['*'] → 继承父工具集
agentDefinition.tools: ['Bash','Edit'] → 精确子集
agentDefinition.tools: ['!Bash'] → 排除模式
useExactTools=true → 完全跳过过滤(fork 路径专用)
allowedTools 参数在作用域上替换父级 session 权限(保留 cliArg 层),避免父级许可泄漏给子代理。
2.3 生命周期 finally 清理
runAgent() 的 finally 块确保即使抛出 AbortError 也执行:
- MCP 服务器关闭(
mcpCleanup()) - Session hooks 清理(
clearSessionHooks()) - Prompt cache 追踪状态清理
- 文件状态缓存释放(
readFileState.clear()) - Todos 孤立条目删除
- 后台 Bash 进程杀死(
killShellTasksForAgent()) - Perfetto trace 注销
3. Fork Subagent(分叉子代理)
3.1 设计理念
Fork 是上下文克隆模式:子代理不是从空白对话开始,而是继承父对话的完整消息历史。核心思路是最大化利用父代理的 prompt cache——子代理与父代理共享相同的 API 请求前缀,命中缓存的概率极高。
启用条件:
// 互斥约束:不能与 coordinator 模式共用
feature('FORK_SUBAGENT') && !isCoordinatorMode() && !isNonInteractiveSession()
3.2 消息构建 buildForkedMessages()
父 AssistantMessage(含所有 tool_use 块)
↓ clone
[fullAssistantMessage]
+
user 消息:
[tool_result x N (全为 FORK_PLACEHOLDER)] ← 占位符让 API 认为工具已完成
+ text: <fork_directive>子任务说明</fork_directive>
FORK_PLACEHOLDER_RESULT 是固定字符串(保证 cache 命中),子代理看到的是已有工具结果和新指令。
3.3 useExactTools=true 稳定化
Fork 模式传 useExactTools=true:
- 直接使用父工具池,不经
resolveAgentTools()过滤 - 继承父的
thinkingConfig(思维链设置) - 继承父的
isNonInteractiveSession值 - 目的:让子代理的 API 请求前缀与父代理字节完全一致,命中 server-side prompt cache
3.4 递归 Fork 防护
// 双重检查,对抗 autocompact 改写消息后的 bypass
if (toolUseContext.options.querySource === `agent:builtin:${FORK_AGENT.agentType}`
|| isInForkChild(toolUseContext.messages)) {
throw new Error('Fork is not available inside a forked worker.')
}
4. Swarm(群集多代理)
4.1 架构概览
Leader Process(claude REPL)
│
├── TeamFile (.claude/teams/{team}/team.json)
│ members: [{ agentId, agentName, color, backendType, paneId, ... }]
│
├── Mailbox (.claude/teams/{team}/inboxes/{agent_name}.json)
│ 用于 peer DM、权限请求、shutdown、idle 通知
│
├── Backend(运行时自动选择)
│ ├── tmux → 创建 tmux pane,发 claude CLI 命令
│ ├── iterm2 → it2 split pane
│ └── in-process → AsyncLocalStorage 隔离,同进程运行
│
└── Leader Permission Bridge(leaderPermissionBridge.ts)
module-level 全局 setter → 允许 in-process 队友使用 leader 的权限对话框
4.2 后端选择策略(registry.ts)
优先级从高到低:
- tmux — 在 tmux 会话中且 tmux 可用
- iterm2 — 在 iTerm2 中且
it2CLI 已安装 - in-process — 兜底(无终端分栏依赖)
选择结果在进程生命周期内固定缓存(cachedBackend)。
4.3 TeamFile:持久化团队状态
路径:~/.claude/teams/{team_name}/team.json
type TeamFile = {
leadAgentId: string // 唯一领导者
members: TeamMember[] // 包含 paneId、backendType、agentId
hiddenPaneIds: PaneId[] // 支持 hide/show
allowedPaths: TeamAllowedPath[] // 权限沙盒
}
写操作均通过 lockfile 序列化,防止并发的多个 Claude 进程竞争写:
const LOCK_OPTIONS = { retries: { retries: 10, minTimeout: 5, maxTimeout: 100 }}
4.4 文件信箱(Mailbox)通信协议
路径:~/.claude/teams/{team}/inboxes/{agent_name}.json
消息格式:
type TeammateMessage = {
from: string // 发送者名称
text: string // 消息内容
timestamp: string
read: boolean
color?: string // 发送者颜色(UI 显示用)
summary?: string // 5-10 字摘要(预览用)
}
消息类型(teammateMailbox.ts):
| 消息类型 | 方向 | 用途 |
|---|---|---|
peer_dm | 任意→任意 | SendMessageTool 的 DM |
permission_request | worker→leader | 权限请求路由 |
permission_response | leader→worker | 权限决策回传 |
shutdown_request | leader→worker | 优雅关闭 |
idle_notification | worker→leader | 完成通知 |
sandbox_permission_request/response | worker↔leader | 沙盒权限 |
写操作原子性:通过 lockfile 包,10 次重试 + 指数退避,防止 N 个并发 Claude 进程同时写同一收件箱。
4.5 权限同步机制(permissionSync.ts)
Worker 遇到需要确认的工具调用
│
├─ [in-process 路径]
│ leader 的 ToolUseConfirm 队列(内存 setter)
│ ← leaderPermissionBridge.getLeaderToolUseConfirmQueue()
│
└─ [pane 路径]
worker → writeToMailbox(leader, permission_request)
│
leader 轮询检测 → UI 弹出确认对话框
│
leader → writeToMailbox(worker, permission_response)
│
worker 轮询收到决策 → 继续 / 拒绝
轮询间隔:PERMISSION_POLL_INTERVAL_MS = 500ms,由 useSwarmPermissionPoller hook 驱动。
4.6 In-Process Teammate 隔离
通过 AsyncLocalStorage 实现同进程内的上下文隔离:
spawnInProcessTeammate()
├── createAbortController() ← 独立中止(不受领导者影响)
├── createTeammateContext() ← AsyncLocalStorage 上下文
├── registerTask(taskState, setAppState) ← 在 AppState 中注册显示
└── startInProcessTeammate() ← runWithTeammateContext() 包裹
└── runAgent() 在隔离上下文中执行
优点:无需额外进程,共享 API 连接和 MCP 服务器。 缺点:崩溃会影响主进程;不支持从 in-process teammate 内部再派生后台代理(限制:"In-process teammates cannot spawn background agents")。
5. Coordinator 模式
COORDINATOR_MODE 是另一套协调架构,与 Fork 互斥:
CLAUDE_CODE_COORDINATOR_MODE=1 → isCoordinatorMode()
├── getCoordinatorSystemPrompt() ← 专属系统提示词
├── getCoordinatorUserContext() ← 专属用户上下文
├── INTERNAL_WORKER_TOOLS ← 仅限内部工具集
└── 禁用 Fork subagent
设计目标:Coordinator 是轻量级调度者,不直接执行工具,而是向 worker 发任务;与 Fork(克隆并行)策略相对。
6. 通信流汇总
┌─────────────────────────────────────────────────────────┐
│ Leader REPL │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Mailbox Poller(500ms 轮询) │ │
│ │ · 收取 peer_dm → 注入为 attachment 消息 │ │
│ │ · 收取 permission_request → 弹 UI 对话框 │ │
│ │ · 收取 idle_notification → Task 状态更新 │ │
│ └─────────────────────────────────────────────────┘ │
│ ↕ 内存信号 │
│ ┌──────────────────────────────────────────────────┐ │
│ │ In-Process Teammate(AsyncLocalStorage 隔离) │ │
│ │ permission via leaderPermissionBridge (内存 fn) │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
↕ 文件 I/O(mailbox + lockfile)
┌──────────────────────────────────────────────────────────┐
│ Pane Teammate(独立进程) │
│ claude CLI 以环境变量指定 team/agent 信息 │
│ · CLAUDE_CODE_AGENT_ID / NAME / TEAM_NAME / COLOR │
│ · CLAUDE_CODE_LEADER_AGENT_ID │
└──────────────────────────────────────────────────────────┘
7. 设计亮点
7.1 Prompt Cache 最大化(Fork 设计)
Fork 子代理通过 useExactTools=true + 固定 placeholder result 确保 API 请求前缀与父代理字节完全一致,最大化 server-side prompt cache 命中率。这是性能优化的核心。
7.2 工具权限隔离(allowedTools 替换语义)
parent session rules: [A, B, C]
子代理 allowedTools: [X, Y]
→ 子代理实际拥有: [cliArg rules, X, Y]
父级 session 允许的工具不会自动泄漏给子代理,必须显式传递。cliArg 层(SDK --allowedTools)始终保留,因为这是外部用户的显式意图。
7.3 文件信箱 + lockfile 并发安全
多个 Claude 进程可同时写同一信箱,通过 lockfile 的重试+指数退避实现可靠串行化。比共享内存队列更健壮(跨进程),比数据库更轻量。
7.4 Backend 抽象层(TeammateExecutor 接口)
interface TeammateExecutor {
spawn(config: TeammateSpawnConfig): Promise<TeammateSpawnResult>
sendMessage(agentId, message): Promise<void>
kill(agentId): Promise<void>
}
统一接口屏蔽 tmux / iTerm2 / in-process 差异,注册模式(registerTmuxBackend())避免循环依赖。
7.5 sidechain 转录(每个代理独立 JSONL)
每个子代理把消息写入 subagents/{agentId}.jsonl,支持会话恢复和调试查看,且使用增量追加(lastRecordedUuid 游标),不重写历史。
7.6 Claude.md 精简(读写代理分级优化)
只读代理(Explore、Plan)自动剥除 claudeMd 和 gitStatus,节省大量 token。按 agentDefinition.omitClaudeMd 字段在代理定义文件中声明,kill-switch 通过 GrowthBook 控制。
8. 可直接复用的设计模式
8.1 文件信箱(Mailbox + lockfile)
场景:多进程 / 多 worker 异步通信,且不想引入消息队列中间件。
// 写:序列化写,带重试退避
await lockfile.lock(path, opts)
const msgs = JSON.parse(await readFile(path))
msgs.push(newMsg)
await writeFile(path, JSON.stringify(msgs))
lockfile.unlock(path)
// 读:轮询 + 标记已读
const msgs = readMailbox(agentName)
const unread = msgs.filter(m => !m.read)
8.2 权限决策气泡(bubble permission mode)
场景:子任务需要弹 UI,但希望对话框出现在主进程终端而非子进程。
permissionMode: 'bubble'+leaderPermissionBridgesetter 注册- Pane 路径:mailbox 双向路由
8.3 AsyncLocalStorage 上下文隔离
场景:同进程并发运行多个"租户",每个租户有独立的 agentId、teamName、color 等上下文。
const teammateContext = new AsyncLocalStorage<TeammateContext>()
runWithTeammateContext(ctx, () => runAgent(...))
// 在任意调用深度取上下文:
getTeamName() → teammateContext.getStore()?.teamName
8.4 Generator 流式 + 终止信号
async function* runAgent(...): AsyncGenerator<Message> {
const controller = isAsync ? new AbortController() : parent.abortController
for await (const msg of query({ signal: controller.signal })) {
yield msg // 流式透传
}
if (controller.signal.aborted) throw new AbortError()
}
消费方调用 break 即可中止,不需要手动清理——finally 块统一清理。
8.5 懒加载工具 Schema(lazySchema())
const inputSchema = lazySchema(() => z.object({ ... }))
// 首次访问时才构建 Zod schema,配合 feature() 门控
避免在模块加载阶段就执行条件 feature() 检查(build-time DCE 友好)。
8.6 Backend 注册模式(避免循环依赖)
// registry.ts
let TmuxBackendClass: (new () => PaneBackend) | null = null
export function registerTmuxBackend(cls: new () => PaneBackend) {
TmuxBackendClass = cls
}
// TmuxBackend.ts(在模块末尾)
registerTmuxBackend(TmuxBackend)
Backend 实现文件负责向 registry 注册自己,registry 不直接 import 实现,打破循环依赖。
9. 源码索引
| 职责 | 文件 |
|---|---|
| Agent 工具入口 | src/tools/AgentTool/AgentTool.tsx |
| 子代理运行核心 | src/tools/AgentTool/runAgent.ts |
| Fork 消息构建 | src/tools/AgentTool/forkSubagent.ts |
| TeamFile & 锁 | src/utils/swarm/teamHelpers.ts |
| 权限同步 | src/utils/swarm/permissionSync.ts |
| 领导者权限桥 | src/utils/swarm/leaderPermissionBridge.ts |
| In-process 运行器 | src/utils/swarm/inProcessRunner.ts |
| In-process 生成 | src/utils/swarm/spawnInProcess.ts |
| 文件信箱 | src/utils/teammateMailbox.ts |
| 内存信箱 | src/utils/mailbox.ts |
| 后端接口 | src/utils/swarm/backends/types.ts |
| 后端注册表 | src/utils/swarm/backends/registry.ts |
| Swarm 门控 | src/utils/agentSwarmsEnabled.ts |
| Coordinator 模式 | src/coordinator/coordinatorMode.ts |
| 通信时序图 | agent-swarm-communication.puml |