第六章:Claude Code CLI 多 Agent 协作------AgentTool、Coordinator 与 Worktree 隔离
- [第六章:Claude Code CLI 多 Agent 协作------AgentTool、Coordinator 与 Worktree 隔离](#第六章:Claude Code CLI 多 Agent 协作——AgentTool、Coordinator 与 Worktree 隔离)
-
- [一、为什么需要多 Agent?](#一、为什么需要多 Agent?)
- [二、Agent 定义体系](#二、Agent 定义体系)
-
- [2.1 三种来源](#2.1 三种来源)
- [2.2 Agent 定义的核心字段](#2.2 Agent 定义的核心字段)
- 三、权限模式(PermissionMode)
-
- [3.1 五种外部权限模式](#3.1 五种外部权限模式)
- [3.2 内部权限模式](#3.2 内部权限模式)
- 四、工具权限:三层收窄机制
-
- [4.1 第一层:所有 Agent 的全局禁止清单](#4.1 第一层:所有 Agent 的全局禁止清单)
- [4.2 第二层:自定义 Agent 的额外限制](#4.2 第二层:自定义 Agent 的额外限制)
- [4.3 第三层:异步 Agent 的正向白名单](#4.3 第三层:异步 Agent 的正向白名单)
- [4.4 `resolveAgentTools()` 执行逻辑](#4.4
resolveAgentTools()执行逻辑)
- [五、AgentTool 执行链路](#五、AgentTool 执行链路)
-
- [5.1 六个阶段逐步解析](#5.1 六个阶段逐步解析)
- [5.2 状态隔离:`createSubagentContext()`](#5.2 状态隔离:
createSubagentContext()) - [5.2 状态隔离:`createSubagentContext()`](#5.2 状态隔离:
createSubagentContext())
- [六、异步 Agent 生命周期](#六、异步 Agent 生命周期)
-
- [6.1 触发条件](#6.1 触发条件)
- [6.2 `runAsyncAgentLifecycle()` 执行阶段](#6.2
runAsyncAgentLifecycle()执行阶段)
- [七、Fork Subagent:并发分支与 Cache 共享](#七、Fork Subagent:并发分支与 Cache 共享)
-
- [7.1 Fork 的特殊性](#7.1 Fork 的特殊性)
- [7.2 并发 Fork 的 Cache 共享策略](#7.2 并发 Fork 的 Cache 共享策略)
- [7.3 递归保护与行为约束](#7.3 递归保护与行为约束)
- [八、Worktree 隔离](#八、Worktree 隔离)
-
- [8.1 解决的问题](#8.1 解决的问题)
- [8.2 物理结构](#8.2 物理结构)
- [8.3 生命周期管理](#8.3 生命周期管理)
- [九、Coordinator 模式](#九、Coordinator 模式)
-
- [9.1 设计意图](#9.1 设计意图)
- [9.2 工具集限制](#9.2 工具集限制)
- [9.3 Coordinator ↔ Worker 交互流程](#9.3 Coordinator ↔ Worker 交互流程)
- 十、整体架构总览
- 十一、设计原则总结
第六章:Claude Code CLI 多 Agent 协作------AgentTool、Coordinator 与 Worktree 隔离
一、为什么需要多 Agent?
单 Agent 架构有三个硬性瓶颈:
上下文窗口上限:大型项目的代码、历史对话、工具调用结果累积到一定量就会触发 compact(见第五章)。子 Agent 各自维护独立的上下文窗口,互不干扰。
顺序执行效率:单 Agent 在同一对话里处理前端重构、后端接口、测试补充,只能串行进行。多 Agent 并发执行,任务完成时间从 O(n) 压缩到接近 O(1)。
权限边界模糊:单 Agent 拥有所有工具权限,误操作的影响面无法控制。多 Agent 架构下,每个子 Agent 的工具集精确限定到任务所需的最小集合。
Claude Code 的解法是分层 Agent 架构:主 Agent 通过 AgentTool 工具派生专门化子 Agent,各自执行子任务,结果汇回主 Agent。本章拆解这套架构的每一层实现。
二、Agent 定义体系
2.1 三种来源
tools/AgentTool/loadAgentsDir.ts
Agent 定义按来源分为三类:
| 类型 | 来源 | 系统提示获取方式 |
|---|---|---|
built-in |
代码内置,系统预定义 | getSystemPrompt(options) 动态生成 |
custom |
.claude/agents/*.md 文件 |
Markdown 正文,构建时读取 |
plugin |
第三方 MCP 插件注入 | getSystemPrompt() 异步获取 |
同名 Agent 的优先级(高→低):managed > flag > project > user > plugin > built-in。
2.2 Agent 定义的核心字段
自定义 Agent 通过 Markdown 文件定义,frontmatter 是 YAML,正文是系统提示:
markdown
---
name: backend-agent
description: 负责 Node.js/Express API 开发,专注 src/api/ 目录
tools: Read, Write, Edit, Bash, Glob, Grep
model: claude-sonnet-4-6
maxTurns: 80
isolation: worktree
permissionMode: acceptEdits
---
你是一位后端工程师,负责维护 Express REST API。
工作范围严格限定在 src/api/ 目录...
BaseAgentDefinition 的关键字段及其作用:
| 字段 | 作用 | 设计意图 |
|---|---|---|
agentType |
唯一标识,主 Agent 调用时的 subagent_type 参数 |
同名 Agent 按优先级覆盖 |
whenToUse |
向主模型暴露的调用时机说明 | 模型用此判断何时 spawn 此 Agent |
tools |
允许的工具列表,* 继承父级过滤后的全集 |
精确控制工具权限边界 |
disallowedTools |
在 tools 基础上额外禁止 |
细粒度减法,不需要重写整个列表 |
model |
模型覆盖,"inherit" 继承父级 |
低优先级子任务可用更轻量的模型 |
maxTurns |
最大 API 往返轮次 | 防止子 Agent 陷入无限工具调用循环 |
isolation |
"worktree" 或 "remote",文件系统隔离策略 |
多 Agent 并行修改同一仓库时防冲突 |
background |
强制后台异步运行 | 长耗时任务不阻塞主对话 |
permissionMode |
权限模式(详见下节) | 控制操作自动化程度 |
mcpServers |
专属 MCP 服务器配置 | 给子 Agent 单独挂载数据库、外部 API 等工具 |
omitClaudeMd |
跳过 CLAUDE.md 加载 | 对不需要项目规范的分析型 Agent 减少噪声 |
三、权限模式(PermissionMode)
权限模式决定了子 Agent 在执行危险操作时的行为:是暂停等待用户确认,还是自动执行?
types/permissions.ts
3.1 五种外部权限模式
default:遇到写文件、执行命令等破坏性操作时,暂停并弹出确认提示。最保守,适合不完全信任的场景。
acceptEdits:文件读写类操作(Read / Write / Edit)自动通过,Shell 命令等仍需确认。适合已明确授权文件修改的 Agent,同时对命令执行保留最后一道审查。
bypassPermissions:所有操作不经确认直接执行。只应在隔离环境(CI 容器、独立 Worktree)中使用,确保即使出错也不影响生产环境。
dontAsk:需要确认的操作静默拒绝,不弹提示。适合只读型分析 Agent,明确防止任何写操作。
plan:Agent 只能调用分析类工具,无法执行任何文件写入或命令。用于需要"先看方案、人工审批再执行"的场景。
3.2 内部权限模式
bubble:权限提示冒泡到父级终端显示,而不是在子 Agent 上下文里处理。Fork Subagent 使用此模式------用户在主界面确认,权限结果透传给后台运行的子 Agent。
auto:自动分类器模式,由系统根据上下文动态判断,内部使用。
四、工具权限:三层收窄机制
子 Agent 的最终可用工具集通过三层过滤确定,每层只能收窄不能扩展:
4.1 第一层:所有 Agent 的全局禁止清单
无论是哪类子 Agent,以下工具一律不可用:
typescript
// constants/tools.ts
ALL_AGENT_DISALLOWED_TOOLS = new Set([
ASK_USER_QUESTION_TOOL_NAME, // 子 Agent 不能直接和用户交互
TASK_OUTPUT_TOOL_NAME, // 只有主线程能输出任务结果
EXIT_PLAN_MODE_V2_TOOL_NAME, // 模式切换只能在主线程
ENTER_PLAN_MODE_TOOL_NAME,
TASK_STOP_TOOL_NAME, // 任务停止权限收归上层
// 防止无限嵌套(ant 内部用户除外)
...(process.env.USER_TYPE === 'ant' ? [] : [AGENT_TOOL_NAME]),
])
禁止 AskUserQuestion 的工程原因:子 Agent 若能直接向用户提问,会导致多个并发 Agent 同时请求用户输入,UX 混乱且无法保证对话一致性。所有用户交互必须经过主 Agent 中转。
禁止 AgentTool 的工程原因 :子 Agent spawn 孙 Agent 会导致递归嵌套,深度无界,token 消耗和状态管理复杂度指数级上升。isInForkChild() 做了一层检测保护,此禁止清单是更硬的防线。
4.2 第二层:自定义 Agent 的额外限制
用户定义的 Agent 还额外禁止访问协调层专属工具(如 SendMessage、SyntheticOutput),这些工具只向 Coordinator 模式开放。
4.3 第三层:异步 Agent 的正向白名单
后台异步 Agent 不用黑名单(禁止某些工具),改用正向白名单(只允许指定工具):
typescript
ASYNC_AGENT_ALLOWED_TOOLS = new Set([
FILE_READ_TOOL_NAME, FILE_EDIT_TOOL_NAME, FILE_WRITE_TOOL_NAME,
BASH_TOOL_NAME, GREP_TOOL_NAME, GLOB_TOOL_NAME,
WEB_SEARCH_TOOL_NAME, WEB_FETCH_TOOL_NAME,
TODO_WRITE_TOOL_NAME, NOTEBOOK_EDIT_TOOL_NAME,
SKILL_TOOL_NAME, TOOL_SEARCH_TOOL_NAME,
ENTER_WORKTREE_TOOL_NAME, EXIT_WORKTREE_TOOL_NAME,
])
异步 Agent 在后台运行时用户无法实时干预,所以采用白名单策略而不是黑名单,把"可能越权"的风险面收得更小。
4.4 resolveAgentTools() 执行逻辑
tools/AgentTool/agentToolUtils.ts:122
最终工具集由 resolveAgentTools() 计算,逻辑如下:
- 根据
isAsync和isBuiltIn对父级工具集做基础过滤 - 处理
tools: ['*'](通配符 = 过滤后的全集) - 从结果中移除
disallowedTools里的工具 - 返回最终工具集 + 该 Agent 允许 spawn 的 Agent 类型列表
五、AgentTool 执行链路
从主模型调用 Agent(subagent_type, prompt) 到子 Agent 开始执行,代码共经历 6 个阶段。先看整体流程图,再逐段解释每步在做什么:
是 Fork 路径
否 普通路径
是
否
是
否
同步前台
异步后台
主模型调用 AgentTool
subagent_type 是否为空
且 Fork 特性开启?
已在 Fork 子 Agent 内?
从 activeAgents 查找 selectedAgent
抛出错误
禁止递归 Fork
selectedAgent = FORK_AGENT
继承父级系统提示和消息历史
等待必要 MCP 服务器就绪
effectiveIsolation
= worktree?
createAgentWorktree
在仓库旁创建独立 Git 分支
使用当前工作目录
组装 promptMessages
shouldRunAsync?
runAgent 阻塞执行
等待完成后返回结果
registerAsyncAgent 注册任务
立即返回占位响应
runAsyncAgentLifecycle 在后台运行
cleanupWorktreeIfNeeded
无变更则删除 Worktree
5.1 六个阶段逐步解析
阶段①:判断走 Fork 路径还是普通路径
主模型调用 Agent() 时可以指定 subagent_type,也可以不指定。
- 不指定 + Fork 特性开启:走 Fork 路径,子 Agent 会完整继承父 Agent 的对话历史和系统提示(详见第七节)
- 指定了 subagent_type:走普通路径,按名字查找对应的 Agent 定义
Fork 路径还有一道递归保护:如果当前已经运行在一个 Fork 子 Agent 内,再次 Fork 会直接抛出错误,防止无限嵌套。
阶段②:查找 Agent 定义(普通路径)
从 activeAgents 列表里按名字找到 selectedAgent,也就是第二节介绍的那份 Agent 定义(工具集、权限模式、模型等全在里面)。找不到直接报错,并列出当前可用的 Agent 类型。
如果 Agent 定义里声明了 requiredMcpServers,系统会轮询等待这些 MCP 服务器连接并认证成功(最多 30 秒),再继续。
阶段③:决定文件系统隔离方式
根据 isolation 参数(或 Agent 定义里的 isolation 字段)决定:
worktree:调用createAgentWorktree()在主仓库旁边创建一个独立 Git 分支副本,子 Agent 在里面操作,不影响主工作目录- 无隔离:子 Agent 直接在当前工作目录操作
阶段④:组装 promptMessages(初始消息)
这是 Fork 路径和普通路径差异最大的地方:
| 路径 | 系统提示 | 初始消息内容 |
|---|---|---|
| 普通路径 | Agent 自己的系统提示(来自 Markdown 文件) | 仅用户传入的 prompt |
| Fork 路径 | 完全复用父 Agent 的系统提示(保证 Cache Key 一致) | 父级完整对话历史 + 本次任务指令 |
普通路径的系统提示组装顺序:
[CLI 前缀 + 权限声明]
[CLAUDE.md 项目规范(omitClaudeMd=true 时跳过)]
[Agent 自身的 systemPrompt(Markdown 正文)]
[当前 CWD、时间戳等运行时上下文]
阶段⑤:判断同步还是异步执行
shouldRunAsync 为 true 的条件,满足任意一个即走异步:
| 条件 | 说明 |
|---|---|
run_in_background === true |
调用时显式指定后台运行 |
selectedAgent.background === true |
Agent 定义里强制后台 |
| 当前是 Coordinator 模式 | Coordinator 派出的 Worker 全部异步 |
Fork 特性开启(forceAsync) |
统一走异步任务模型 |
| KAIROS 助手模式开启 | 防止串行子 Agent 堵塞输入队列 |
同步执行 :runAgent() 一直跑到结束,主对话循环等着,结果出来才能继续。
异步执行 :registerAsyncAgent() 先注册一个任务 ID 并立即返回占位响应,然后 runAsyncAgentLifecycle() 在后台独立运行,通过 <task-notification> 向主 Agent 推送进度。
阶段⑥:运行结束后清理 Worktree
cleanupWorktreeIfNeeded() 检查 Worktree 是否有实质性变更:
- 无变更 → 自动删除,不留残留
- 有变更 → 保留并返回路径,由主 Agent 或用户决定如何处理
5.2 状态隔离:createSubagentContext()
子 Agent 的系统提示不是直接使用 Markdown 正文,而是经过分层组装:
[系统提示前缀(CLI 版本、权限声明等)]
[CLAUDE.md 内容(omitClaudeMd=true 时跳过)]
[Agent 自身的 systemPrompt]
[运行时上下文(CWD、时间戳等)]
这样设计的意义:子 Agent 既能了解项目级规范(CLAUDE.md),又有明确的专业方向(自身 systemPrompt),不需要在每个 Agent 文件里重复项目规范。
5.2 状态隔离:createSubagentContext()
子 Agent 通过 createSubagentContext() 创建完全隔离的 ToolUseContext:
readFileState:从父级克隆,不共享写状态(防止并发文件读取冲突)abortController:新建子控制器,父级中止可传播到子级,反向不传播setAppState:默认 no-op,子 Agent 的状态变更不回写父级shouldAvoidPermissionPrompts:强制true,后台 Agent 不弹权限提示框addNotification/setToolJSX:undefined,子 Agent 不控制主界面 UI
关于状态隔离的深入实现,详见第七章。
六、异步 Agent 生命周期
6.1 触发条件
agentDefinition.background === true(定义层强制后台)- 特性开关
FORK_SUBAGENT开启时,所有 Agent 默认异步
6.2 runAsyncAgentLifecycle() 执行阶段
tools/AgentTool/agentToolUtils.ts:508
阶段一:立即返回
AgentTool 调用后,主 Agent 的 query 循环不阻塞,立刻得到一个占位响应,任务在后台启动。
阶段二:进度追踪
子 Agent 每产生一条消息,进度追踪器更新 UI,以 <task-notification> 格式推送:
xml
<task-notification>
<task_id>agent-a1b2c3d4</task_id>
<status>in_progress</status>
<summary>已完成 12/45 个文件的类型迁移</summary>
</task-notification>
主 Agent 在后续对话轮次中可以读取这些通知,了解各子 Agent 状态,决定是否追加指令。
阶段三:结果收集与安全分类
任务完成后,如果特性 TRANSCRIPT_CLASSIFIER 开启,系统对子 Agent 的完整转录做安全分类检查,确认没有越权行为,再把结果提交给主 Agent。
阶段四:通知推送
typescript
enqueueAgentNotification({
taskId,
status: 'completed' | 'failed' | 'aborted',
finalMessage,
usage: { totalTokens, toolUses, durationMs },
})
主界面弹出完成通知,用户可选择查看完整的子 Agent 对话记录。
七、Fork Subagent:并发分支与 Cache 共享
7.1 Fork 的特殊性
tools/AgentTool/forkSubagent.ts
普通子 Agent 以空白上下文启动(只有系统提示和任务描述)。Fork 子 Agent 继承父 Agent 的完整对话历史,相当于从当前对话状态克隆出一个并行分支。
typescript
// forkSubagent.ts:60-71
export const FORK_AGENT: BuiltInAgentDefinition = {
agentType: 'fork',
tools: ['*'], // 工具集与父级完全一致------这是 cache 共享的必要条件
maxTurns: 200,
model: 'inherit',
permissionMode: 'bubble', // 权限提示冒泡到父级终端
getSystemPrompt: () => '', // 不注入额外系统提示,完全复用父级
}
tools: ['*'] 不是随意的设计------工具列表是 Anthropic API Prompt Cache Key 的组成要素之一。如果子 Agent 工具集与父级不同,请求前缀就会不同,缓存无法命中。
7.2 并发 Fork 的 Cache 共享策略
多个并发 Fork 的 API 请求消息结构经过精心设计,使前缀最大化重合:
消息列表结构(buildForkedMessages 构建):
[父级全部历史消息] ← 所有 fork 完全相同
[父级最后一条 assistant message] ← 所有 fork 完全相同(含所有 tool_use blocks)
[user message]:
tool_result: FORK_PLACEHOLDER ← 所有 fork 相同(固定占位文本)
tool_result: FORK_PLACEHOLDER ← 所有 fork 相同
text: "<fork_directive>具体指令" ← 仅此处各 fork 不同
差异被推到消息列表的最末尾,使得 N 个并发 Fork 共享 1 份 Prompt Cache,其余 N-1 次全部 cache hit,并行任务的 token 开销接近单次调用。
7.3 递归保护与行为约束
防递归 :isInForkChild() 检测消息历史中是否含有 Fork boilerplate 标记,若已在 Fork 子 Agent 内再次调用 AgentTool,系统直接拒绝,防止无界嵌套。
行为约束:Fork 消息里注入强约束 boilerplate,明确禁止子 Agent:
- 再次 spawn 子 Agent
- 产生无关的对话性输出
- 在工具调用之间插入文字(所有中间步骤应静默)
- 输出超过 500 词的报告
这些约束把 Fork 子 Agent 锁定在"纯执行"模式,防止它退化为一个普通的对话 Agent。
八、Worktree 隔离
8.1 解决的问题
多个子 Agent 并发修改同一仓库时,存在两类冲突风险:
- 文件冲突:Agent A 和 Agent B 同时修改同一个文件,互相覆盖
- 环境污染:Agent A 修改了依赖配置,导致 Agent B 的测试用例跑出错误结论
isolation: 'worktree' 为每个子 Agent 在仓库旁创建独立的 Git Worktree,文件操作完全隔离。
8.2 物理结构
my-project/ ← 主工作目录(主 Agent 和无隔离的子 Agent)
my-project-worktrees/
├── agent-a1b2c3d4/ ← 子 Agent A 的独立副本
│ └── (完整代码树,独立 Git 分支)
└── agent-e5f6g7h8/ ← 子 Agent B 的独立副本
└── (完整代码树,独立 Git 分支)
Worktree 使用 git worktree add 创建,与主仓库共享 .git 对象数据库(不复制 pack 文件),创建速度快,不占用额外存储空间存储历史对象。
8.3 生命周期管理
tools/AgentTool/AgentTool.tsx:590-685
子 Agent 结束后,系统通过 hasWorktreeChanges(worktreePath, headCommit) 检测是否有实质性变更:
| 状态 | 处理方式 |
|---|---|
| 无变更(Agent 无实质输出) | removeAgentWorktree() 自动删除,不留残留 |
| 有变更 | 保留 Worktree,返回 { worktreePath, worktreeBranch } |
主 Agent 拿到变更后的 Worktree 路径,可以继续决定:审查 diff、merge 进主分支,或丢弃。这个决策权留给主 Agent(或用户),Worktree 机制本身不做自动 merge。
九、Coordinator 模式
9.1 设计意图
Coordinator 是 Claude Code 多 Agent 架构里的"调度层":主 Agent 进入 Coordinator 模式后,不再直接执行任何文件操作或命令,专注于任务分解、Worker 调度和结果整合。
这是关注点分离的体现:让执行层(Worker)和调度层(Coordinator)承担不同职责,避免主 Agent 既要记跟踪全局进度,又要亲自改代码导致注意力分散。
9.2 工具集限制
Coordinator 只有 4 个工具可用:
typescript
// coordinatorMode.ts
const COORDINATOR_MODE_ALLOWED_TOOLS = new Set([
AGENT_TOOL_NAME, // spawn Worker Agent,派发任务
TASK_STOP_TOOL_NAME, // 终止指定 Worker
SEND_MESSAGE_TOOL_NAME, // 向运行中的 Worker 发追加指令
SYNTHETIC_OUTPUT_TOOL_NAME, // 整合最终结果输出
])
没有 Read、Write、Bash------Coordinator 自身不接触文件系统。这个约束强迫它保持调度角色,所有"脏活"委托给 Worker。
9.3 Coordinator ↔ Worker 交互流程
AgentTool spawn
AgentTool spawn
AgentTool spawn
task-notification 上报进度
task-notification 上报进度
task-notification 上报进度
SendMessage 追加约束
TaskStop 终止出错 Worker
SyntheticOutput
Coordinator
Worker A
负责 src/auth/
Worker B
负责 src/api/
Worker C
负责测试
汇总结果 → 用户
关键机制:
Worker 不直接与其他 Worker 通信,所有协调通过 Coordinator 中转。Coordinator 通过 SendMessage({ to: taskId, content: "..." }) 追加指令,通过 TaskStop({ task_id }) 中止行为异常的 Worker。
这种"星型拓扑"(所有 Worker 连接 Coordinator,Worker 间不直连)简化了状态管理,Coordinator 始终持有全局一致的任务状态视图。
十、整体架构总览
worktree
无隔离
同步前台
异步后台
Fork 分支
用户复杂任务
AgentTool 入口
加载 Agent 定义
三种来源
resolveAgentTools
三层权限收窄
隔离策略
createAgentWorktree
独立 Git 分支
当前工作目录
执行模式
runAgent
阻塞执行
runAsyncAgentLifecycle
进度通知
buildForkedMessages
Cache 共享
query 循环
createSubagentContext 隔离
recordSidechainTranscript
独立转录
Coordinator 汇总
或直接返回主 Agent
十一、设计原则总结
| 原则 | 实现机制 | 工程价值 |
|---|---|---|
| 最小权限 | 三层工具禁止清单 + 正向白名单 | 限制误操作影响面,故障隔离 |
| 状态隔离 | createSubagentContext() 克隆所有可变状态 |
并发安全,防止主循环状态被污染 |
| 文件隔离 | Git Worktree 独立分支 | 多 Agent 并发修改不冲突 |
| Cache 最大化 | Fork 消息结构设计,差异推到列表末尾 | N 个并发 Fork 只建 1 份 Cache |
| 调度与执行分离 | Coordinator 限制 4 工具,不接触文件系统 | 调度层保持清晰的全局视图 |
| 人工可干预 | 禁止 AskUserQuestion 直连用户,权限 bubble 到父级 |
用户始终通过主 Agent 保持控制权 |
| 自动清理 | Worktree 无变更时自动删除 | 不留残留,保持工作目录整洁 |