10. AgentTool - 子 Agent 生成所属分组工具系统概述AgentTool 是 Claude Code 实现任务委派与上下文隔离的核心机制主 Agent 通过它派生子 Agent把多步复杂任务交给一个独立的对话线程去完成。子 Agent 有三种形态——built-in内置通用 Agent、custom用户/项目配置的 Markdown Agent、plugin插件提供的 Agent它们都通过loadAgentsDir.ts统一加载。AgentTool 还引入了Fork subagent机制不指定subagent_type时 fork 当前 Agent 的上下文让廉价的研究型任务可以继承父对话的 prompt cache。UI 层面 AgentTool 是最复杂的工具之一——它要渲染子 Agent 的进度流progress messages、把连续的搜索/读取操作折叠成 summary、在 condensed/verbose/transcript 三种模式下展示不同密度的信息。agentColorManager.ts给每个 agentType 分配主题色让用户能在 teams 面板中一眼区分多个并行 Agent。源码位置[tools/AgentTool/prompt.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/AgentTool/prompt.ts)[tools/AgentTool/UI.tsx](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/AgentTool/UI.tsx)[tools/AgentTool/loadAgentsDir.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/AgentTool/loadAgentsDir.ts)[tools/AgentTool/agentColorManager.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/AgentTool/agentColorManager.ts)[tools/AgentTool/forkSubagent.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/AgentTool/forkSubagent.ts)[tools/AgentTool/runAgent.ts](file:///e:/2026plan/AI_Lab/claude-code-sourcemap-main/restored-src/src/tools/AgentTool/runAgent.ts)核心实现分析1. prompt.ts动态 vs 静态 agent 列表的缓存优化getPrompt()的开头有一个关键决策// When the gate is on, the agent list lives in an agent_listing_delta// attachment (see attachments.ts) instead of inline here. This keeps the// tool description static across MCP/plugin/permission changes so the// tools-block prompt cache doesnt bust every time an agent loads.constlistViaAttachmentshouldInjectAgentListInMessages()constagentListSectionlistViaAttachment?Available agent types are listed in system-reminder messages in the conversation.:Available agent types and the tools they have access to:${effectiveAgents.map(agentformatAgentLine(agent)).join(\n)}shouldInjectAgentListInMessages()的注释揭示了原因“The dynamic agent list was ~10.2% of fleet cache_creation tokens: MCP async connect, /reload-plugins, or permission-mode changes mutate the list → description changes → full tool-schema cache bust.” 把 agent 列表从工具描述里挪到 attachment 消息能让工具描述保持静态MCP/插件/权限变化不再触发整个 tools-block 的缓存失效——这是 10% fleet 缓存优化背后的设计。GrowthBook gatetengu_agent_list_attach控制灰度。2. Fork subagent 的 prompt 设计当isForkSubagentEnabled()时prompt 注入When to fork小节constwhenToForkSectionforkEnabled?## When to fork Fork yourself (omit \subagent_type\) when the intermediate tool output isnt worth keeping in your context. The criterion is qualitative — will I need this output again — not task size. - **Research**: fork open-ended questions... - **Implementation**: prefer to fork implementation work that requires more than a couple of edits. Forks are cheap because they share your prompt cache. Dont set \model\ on a fork — a different model cant reuse the parents cache. Pass a short \name\ (one or two words, lowercase) so the user can see the fork in the teams panel and steer it mid-run.:这里有三个核心约束① Fork 的判据是输出是否值得保留在上下文不是任务大小② Fork 不能设model否则无法共享父 prompt cache③ Fork 必须给name方便用户在 teams 面板里识别并 mid-run 干预。3. “Don’t peek. Don’t race.” 强约束Fork 模式下有两段对模型行为的硬约束**Dont peek.** The tool result includes an output_file path — do not Read or tail it unless the user explicitly asks for a progress check. You get a completion notification; trust it. Reading the transcript mid-flight pulls the forks tool noise into your context, which defeats the point of forking. **Dont race.** After launching, you know nothing about what the fork found. Never fabricate or predict fork results in any format — not as prose, summary, or structured output. The notification arrives as a user-role message in a later turn; it is never something you write yourself.这两条直击 LLM 的两个失败模式① 偷读 fork 转录文件会污染父上下文违背 fork 的初衷② 在通知到达前编造 fork 结果是 LLM 幻觉的常见来源。配套的forkExamples给出用户中途询问 fork 进度的标准答复——“Still waiting on the audit — that’s one of the things it’s checking. Should land shortly.”只给状态不给猜测。4. formatAgentLine工具列表的三态展示formatAgentLine把单个 agent 格式化为一行exportfunctionformatAgentLine(agent:AgentDefinition):string{consttoolsDescriptiongetToolsDescription(agent)return-${agent.agentType}:${agent.whenToUse}(Tools:${toolsDescription})}getToolsDescription处理 allowlistdenylist 同时存在的复杂情况if(hasAllowlisthasDenylist){// Both defined: filter allowlist by denylist to match runtime behaviorconstdenySetnewSet(disallowedTools)consteffectiveToolstools.filter(t!denySet.has(t))if(effectiveTools.length0)returnNonereturneffectiveTools.join(, )}elseif(hasAllowlist){returntools.join(, )}elseif(hasDenylist){returnAll tools except${disallowedTools.join(, )}}returnAll tools注释强调filter allowlist by denylist to match runtime behavior——prompt 文本必须与运行时实际可用工具一致否则模型会按描述调用一个被 deny 的工具而失败。5. loadAgentsDir.ts三种 agent 来源的统一加载getAgentDefinitionsWithOverrides()是 agent 加载的入口被 memoizeexportconstgetAgentDefinitionsWithOverridesmemoize(async(cwd:string):PromiseAgentDefinitionsResult{if(isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)){constbuiltInAgentsgetBuiltInAgents()return{activeAgents:builtInAgents,allAgents:builtInAgents}}// ...constmarkdownFilesawaitloadMarkdownFilesForSubdir(agents,cwd)constcustomAgentsmarkdownFiles.map(/* parseAgentFromMarkdown */)letpluginAgentsPromiseloadPluginAgents()if(feature(AGENT_MEMORY_SNAPSHOT)isAutoMemoryEnabled()){const[pluginAgents_]awaitPromise.all([pluginAgentsPromise,initializeAgentMemorySnapshots(customAgents),])pluginAgentsPromisePromise.resolve(pluginAgents_)}constpluginAgentsawaitpluginAgentsPromiseconstbuiltInAgentsgetBuiltInAgents()constallAgentsList[...builtInAgents,...pluginAgents,...customAgents]constactiveAgentsgetActiveAgentsFromList(allAgentsList)// Initialize colors for all active agentsfor(constagentofactiveAgents){if(agent.color)setAgentColor(agent.agentType,agent.color)}return{activeAgents,allAgents:allAgentsList,failedFiles}},)三个来源并行加载built-in代码内嵌、plugin插件 manifest、customagents/ 目录的 Markdown。getActiveAgentsFromList按优先级去重——builtIn plugin user project flag managed后者覆盖前者同名 agentType。initializeAgentMemorySnapshots在 memory 启用时并发初始化快照——这是把加载插件和初始化 memory用Promise.all并发的优化点。6. AgentDefinition 类型族exporttypeAgentDefinition|BuiltInAgentDefinition|CustomAgentDefinition|PluginAgentDefinition三者共享BaseAgentDefinition区别在source字段和 system prompt 获取方式BuiltInAgentDefinitionsource: built-ingetSystemPrompt接收toolUseContext参数动态 promptCustomAgentDefinitionsource: userSettings | projectSettings | policySettings | flagSettingsgetSystemPrompt是无参闭包PluginAgentDefinitionsource: plugin附带plugin: string元数据BaseAgentDefinition的字段极丰富tools/disallowedTools/skills/mcpServers/hooks/color/model/effort/permissionMode/maxTurns/memory/isolation/omitClaudeMd等。omitClaudeMd的注释揭示了大规模优化“Read-only agents (Explore, Plan) don’t need commit/PR/lint guidelines — the main agent has full CLAUDE.md and interprets their output. Saves ~5-15 Gtok/week across 34M Explore spawns.” Explore 这种只读 agent 不注入 CLAUDE.md每周节省 5-15 Gtok。7. parseAgentFromMarkdown宽松解析 错误隔离Markdown agent 解析对每个字段都做容错constbackgroundRawfrontmatter[background]if(backgroundRaw!undefinedbackgroundRaw!truebackgroundRaw!falsebackgroundRaw!truebackgroundRaw!false){logForDebugging(Agent file${filePath}has invalid background value ${backgroundRaw}. Must be true, false, or omitted.)}constbackgroundbackgroundRawtrue||backgroundRawtrue?true:undefined每个字段解析失败都只是logForDebugging 默认值不抛错——避免一个 agent 文件格式错误导致整个 agent 系统不可用。文件级别也做了隔离getParseError区分缺少 name 字段和缺少 description 字段等具体错误便于用户排查。文件没有name字段时直接静默跳过注释说they’re likely co-located reference documentation——agents 目录可能并存非 agent 文档。8. Memory 工具的自动注入agent 启用 memory 时自动注入文件操作工具// If memory is enabled, inject Write/Edit/Read tools for memory accessif(isAutoMemoryEnabled()memorytools!undefined){consttoolSetnewSet(tools)for(consttoolof[FILE_WRITE_TOOL_NAME,FILE_EDIT_TOOL_NAME,FILE_READ_TOOL_NAME]){if(!toolSet.has(tool)){tools[...tools,tool]}}}getSystemPrompt也在 memory 启用时拼上loadAgentMemoryPrompt。这是agent 配置驱动运行时能力的设计——声明memory: user后工具集和 prompt 自动扩展无需用户手动加 FileEdit/FileRead 到 tools 列表。9. agentColorManager.ts8 色分配exporttypeAgentColorName|red|blue|green|yellow|purple|orange|pink|cyanexportconstAGENT_COLOR_TO_THEME_COLOR{red:red_FOR_SUBAGENTS_ONLY,blue:blue_FOR_SUBAGENTS_ONLY,// ...}asconstsatisfies RecordAgentColorName,keyofTheme注释里_FOR_SUBAGENTS_ONLY后缀是关键——这些颜色只用于 subagent主 Agent 不会用避免视觉混淆。general-purposeagentType 显式返回 undefined不着色exportfunctiongetAgentColor(agentType:string):keyofTheme|undefined{if(agentTypegeneral-purpose)returnundefinedconstagentColorMapgetAgentColorMap()constexistingColoragentColorMap.get(agentType)if(existingColorAGENT_COLORS.includes(existingColor)){returnAGENT_COLOR_TO_THEME_COLOR[existingColor]}returnundefined}getAgentColorMap()来自bootstrap/state.ts是全局持久化的 Map——同一 agentType 在整个会话中颜色稳定不随重载变化。10. UI.tsxprogress messages 的折叠算法processProgressMessages是 AgentTool UI 最复杂的函数之一它把子 Agent 的连续搜索/读取操作折叠成 summaryfunctionprocessProgressMessages(messages,tools,isAgentRunning):ProcessedMessage[]{// Only process for antsif(external!ant){returnmessages.filter(/* ... */).map(m({type:original,message:m}))}// ...letcurrentGroupnullfunctionflushGroup(isActive){if(currentGroup(currentGroup.searchCount0||currentGroup.readCount0||currentGroup.replCount0)){result.push({type:summary,searchCount,readCount,replCount,uuid,isActive})}currentGroupnull}// ...}注释说Only process for ants——这个折叠算法只对 ant 用户启用外部用户看到原始消息流。currentGroup累积连续的 search/read/REPL 操作遇到非此类操作就 flush。isActive标记最后一组是否还在运行中影响 spinner 显示。getSearchOrReadInfo用toolUseByIDMap 反查 tool_use 块避免依赖 normalizedMessages。11. extractLastToolInfo从尾部倒推extractLastToolInfo从 progress messages 尾部倒推最后一个工具信息用于 grouped 渲染// Count trailing consecutive search/read operations from the endletsearchCount0letreadCount0for(letiprogressMessages.length-1;i0;i--){constmsgprogressMessages[i]!if(!hasProgressMessage(msg.data))continueconstinfogetSearchOrReadInfo(msg,tools,toolUseByID)if(info(info.isSearch||info.isRead)){if(msg.data.message.typeuser){if(info.isSearch)searchCountelseif(info.isRead)readCount}}else{break}}if(searchCountreadCount2){returngetSearchReadSummaryText(searchCount,readCount,true)}如果尾部有 ≥2 个连续 search/read就显示 summary如3 searches, 2 reads否则找最后一个 tool_result用tool.getToolUseSummary获取摘要。这是把子 Agent 正在做什么压缩成一行展示给用户的算法。12. renderToolResultMessage三状态分支子 Agent 结果有三种状态分支if(internal.statusremote_launched){/* 远程 agent 启动 */}if(data.statusasync_launched){/* 后台 agent 启动 */}if(data.status!completed)returnnull// 未完成不渲染结果// completed 状态constresult[${totalToolUseCount}tool uses,${formatNumber(totalTokens)}tokens,formatDuration(totalDurationMs)]constcompletionMessageDone (${result.join( · )})completed状态渲染Done (N tool uses · M tokens · duration)摘要 transcript 模式下的完整子 Agent 转录。async_launched显示Backgrounded agent加 ↓ 快捷键提示。remote_launched显示 task ID 和 session URL。13. renderGroupedAgentToolUse并行 Agent 分组渲染当一条消息里发起多个 Agent 调用时renderGroupedAgentToolUse把它们分组展示constallSameTypeagentStats.length0agentStats.every(statstat.agentTypeagentStats[0]?.agentType)constcommonTypeallSameTypeagentStats[0]?.agentType!Agent?agentStats[0]?.agentType:nullconstallAsyncagentStats.every(statstat.isAsync)returnBox flexDirectioncolumnmarginTop{1}Box flexDirectionrowToolUseLoader shouldAnimate{shouldAnimateanyUnresolved}isUnresolved{anyUnresolved}isError{anyError}/Text{allComplete?allAsync?Text bold{toolUses.length}/Textbackground agents launched{ }Text dimColorKeyboardShortcutHint shortcut↓actionmanageparens//Text/:Text bold{toolUses.length}/Text{ }{commonType?${commonType}agents:agents}finished/:RunningText bold{toolUses.length}/Text{ }{commonType?${commonType}agents:agents}…/}{ }/Text{!allAsyncCtrlOToExpand/}/Box{agentStats.map((stat,index)AgentProgressLine/* ... *//)}/Box三态展示运行中“Running N agents…”、完成“N agents finished”、后台启动“N background agents launched”。allSameType检测是否所有 agent 同类型是则在标题里显示类型名如3 test-runner agents finished更紧凑。每个 agent 用AgentProgressLine组件展示颜色来自getAgentColor。关键设计要点agent 列表外置到 attachmentshouldInjectAgentListInMessages把动态 agent 列表从工具描述挪到 attachment 消息避免 MCP/插件变化触发 tools-block 缓存失效节省约 10% fleet cache_creation tokens。Fork 的三条铁律fork 不设 model保 cache、不 peek output_file防上下文污染、不 race防幻觉——prompt 里反复强调并在示例中展示正确行为。宽松解析 错误隔离agent Markdown 解析对每字段做容错单文件错误不拖垮整个 agent 系统无name字段的文件被静默跳过视为参考文档。Memory 自动工具注入声明memory: user/project/local后FileEdit/FileWrite/FileRead 自动加入 tools 列表prompt 自动拼接 memory 内容——配置驱动运行时能力。subagent 专属颜色隔离8 种颜色全部带_FOR_SUBAGENTS_ONLY后缀的主题键主 Agent 永不着色避免视觉混淆颜色映射持久化在 bootstrap state全会话稳定。progress 折叠算法仅 ant 启用连续 search/read/REPL 操作折叠成 summary 是 ant-only 优化外部用户看原始流——反映内部用户对密度更高的信息展示耐受度更好。与其他模块的关系forkSubagent.ts / runAgent.tsfork 与 fresh subagent 的实际派生逻辑分别走不同的createSubagentContext路径fork 共享父renderedSystemPrompt以命中 prompt cache。bootstrap/state.tsgetAgentColorMap持久化 agentType→color 映射跨会话稳定。memdir 模块isAutoMemoryEnabled、loadAgentMemoryPrompt、checkAgentMemorySnapshot给 memory-capable agent 提供记忆能力。markdownConfigLoaderloadMarkdownFilesForSubdir(agents, cwd)是统一的 Markdown 配置加载器也服务于 commands、skills 等。plugins/loadPluginAgents插件 agent 加载器memoized 并可与 memory snapshot 初始化并发。SendMessageToolprompt 末尾指引To continue a previously spawned agent, use SendMessageTool with the agent’s ID or name——持续会话走 SendMessage 而非新派生。utils/collapseReadSearchgetSearchOrReadFromContent、getSearchReadSummaryText是 progress 折叠算法的共享工具。小结AgentTool 是 Claude Code 工具系统里最复杂的一个——它本身是工具又派生出新的 Agent 上下文。它的 prompt 在 fork 启用与否、coordinator 模式与否、agent 列表外置与否之间动态切换每条分支都对应具体的缓存/隔离/治理目标。它的 UI 渲染要处理三种完成状态、三种渲染密度condensed/verbose/transcript、并行分组、progress 折叠——一份 progress 流可能包含数十条消息要压缩成Running 3 agents… 2 more tool uses这样的高密度摘要。loadAgentsDir.ts把 built-in/plugin/custom 三源 agent 统一加载并按优先级去重agentColorManager.ts给每个 agentType 分配持久化颜色。AgentTool 是工具系统与 Agent 系统的交汇点理解它就理解了 Claude Code 的任务委派哲学。