源码位置:
src/tools/AgentTool/、src/utils/swarm/、src/tasks/InProcessTeammateTask/
Claude Code 支持三种不同的多代理模式:
模式 1:普通 Subagent(指定类型)
Agent({ subagent_type: ‘general-purpose’, prompt: ‘…’ }) → 创建预定义类型的子代理,独立运行
模式 2:Fork Subagent(继承上下文) Agent({ prompt: ‘…’ }) // 不指定 subagent_type → Fork 当前会话,子代理继承父代理的完整上下文
模式 3:Agent Teams(团队协作) TeamCreate → 创建团队 → 多个 in-process 队友并发运行 → 通过 mailbox 通信和权限同步
5.2.1 入口
源码位置:src/tools/AgentTool/runAgent.ts:248
// 注意:是 async generator(function*),不是普通 async function // 逐条 yield Message,调用方以 for await...of 消费 export async function* runAgent({ agentDefinition, promptMessages, // 初始消息(替代旧的 directive 字符串) toolUseContext, canUseTool, // 工具权限检查函数(子代理独立实例) isAsync, // 是否后台异步运行 forkContextMessages, querySource, override, // { systemPrompt?, userContext?, agentId?, abortController? } model, maxTurns, availableTools, // 调用方预组装的工具池 allowedTools, // 显式工具白名单(替换所有父代理 allow 规则) useExactTools, // Fork 专用:跳过 resolveAgentTools(),直接用 availableTools worktreePath, ... }: { ... }): AsyncGenerator
为什么是 async generator? 子代理的 query() 也是 async generator,runAgent 直接 yield 转发其事件,使调用方能实时观察子代理进度(用于 TaskOutput 更新),无需等待子代理完成。
5.2.2 代理定义(AgentDefinition)
源码位置:src/tools/AgentTool/loadAgentsDir.ts:106
代理定义有三种来源,共用 BaseAgentDefinition 基础字段:
// 所有代理共用的基础字段 type BaseAgentDefinition = { agentType: string whenToUse: string tools?: string[] // 工具白名单('*' = 父代理全部工具) disallowedTools?: string[] model?: string // 'inherit' = 继承父代理模型 permissionMode?: PermissionMode maxTurns?: number skills?: string[] // 预加载的 skill 名称列表 mcpServers?: AgentMcpServerSpec[] // 代理专属 MCP 服务器 hooks?: HooksSettings isolation?: 'worktree' | 'remote' // 隔离模式:git worktree 或远程 background?: boolean // 总是以后台任务方式 spawn memory?: 'user' | 'project' | 'local' // 持久记忆 scope initialPrompt?: string // 预置在第一个 user turn 前的提示 omitClaudeMd?: boolean // 省略 CLAUDE.md(Explore/Plan 优化,节省 token) requiredMcpServers?: string[] // 必须存在的 MCP 服务器(否则不可用) criticalSystemReminder_EXPERIMENTAL?: string // 每轮注入的关键提醒 } // 内置代理(源码定义) type BuiltInAgentDefinition = BaseAgentDefinition & ) => string } // 自定义代理(从 .claude/agents/*.md 加载) type CustomAgentDefinition = BaseAgentDefinition & // 插件代理(从插件系统加载) type PluginAgentDefinition = BaseAgentDefinition & // 联合类型 type AgentDefinition = BuiltInAgentDefinition | CustomAgentDefinition | PluginAgentDefinition
代理优先级与覆盖顺序(src/tools/AgentTool/loadAgentsDir.ts:196):
built-in → plugin → userSettings → projectSettings → flagSettings → policySettings
相同 agentType 时,后者覆盖前者。这意味着 managed(policySettings)代理可以覆盖所有用户自定义代理,plugin 代理可以覆盖 built-in。
omitClaudeMd 设计背景:Explore、Plan 等只读代理不需要提交/PR/lint 规范(来自 CLAUDE.md),主代理会解读它们的输出。关闭后每次 spawn 节省 5-15 Gtok,跨 3400万+ Explore spawn 有显著效果。
5.2.3 AsyncLocalStorage 上下文隔离
子代理通过 AsyncLocalStorage 实现进程内隔离:
// src/utils/agentContext.ts type SubagentContext = { agentId: string parentAgentId?: string // ... } // 子代理运行时,所有在此 async 调用链内的代码 // 都能通过 getAgentContext() 读到正确的 agentId function runWithAgentContext
( context: AgentContext, fn: () => Promise
): Promise
这意味着:
- 子代理读取
getSessionId()会得到自己的 ID - 子代理的 Todo 列表与父代理隔离
- 子代理的工具调用权限检查使用子代理自己的上下文
这是最精妙的设计之一,也是 Prompt Cache 优化的关键。
5.3.1 什么是 Fork?
源码位置:src/tools/AgentTool/forkSubagent.ts:32
当 subagent_type 省略时,且 isForkSubagentEnabled() 返回 true 时触发 Fork 模式。子代理继承父代理的:
- 完整对话历史(字节级相同的消息前缀)
- 系统提示(通过
override.systemPrompt传递已渲染字节,不重新构建) - 工具列表(
tools: ['*']+useExactTools: true,跳过resolveAgentTools()) - 权限模式(
permissionMode: 'bubble')
isForkSubagentEnabled() 的三个条件(需同时满足):
feature('FORK_SUBAGENT')编译时 gate 开启!isCoordinatorMode()--- 与 Coordinator 模式互斥(coordinator 有自己的编排模型)!getIsNonInteractiveSession()--- 仅在交互式会话中启用
5.3.2 FORK_AGENT 定义
源码位置:src/tools/AgentTool/forkSubagent.ts:60
export const FORK_AGENT =
为什么 getSystemPrompt 返回空字符串?
因为 Fork 子代理通过 override.systemPrompt 传递父代理已渲染 的系统提示字节。重新调用 getSystemPrompt() 可能因 GrowthBook(功能开关服务)状态变化而产生不同结果,破坏 Prompt Cache。
5.3.3 buildForkedMessages():Prompt Cache 最大化
源码位置:src/tools/AgentTool/forkSubagent.ts:107
export function buildForkedMessages( directive: string, assistantMessage: AssistantMessage, ): MessageType[] // 只返回 2 条新消息!不含父代理历史(历史已在上下文中)
关键设计:函数只返回追加到父代理历史末尾的 2 条消息:
// Step 1:克隆父代理 assistant 消息(含所有 tool_use 块,包括 thinking 和 text) const fullAssistantMessage = { ...assistantMessage, uuid: randomUUID(), // 新 UUID(避免与父代理冲突) message: { ...assistantMessage.message, content: [...assistantMessage.message.content] }, } // Step 2:为每个 tool_use 块创建相同占位符 tool_result const FORK_PLACEHOLDER_RESULT = 'Fork started --- processing in background' const toolResultBlocks = toolUseBlocks.map(block => ({ type: 'tool_result', tool_use_id: block.id, content: [{ type: 'text', text: FORK_PLACEHOLDER_RESULT }], // 所有子代理占位符文本完全相同 → prompt cache 命中 })) // Step 3:构建最终用户消息([...tool_results, 指令文本]) const toolResultMessage = createUserMessage({ content: [...toolResultBlocks, { type: 'text', text: buildChildMessage(directive) }], }) return [fullAssistantMessage, toolResultMessage] // 只有 2 条 // 完整 API 序列 = parentHistory + [fullAssistantMessage, toolResultMessage]
边界情况 :若 assistantMessage 中没有 tool_use 块(不应发生),直接返回单条包含指令文本的 user message。
5.3.4 buildChildMessage():Fork 工作者指令
源码位置:src/tools/AgentTool/forkSubagent.ts:171
buildChildMessage() 生成 fork 子代理的操作规则,包裹在
标签内:
STOP. READ THIS FIRST. 你是一个 forked worker process,你不是主代理。 规则(不可违反): 1. 系统提示说"默认 fork"------忽略它,你已经是 fork,不要再 spawn 子代理 2. 不要闲聊、问问题、或建议后续步骤 3. 直接使用工具:Bash、Read、Write 等 4. 修改文件后提交,报告中包含 commit hash 5. 不在工具调用之间输出文本,最后统一报告 6. 严格在指令 scope 内工作 7. 报告不超过 500 字 输出格式(纯文本标签,不用 markdown 标题): Scope:
<你的工作范围,一句话>
Result:
<关键发现 答案="">
Key files:
<相关文件路径>
Files changed:
<修改的文件 +="" commit="" hash="">
Issues:
<发现的问题(仅有问题时列出)>
发现的问题(仅有问题时列出)>
修改的文件>
相关文件路径>
关键发现>
你的工作范围,一句话>
{FORK_DIRECTIVE_PREFIX}{directive}
这段强制输出格式的设计防止了 fork 子代理将父代理的系统提示("默认 fork 以并行执行")错误地应用到自身。
5.3.5 防止递归 Fork
源码位置:src/tools/AgentTool/forkSubagent.ts:78
// FORK_BOILERPLATE_TAG 从 constants/xml.ts 导入,值为 'fork-boilerplate' import { FORK_BOILERPLATE_TAG } from '../../constants/xml.js' export function isInForkChild(messages: MessageType[]): boolean >`) ) }) }
为什么 Fork 子代理仍保留 Agent 工具? 为保证 API 请求前缀中工具定义字节完全一致(prompt cache 需要)。但在调用时通过 isInForkChild() 拒绝递归 fork,而不是从工具池中移除 Agent 工具。
5.3.6 Worktree 隔离的 Fork
源码位置:src/tools/AgentTool/forkSubagent.ts:205
当代理定义中有 isolation: 'worktree' 时,fork 子代理在独立的 git worktree 中运行。此时会在指令中注入 buildWorktreeNotice(),告知子代理:
你继承了工作在 {parentCwd} 的父代理的对话上下文。 你在 {worktreeCwd} 的隔离 git worktree 中运行------ 同一仓库、相同相对文件结构、独立 working copy。 上下文中的路径指向父代理目录;请翻译到你的 worktree 根目录。 如果父代理可能修改了文件,请在编辑前重新读取。 你的修改仅在此 worktree 中,不影响父代理文件。
5.4.1 架构概览
Leader(主代理) │ ├─ TeamCreate({ teammates: ['worker-a', 'worker-b'] }) │ ├─ worker-a 在 in-process 中运行(InProcessTeammateTask) │ ├─ 独立的 AgentContext(AsyncLocalStorage) │ ├─ 独立的消息历史 │ └─ 通过 mailbox 与 leader 通信 │ └─ worker-b 在 in-process 中运行(InProcessTeammateTask) ├─ 独立的 AgentContext ├─ 独立的消息历史 └─ 通过 mailbox 与 leader 通信
5.4.2 In-process Runner(核心)
源码位置:src/utils/swarm/inProcessRunner.ts
// 注意:上下文是 runWithTeammateContext(不是 runWithAgentContext)
// TeammateContext 比 AgentContext 多了 teamName、mailbox、permissionMode 等字段
async function runInProcessTeammate( identity: TeammateIdentity, toolUseContext: ToolUseContext, ) { // 1. 克隆文件状态缓存(隔离文件读写状态,防止并发队友互相影响) cloneFileStateCache()
// 2. 为队友创建独立的 AbortController(2 个!) const abortController = createAbortController() // kill 整个队友 const currentWorkAbortController = createAbortController() // 仅 abort 当前轮次
// 3. 创建队友专用的 canUseTool 函数(权限路由,见下文) const canUseTool = createInProcessCanUseTool(identity, abortController)
// 4. 在独立的 TeammateContext 中运行(AsyncLocalStorage 隔离) return runWithTeammateContext(teammateContext, async () => {
// 5. 启动队友的 Agent Loop(runAgent 是 async generator) for await (const message of runAgent({ agentDefinition: teammate, ... })) { // 更新 AppState、写 sidechain 转录、检查 mailbox 消息等 }
}) }
5.4.3 Mailbox 通信机制
队友之间(包括与 leader)通过 "邮箱"(mailbox)进行异步通信:
// src/utils/teammateMailbox.ts
readMailbox(teammateId: string): Message[] // 读取收到的消息 writeToMailbox(teammateId: string, msg): void // 发送消息
// 消息类型识别 isPermissionResponse(msg): boolean // 权限批准/拒绝 isShutdownRequest(msg): boolean // 关闭请求 createIdleNotification(): Message // 队友完成通知
5.4.4 权限同步(Permission Sync)
源码位置:src/utils/swarm/inProcessRunner.ts:128
当队友的工具调用需要用户确认(behavior === 'ask')时,走以下路由:
Worker 调用工具
│ ├─ hasPermissionsToUseTool() 返回 ‘allow’/‘deny’ → 直接通过/拒绝 │ └─ 返回 ‘ask’(需要用户确认)
│ ├─【主路径】setToolUseConfirmQueue 可用(leader 的 TUI 在线) │ → 将权限请求推入 leader 的 ToolUseConfirm 队列 │ → 显示带 worker badge(名称 + 颜色)的确认对话框 │ → 等待用户操作,与 leader 自己的工具确认使用相同 UI │ └─【备用路径】TUI bridge 不可用(headless / 后台模式) → 通过 mailbox 向 leader 发送权限请求 → 以 500ms 间隔轮询(PERMISSION_POLL_INTERVAL_MS) → 等待 leader 响应后继续
Bash 命令的预处理 :在发送给 leader 前,先用 classifier(BASH_CLASSIFIER feature gate)自动审批,若分类器批准则跳过 leader 确认。这与 leader 的处理不同(leader 是竞速,worker 是串行等待)。
// leaderPermissionBridge.ts 提供的关键函数
getLeaderToolUseConfirmQueue() // 获取 leader 的 ToolUseConfirm 状态设置函数 getLeaderSetToolPermissionContext() // 获取 leader 的权限上下文
源码位置:src/coordinator/coordinatorMode.ts
Coordinator 模式专用于长时间自主运行的任务。通过环境变量开启,与 Fork Subagent 互斥。
// 判断当前是否处于 coordinator 模式
export function isCoordinatorMode(): boolean return false }
// 恢复会话时,自动匹配会话存储的模式(必要时翻转 env var) export function matchSessionMode(sessionMode: ‘coordinator’ | ‘normal’ | undefined): string | undefined
// 向 coordinator 注入 worker 的可用工具列表(coordinator 据此决定分配什么能力给 worker) export function getCoordinatorUserContext(scratchpadDir?: string): string
注意 :源码中没有 getCoordinatorSystemPrompt() 函数。Coordinator 的系统提示来自 buildEffectiveSystemPrompt() 中的标准路径(coordinator 模式通过 userContext 注入工具列表,不是独立的 prompt 函数)。
与 Fork 的互斥关系:
// forkSubagent.ts
export function isForkSubagentEnabled(): boolean }
Worker 工具集限制 (src/constants/tools.ts):
// ASYNC_AGENT_ALLOWED_TOOLS 限制 worker 的工具池
// 不包含:TeamCreate/TeamDelete(高级编排工具)、SendMessage、SyntheticOutput const INTERNAL_WORKER_TOOLS = new Set([ TEAM_CREATE_TOOL_NAME, TEAM_DELETE_TOOL_NAME, SEND_MESSAGE_TOOL_NAME, SYNTHETIC_OUTPUT_TOOL_NAME, ]) // Worker = ASYNC_AGENT_ALLOWED_TOOLS - INTERNAL_WORKER_TOOLS
时间 →
父代理历史消息(共享 Prompt Cache): ┌──────────────────────────────────────────┐ │ user: "完成以下三个任务…" │ │ assistant: [tool_use: agent(task1)] │ │ [tool_use: agent(task2)] │ │ [tool_use: agent(task3)] │ └──────────────────────────────────────────┘
│ buildForkedMessages() 为每个任务创建子代理消息: │ ┌───────────┼───────────┐ ▼ ▼ ▼
子代理1消息: 子代理2消息: 子代理3消息: […共享历史] […共享历史] […共享历史] [assistant] [assistant] [assistant] ← 完全相同 [user: [user: [user:
placeholder] placeholder] placeholder] ← 完全相同 task1指令] task2指令] task3指令] ← 只有这行不同
↑ Prompt Cache 命中 ↑ ↑ 命中 ↑ ↑ 命中 ↑ 只有末尾指令行不同,前缀字节完全相同,最大化缓存命中
关键设计约定总结:
runAgent 是
async function* 不是普通 async function;以 async generator yield Message
buildForkedMessages 返回 2 条 只返回追加的 2 条;完整序列 = parentHistory + 2条 Fork 子代理保留 Agent 工具 为保证 prompt cache,不移除工具;通过
isInForkChild() 在调用时阻断
getCoordinatorSystemPrompt() 不存在 Coordinator 通过
getCoordinatorUserContext() 注入工具列表 权限路由主路径是 leader TUI mailbox 是 headless/后台的 fallback,不是主路径 Teammate 用
runWithTeammateContext 不是
runWithAgentContext;TeammateContext 含更多字段
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/270719.html