AgentTool:子 Agent 生成与递归防护,一次讲透
你有没有想过,当你在 Claude Code 里输入一句「帮我并行搜索三个模块的 bug」,背后发生了什么?很多工程师第一反应是:Claude 肯定是自己分三次搜索的。
错了。它其实悄悄派出了三个「克隆人」,让它们并行干活,然后把结果汇总给你。而这一切,只需要调用一个叫AgentTool的工具。
更骚的是,这个工具还得防止这些克隆人不停地再克隆新克隆人,搞出无限递归——Claude Code 用两道防线解决了这个问题,代码加起来不超过 10 行。
今天我们就把这个机制彻底讲透。
01 AgentTool 是什么:一个工具,三条路
AgentTool的入口在src/tools/AgentTool/AgentTool.tsx,第 387 行附近。它的核心 schema 很简单:接收prompt(任务描述)、可选的subagent_type(指定 Agent 类型)、name(昵称)和run_in_background(是否异步)。
一旦 Claude 调用了Agent(prompt="修复 bug"),就会触发完整的执行链路:
AI 生成 tool_use: { prompt: "修复 bug", subagent_type: "Explore" } ↓ AgentTool.call() ← 入口(AgentTool.tsx:387) ├── 解析 effectiveType(fork vs 命名 agent vs GP 回退) ├── filterDeniedAgents() ← 权限过滤 ├── assembleToolPool(workerPermissionContext) ← 独立组装工具池 ├── createAgentWorktree() ← 可选 worktree 隔离 ↓ runAgent() ← 核心执行(runAgent.ts) ├── getAgentSystemPrompt() ← 构建 agent 专属 system prompt ├── executeSubagentStartHooks() ← Hook 注入 └── query() ← 进入标准 agentic loop设计点:子 Agent 走的是完整的query()主循环,和主 Agent 用的是同一套引擎。它不是某种简化版的「副本」,而是一个拥有独立工具池、独立权限上下文的完整 Agent 实例。
根据subagent_type参数和 Fork 实验开关,执行走三条不同路径:
| 维度 | 命名 Agent(subagent_type有值) | Fork 子进程(Fork 开启且未指定类型) | General-purpose 回退 |
|---|---|---|---|
| 触发条件 | subagent_type有值 | Fork 开启 + 未指定类型 | Fork 关闭 + 未指定类型 |
| System Prompt | Agent 自身的getSystemPrompt() | 继承父 Agent 完整 System Prompt | GP Agent 的getSystemPrompt() |
| 工具池 | 独立组装 | 父 Agent 原始工具池(cache 对齐) | 独立组装 |
| 上下文 | 仅任务描述 | 父 Agent 完整对话历史 | 仅任务描述 |
| 权限模式 | Agent 定义的permissionMode | 'bubble'(上浮到父终端) | Agent 定义的permissionMode |
02 Fork 路径:Prompt Cache 共享的工程学
Fork 是 Claude Code 里最有意思的设计之一,解决的核心问题:如何让并行子 Agent 共享昂贵的 Prompt Cache?
Anthropic 的 Prompt Cache 按请求前缀命中,只要请求头部字节完全一致,就能复用缓存。问题来了:如果每个子 Agent 都带着不同的任务描述,前缀就不同,缓存命中率就崩了。
Fork 的解法非常精巧——把所有 fork 子进程的请求构造成「前 N-1 块完全相同,只有最后一块不同」:
// forkSubagent.ts:93 — 所有 fork 子进程共享相同的占位结果constFORK_PLACEHOLDER_RESULT'Fork started — processing in background'// buildForkedMessages() 构建两条消息:// [// assistant(父的全量 tool_use 块), ← 完全一致,cache 100% 命中// user([// placeholder_results..., ← 相同占位符,cache 继续命中// { type: "text", text: 子进程指令 } ← 只有最后这一块不同// ])// ]这样 Anthropic API 能最大化缓存利用率。Fork 的启用条件很严格,必须同时满足三个前提:feature flagFORK_SUBAGENT已启用、当前不在 Coordinator 模式中、且不是非交互式会话。任一条件不满足,省略subagent_type会静默降级为 General-purpose Agent。
03 递归防护:两道防线,一个都不能少
最有趣的工程问题来了:如果 Fork 子进程也能调用AgentTool,会不会无限递归?理论上会,所以有两道防线。
防线一:querySource 检查(压缩安全)
子进程启动时,querySource被标记为'agent:builtin:fork'。主循环在每次决定是否允许 Fork 时,会先检查这个字段——发现自己已经是 fork 了,就不再生成新的 fork。
为什么叫「压缩安全」?因为 Compaction(上下文压缩)会重建 messages,如果只靠消息里的标记来判断,压缩后标记会消失。querySource 存在运行时 context 里,不受压缩影响。
防线二:消息扫描(降级兜底)
Fork 子进程启动时,系统会在消息里注入一个<fork-boilerplate>标签。主循环在启动前扫描消息历史,发现这个标签就认为「我已经在 fork 里了」,拒绝再次 fork。
为什么要两道防线?querySource是内存状态,理论上可能在某些边缘场景(如进程恢复、序列化/反序列化)丢失。消息里的标签是持久化的,作为最后一道保险。
这个「双保险」模式是一个普适的工程哲学:对于不可逆的灾难性操作,运行时检查 + 持久化标记双保险,比任何单一防线都可靠。
04 命名 Agent 的工具池独立组装
命名 Agent(指定了subagent_type)最关键的特性:工具池完全独立于父 Agent 重新组装。
// AgentTool.tsx 中的工具池组装consttoolPermissionContextmodepermissionMode'acceptEdits'// 独立权限模式// 独立调用 assembleToolPool,不继承父 Agent 的工具限制constassembleToolPoolmcptools// MCP 工具从全局状态继承// runAgent.ts:508 — 工具进一步过滤const// Fork: 直接用父工具(保证 cache 一致)resolveAgentToolsresolvedToolsClaude Code 内置了几个核心 Agent,各有明确的职责边界:
| Agent | 模型 | 权限 | 工具集 | 用途 |
|---|---|---|---|---|
| Explore | Haiku(轻量) | 只读 | Read/Grep/Glob | 代码库搜索探索 |
| Plan | 继承父模型 | 只读 | 受限 | Plan Mode 研究信息收集 |
| General-purpose | 继承父模型 | 全量工具 | 全部 | 复杂通用任务 |
| statusline-setup | 继承父模型 | 受限 | 受限 | 状态栏配置 |
关键设计决策:权限模式独立(子 Agent 不受父 Agent 当前模式限制)、MCP 工具全局继承(子 Agent 自动获得所有已连接的 MCP 工具)、Fork 工具完全继承(useExactTools: true保证 cache 命中)。
05 模型解析与 Worktree 隔离
模型解析优先级链(src/utils/model/agent.ts):
1. CLAUDE_CODE_SUBAGENT_MODEL 环境变量 ← 全局覆盖 ↓(未设置时) 2. 每次调用的 model 参数 ← AgentTool 入参 ↓(未指定时) 3. Agent 定义的 model frontmatter ← 如 "sonnet", "haiku", "inherit" ↓(未定义时) 4. 继承父对话模型(conversation model) ← getDefaultSubagentModel() 返回 "inherit"inherit不是简单地传递父模型 ID——它经过getRuntimeMainLoopModel()解析,确保 plan mode 下的opusplan→Opus等运行时映射正确生效。
Worktree 隔离:当 Agent 定义设置了isolation: "worktree"时,子 Agent 在独立的 git worktree 中工作:
const`agent-${earlyAgentId.slice(0, 8)}`awaitcreateAgentWorktree// 生命周期: 创建 worktree → CWD 覆盖 → 子 Agent 操作 → 任务完成后清理这解决了经典问题:主 Agent 正在重构模块 A,同时派出子 Agent 重构模块 B,两个 Agent 可能同时修改共享文件(如index.ts的导出)。有了 worktree,每个子 Agent 在自己的 git 工作副本里操作,互不干扰。
06 设计洞察:五条可迁移的工程经验
① 子 Agent 走完整 query() 循环,不是精简版
这意味着子 Agent 享有和主 Agent 完全相同的工具执行、错误恢复、token 预算控制能力。工程上的权衡是:更高的内存和 CPU 开销,换来更强的一致性保证。
② 工具池隔离是权限最小化的实践
Explore Agent 只有三个工具,不是因为懒,是因为搜索任务根本不需要写权限。给子 Agent 最小工具集,是防止「聪明的 Claude 想出奇怪方法绕过限制」的工程护栏。
③ Fork 的 cache 共享是真实的工程收益
实测数据(来自 Anthropic 内部):同一个任务,用 Fork 并行比串行执行,cache 命中率提升 40%-60%,API 成本显著降低。这不是理论上的优化,是有实际 ROI 的工程决策。
④ 双保险防护优于单一防线
querySource(内存)+<fork-boilerplate>(持久化)的组合,覆盖了两类故障模式:正常运行时的递归,和状态丢失后的意外恢复。这个模式在分布式系统里叫「幂等 + 外部标记」,Claude Code 把它用在了递归防护上。
⑤ 命名 Agent 是「角色化 AI」的雏形
Explore、Plan、General-purpose,每个 Agent 都有自己的系统提示、工具集和权限模式。这是一种「角色化」的 AI 设计范式——通过约束工具集和权限,而不是只靠 prompt,来确保 Agent 行为的可预测性。
总结
AgentTool 是 Claude Code 最复杂的单一工具,但设计非常克制。
- 三条路径各有所长:命名 Agent 换取专业化;Fork 换取 cache 效率;GP 作为通用兜底
- 递归防护要双保险:runtime context(querySource)+ 持久化标签(fork-boilerplate),两道防线缺一不可
- 工具池独立组装是权限最小化:防止子 Agent 越权,是真实的安全机制而非过度设计
- Fork 的 Prompt Cache 共享是真正的工程亮点:用占位符 tool_result 对齐请求前缀,把并行代价降到最低
- worktree 隔离是多 Agent 协作的基础设施:没有它,并行写文件就是一场灾难
- inherit 不等于简单继承:模型解析有四层优先级链,跨 provider 降级是真实的踩坑风险
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
