第六章:Claude Code CLI 多 Agent 协作——AgentTool、Coordinator 与 Worktree 隔离
- 第六章:Claude Code CLI 多 Agent 协作——AgentTool、Coordinator 与 Worktree 隔离
-
- 一、为什么需要多 Agent?
- 二、Agent 定义体系
- 三、权限模式(PermissionMode)
- 四、工具权限:三层收窄机制
- 五、AgentTool 执行链路
- 六、异步 Agent 生命周期
- 七、Fork Subagent:并发分支与 Cache 共享
- 八、Worktree 隔离
- 九、Coordinator 模式
- 十、整体架构总览
- 十一、设计原则总结
单 Agent 架构有三个硬性瓶颈:
上下文窗口上限:大型项目的代码、历史对话、工具调用结果累积到一定量就会触发 compact(见第五章)。子 Agent 各自维护独立的上下文窗口,互不干扰。
顺序执行效率:单 Agent 在同一对话里处理前端重构、后端接口、测试补充,只能串行进行。多 Agent 并发执行,任务完成时间从 O(n) 压缩到接近 O(1)。
权限边界模糊:单 Agent 拥有所有工具权限,误操作的影响面无法控制。多 Agent 架构下,每个子 Agent 的工具集精确限定到任务所需的最小集合。
Claude Code 的解法是分层 Agent 架构:主 Agent 通过 AgentTool 工具派生专门化子 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,正文是系统提示:
---
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 减少噪声
权限模式决定了子 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,以下工具一律不可用:
// 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 不用黑名单(禁止某些工具),改用正向白名单(只允许指定工具):
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 类型列表
从主模型调用 Agent(subagent_type, prompt) 到子 Agent 开始执行,代码共经历 6 个阶段。先看整体流程图,再逐段解释每步在做什么:
是 Fork 路径
否 普通路径
是
否
是
否
同步前台
异步后台
主模型调用 AgentTool
subagent_type 是否为空
无变更则删除 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 路径和普通路径差异最大的地方:
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() 在后台独立运行,通过
向主 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
关于状态隔离的深入实现,详见第七章。
6.1 触发条件
agentDefinition.background === true(定义层强制后台)- 特性开关
FORK_SUBAGENT开启时,所有 Agent 默认异步
6.2 runAsyncAgentLifecycle() 执行阶段
tools/AgentTool/agentToolUtils.ts:508
阶段一:立即返回
AgentTool 调用后,主 Agent 的 query 循环不阻塞,立刻得到一个占位响应,任务在后台启动。
阶段二:进度追踪
子 Agent 每产生一条消息,进度追踪器更新 UI,以
格式推送:
agent-a1b2c3d4
in_progress
已完成 12/45 个文件的类型迁移
主 Agent 在后续对话轮次中可以读取这些通知,了解各子 Agent 状态,决定是否追加指令。
阶段三:结果收集与安全分类
任务完成后,如果特性 TRANSCRIPT_CLASSIFIER 开启,系统对子 Agent 的完整转录做安全分类检查,确认没有越权行为,再把结果提交给主 Agent。
阶段四:通知推送
enqueueAgentNotification({ taskId, status: 'completed' | 'failed' | 'aborted', finalMessage, usage: { totalTokens, toolUses, durationMs }, })
主界面弹出完成通知,用户可选择查看完整的子 Agent 对话记录。
7.1 Fork 的特殊性
tools/AgentTool/forkSubagent.ts
普通子 Agent 以空白上下文启动(只有系统提示和任务描述)。Fork 子 Agent 继承父 Agent 的完整对话历史,相当于从当前对话状态克隆出一个并行分支。
// forkSubagent.ts:60-71 export const FORK_AGENT: BuiltInAgentDefinition =
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 不同
差异被推到消息列表的最末尾,使得 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。
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) 检测是否有实质性变更:
removeAgentWorktree() 自动删除,不留残留 有变更 保留 Worktree,返回
{ worktreePath, worktreeBranch }
主 Agent 拿到变更后的 Worktree 路径,可以继续决定:审查 diff、merge 进主分支,或丢弃。这个决策权留给主 Agent(或用户),Worktree 机制本身不做自动 merge。
9.1 设计意图
Coordinator 是 Claude Code 多 Agent 架构里的"调度层":主 Agent 进入 Coordinator 模式后,不再直接执行任何文件操作或命令,专注于任务分解、Worker 调度和结果整合。
这是关注点分离的体现:让执行层(Worker)和调度层(Coordinator)承担不同职责,避免主 Agent 既要记跟踪全局进度,又要亲自改代码导致注意力分散。
9.2 工具集限制
Coordinator 只有 4 个工具可用:
// 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 交互流程
关键机制:
Worker 不直接与其他 Worker 通信,所有协调通过 Coordinator 中转。Coordinator 通过 SendMessage({ to: taskId, content: "…" }) 追加指令,通过 TaskStop({ task_id }) 中止行为异常的 Worker。
这种"星型拓扑"(所有 Worker 连接 Coordinator,Worker 间不直连)简化了状态管理,Coordinator 始终持有全局一致的任务状态视图。
或直接返回主 Agent
createSubagentContext() 克隆所有可变状态 并发安全,防止主循环状态被污染
文件隔离 Git Worktree 独立分支 多 Agent 并发修改不冲突
Cache 最大化 Fork 消息结构设计,差异推到列表末尾 N 个并发 Fork 只建 1 份 Cache
调度与执行分离 Coordinator 限制 4 工具,不接触文件系统 调度层保持清晰的全局视图
人工可干预 禁止
AskUserQuestion 直连用户,权限 bubble 到父级 用户始终通过主 Agent 保持控制权
自动清理 Worktree 无变更时自动删除 不留残留,保持工作目录整洁
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/264297.html