第9章:子智能体与 Fork 模式

学习目标: - 深入理解子智能体的生成机制和完整生命周期管理 - 掌握 Fork 模式的缓存共享策略与字节级继承原理 - 学会设计和创建自定义智能体以应对特定场景需求 - 理解对抗性验证 Agent 的设计哲学及其工程意义

Claude Code 的真正威力不仅在于单轮对话的能力,更在于它可以将复杂任务委派给专门的子智能体(Subagent)并行处理。想象一个大型交响乐团:指挥家(主智能体)不需要亲自演奏每一种乐器,而是将不同声部的演奏交给各自领域的专家(子智能体)。本章将深入解析 AgentTool 的完整架构,揭示内置智能体的设计哲学,并重点拆解 Fork 模式如何通过精巧的缓存策略实现真正的并行执行而不浪费 token。


9.1 AgentTool 架构

目录结构与模块职责

AgentTool 的代码位于智能体工具目录下,由以下核心模块组成:

职责 说明 设计考量
工具主入口 处理输入 schema、路由策略、同步/异步分支 单一入口点降低调用复杂度
智能体运行器 管理生命周期、上下文构建、MCP 初始化 生命周期与业务逻辑解耦
Fork 模式实现 构建缓存安全的消息前缀 缓存策略独立演进
内置智能体注册表 根据 feature gate 动态加载 按需加载减少启动开销
自定义智能体加载 解析 Markdown/JSON 定义 用户友好的声明式配置
工具函数集 工具过滤、解析、结果格式化等 可复用的基础设施

这套架构的核心设计原则是关注点分离:工具主入口负责"决定做什么",运行器负责"怎么做",Fork 模块负责一种特定的"怎么做得更高效"的策略。

架构洞察:为什么不将路由逻辑和执行逻辑合并?

在很多简单系统中,路由和执行是同一个模块。但 Claude Code 的场景远比简单系统复杂——一个工具调用可能走同步路径(主线程等待)、异步路径(后台执行)或 Fork 路径(缓存并行)。将路由决策与执行过程分离,意味着当需要新增一种执行策略(比如未来的"流式子智能体")时,只需要在路由层增加一个分支,而不需要修改运行器核心逻辑。这是经典的策略模式在系统架构层面的应用。

BaseAgentDefinition 类型定义

所有智能体都基于智能体加载模块中定义的 BaseAgentDefinition 类型,包含以下关键字段:智能体唯一标识符(agentType)、使用场景描述(whenToUse)、允许/禁止的工具列表、预加载的 skill 名称、智能体专属 MCP 服务器、生命周期钩子、UI 显示颜色、模型指定、推理努力程度、权限模式、最大轮次限制、是否后台运行、隔离模式、是否省略 CLAUDE.md 上下文等。

这些字段并非随意堆砌,而是可以按照功能维度归类为四大类:

graph LR
    subgraph 身份与描述
        A1["agentType"]
        A2["whenToUse"]
        A3["color"]
    end
    subgraph 工具与能力
        B1["allowedTools /<br/>disallowedTools"]
        B2["skills"]
        B3["mcpServers"]
    end
    subgraph 执行控制
        C1["model / effort"]
        C2["maxTurns"]
        C3["permissionMode /<br/>background"]
    end
    subgraph 上下文管理
        D1["omitClaudeMd /<br/>isolationMode"]
        D2["hooks"]
        D3["memory"]
    end

    classDef category fill:#e8f4fd,stroke:#2196F3,stroke-width:2px,color:#1565C0
    class A1,A2,A3,B1,B2,B3,C1,C2,C3,D1,D2,D3 category

从这个类型衍生出三种具体的智能体定义:

  • BuiltInAgentDefinition(内置智能体):通过 getSystemPrompt() 动态生成系统提示。内置智能体的提示在编译时固定,但运行时通过函数动态构建,这样可以在不同环境(feature gate 开关状态不同)下生成不同的提示内容。
  • CustomAgentDefinition(自定义智能体):来自用户/项目/策略设置。用户通过 Markdown 文件声明式定义,系统自动解析。
  • PluginAgentDefinition(插件智能体):插件提供的智能体,带有 plugin 元数据。插件智能体可以携带额外的版本信息和依赖声明。

交叉引用: 插件智能体的加载机制与第11章的插件系统紧密相关。插件不仅可以提供技能(Skill),还可以提供智能体(Agent),两者共享相同的插件发现和加载基础设施。

三种智能体来源

智能体的加载优先级由加载合并函数决定:

graph TD
    A["内置智能体<br/>(开箱即用,默认选项)"]
    B["插件智能体<br/>(可覆盖同名内置智能体)"]
    C["用户设置<br/>~/.claude/settings.json"]
    D["项目设置<br/>.claude/settings.json"]
    E["Flag 设置<br/>CLI 启动参数"]
    F["策略设置<br/>(企业管理配置,最高优先级)"]

    A -->|"被覆盖"| B
    B -->|"被覆盖"| C
    C -->|"被覆盖"| D
    D -->|"被覆盖"| E
    E -->|"被覆盖"| F

    style F fill:#ff6b6b,stroke:#c0392b,color:#fff
    style E fill:#f39c12,stroke:#d68910,color:#fff
    style D fill:#3498db,stroke:#2471a3,color:#fff
    style C fill:#2ecc71,stroke:#27ae60,color:#fff
    style B fill:#9b59b6,stroke:#7d3c98,color:#fff
    style A fill:#bdc3c7,stroke:#7f8c8d,color:#333
  1. 内置智能体(built-in):优先级最低,作为默认选项
  2. 插件智能体(plugin):可以覆盖内置智能体
  3. 用户设置(userSettings) -> 项目设置(projectSettings) -> Flag 设置(flagSettings) -> 策略设置(policySettings):优先级依次升高

这意味着策略级别的自定义智能体可以覆盖同名内置智能体,为企业部署提供了灵活性。

实际使用场景:企业安全审计覆盖

假设一个金融科技公司需要所有代码审查都遵循内部安全规范。管理员可以在策略设置中定义一个名为 code-review 的智能体,覆盖内置的同名智能体。这个自定义版本会在系统提示中加入公司特定的安全检查清单(如 PCI-DSS 合规检查),并接入内部的漏洞扫描 MCP 服务器。这样无论开发者如何配置个人设置,企业级的安全审查标准始终生效。


9.2 内置智能体

内置智能体在注册模块中通过 getBuiltInAgents() 函数返回当前环境下可用的智能体列表。每个智能体都被精心设计为特定领域的专家。它们的组合覆盖了软件工程中最常见的四种工作模式:探索、规划、执行和验证。

graph LR
    subgraph 只读层
        E["Explore<br/>(搜索)"]
        P["Plan<br/>(规划)"]
    end
    subgraph 读写层
        G["General<br/>(执行)"]
        V["Verification<br/>(验证)"]
    end

    style E fill:#3498db,stroke:#2471a3,color:#fff
    style P fill:#2ecc71,stroke:#27ae60,color:#fff
    style G fill:#f39c12,stroke:#d68910,color:#fff
    style V fill:#e74c3c,stroke:#c0392b,color:#fff

Explore Agent:只读代码探索专家

Explore Agent 是一个高速只读搜索智能体。它的定义中声明了严格的禁止操作列表:禁止 Agent、ExitPlanMode、FileEdit、FileWrite、NotebookEdit 等工具,并可选择使用 haiku 模型以降低成本,同时省略 CLAUDE.md 以节省 token。

其核心设计理念体现在两个关键决策上:

第一,严格的只读约束。 系统提示中声明了严格的禁止操作列表(不能创建文件、修改文件、运行任何改变系统状态的命令),并且在工具层面通过 disallowedTools 物理禁止了 Edit、Write 等工具。这种"双重锁"设计——提示层面的软约束加上工具层面的硬约束——确保了即使模型"幻觉"中想要修改文件,也会因工具不可用而被物理阻止。

第二,omitClaudeMd 优化。 Explore 是只读搜索智能体,不需要 commit/PR/lint 规则。CLAUDE.md 通常包含项目的编码规范、commit 消息格式、PR 模板等内容,这些对于一个只做搜索的智能体完全无用。省略 CLAUDE.md 不仅减少了 token 消耗,更重要的是减少了系统提示的噪声,让模型更聚焦于搜索任务本身。这一优化据估计每周可节省 5-15 Gtoken。

最佳实践:如何有效利用 Explore Agent

Explore Agent 最适合以下场景: - 代码考古:快速定位某个 bug 的根因,追踪调用链路 - 依赖分析:理解某个函数被哪些模块引用 - 架构探索:梳理项目的目录结构和模块关系 - 知识检索:查找某个特定配置项或常量的定义位置

使用提示:给 Explore Agent 足够具体的搜索目标,比如"找到处理用户认证的所有中间件函数"比"看看代码"效果好得多。

Plan Agent:结构化规划智能体

Plan Agent 复用了 Explore Agent 的工具集(同样禁止 Edit、Write 等修改类工具),但承担不同角色——它是软件架构师和规划专家。Plan Agent 使用 inherit 模型并省略 CLAUDE.md,其系统提示要求它输出结构化的实现计划,并以"关键文件"列表结尾,为后续的实现阶段提供清晰指引。

Plan Agent 的输出通常包含以下结构化信息:

输出要素 说明 对后续阶段的价值
问题分析 对当前需求的理解和拆解 确保实现方向正确
实施步骤 按优先级排列的修改步骤 General Agent 可按步骤逐一执行
关键文件 需要阅读和修改的文件列表 减少后续阶段的搜索开销
风险评估 可能的副作用和注意事项 Verification Agent 的检查重点
依赖关系 步骤之间的前后依赖 决定是否可以并行执行

为什么 Plan Agent 要省略 CLAUDE.md?

这与 Explore Agent 的原因相同但又有微妙的区别。Explore 省略是因为"不需要",Plan 省略是因为"不应该"。规划阶段的目的是理解代码结构并制定计划,不应该受限于项目特定的编码风格约束。如果 Plan Agent 看到了"本项目使用 camelCase 命名"这样的规则,它可能会在规划阶段就考虑命名细节,而这些细节应该在实现阶段处理。省略 CLAUDE.md 让 Plan Agent 更关注"做什么"而非"怎么做"。

General Purpose Agent:通用智能体

General Purpose Agent 是最灵活的智能体,拥有全部工具权限(使用通配符 '*' 允许所有工具)。但实际可用工具仍受全局过滤函数的约束。它是实际干活的"执行者"——前面 Explore 负责侦查,Plan 负责制定计划,General Purpose 负责动手修改。

General Purpose Agent 的设计哲学是"默认信任,边界后移"。它不做工具层面的预设限制,而是依赖全局安全层(权限系统、工具过滤函数)来确保安全。这种设计的好处是灵活性最大化——不同的任务可以动态组合不同的工具集,而不需要为每种任务预定义工具白名单。

反模式警告:不要用 General Purpose Agent 做只读任务

虽然 General Purpose Agent 技术上可以执行只读搜索,但这会带来不必要的成本和安全风险。只读任务应该使用 Explore Agent:它的 haiku 模型更便宜、省略 CLAUDE.md 节省 token、工具限制消除了意外修改的风险。这是一个典型的最小权限原则的应用。

Verification Agent:验证智能体

Verification Agent 是一个独特的"对抗性"智能体,设计目标是尽可能破坏被验证的代码,而不是确认它能工作。它使用红色 UI 标识强调对抗性质,始终后台运行,禁止修改项目文件,使用 inherit 模型。

对抗性设计的深层哲学

Verification Agent 的系统提示明确警告了两种失败模式:

  1. 验证回避(Verification Avoidance):模型倾向于找借口不运行测试,比如"这段代码逻辑上看起来正确"或"测试环境配置复杂"。系统提示列出了常见的自我合理化借口,要求 Agent 必须实际执行验证而非口头确认。

  2. 前 80% 的表面正确性(Surface Correctness Trap):代码可能通过了基本的 happy path 测试,但在边界条件、并发场景、错误路径上存在隐患。系统提示要求 Agent 特别关注"不那么明显"的失败模式。

这种对抗性设计借鉴了软件工程中的红队测试(Red Teaming)理念。传统测试验证"代码按照预期工作",而红队测试验证"代码在非预期情况下不会崩溃"。将这两种思维应用到同一个 LLM 的不同实例上,形成了一种"左手打右手"的自我对抗机制。

flowchart TD
    A["主智能体完成了代码修改"] --> B["Verification Agent 被激活(后台)"]
    B --> C["尝试运行测试,寻找失败的用例"]
    B --> D["检查边界条件和异常路径"]
    B --> E["寻找遗漏的 import 或类型错误"]
    B --> F["验证并发安全性"]
    B --> G["检查是否有遗漏的文件需要修改"]
    C --> H["输出:发现的问题列表 + 建议修复方案"]
    D --> H
    E --> H
    F --> H
    G --> H

    style A fill:#3498db,stroke:#2471a3,color:#fff
    style B fill:#e74c3c,stroke:#c0392b,color:#fff
    style H fill:#2ecc71,stroke:#27ae60,color:#fff

设计智慧:为什么 Verification Agent 要后台运行?

Verification Agent 始终在后台运行,这是一个深思熟虑的设计决策。原因有三:第一,验证是一个"可以等待"的任务——用户不需要实时看到验证过程,只需要看到最终结果;第二,后台运行释放了主线程,让用户可以继续与主智能体交互;第三,后台模式强制验证 Agent 独立工作,不会因为等待用户输入而中断验证流程。


9.3 Fork 模式:缓存安全的并行执行

Fork 模式是 Claude Code 中最精巧的架构创新之一。它允许主智能体将同一时刻的完整上下文"分叉"给多个并行子任务,同时利用 Anthropic API 的 prompt cache 机制避免重复传输大量 token。

Fork 模式的直觉理解

如果你熟悉 Unix 系统的 fork() 系统调用,Claude Code 的 Fork 模式与其有异曲同工之妙:

类比维度 Unix fork() Claude Code Fork 模式
触发时机 父进程调用 fork() 主智能体调用 Agent 工具
继承内容 内存映像、文件描述符 系统提示、工具定义、对话历史
分叉后关系 父子进程独立运行 主/子智能体并行运行
通信方式 pipe/shared memory 任务通知 XML
资源节省 Copy-on-Write 内存共享 Prompt Cache token 共享
递归防护 进程层级限制 querySource 检查

forkSubagent 模块的核心设计

Fork 模式的激活需要满足三个条件:feature gate 开启、非 Coordinator 模式、非非交互模式。当 Coordinator 模式激活时,Fork 模式会被自动禁用,因为 Coordinator 已经拥有自己的任务委派模型。

交叉引用: Coordinator 模式与 Fork 模式的互斥关系将在第10章详细讨论。简言之,Coordinator 是一种更重量的编排模式,适合需要精细任务管理的场景;Fork 是一种轻量级的并行模式,适合将同一上下文分发到多个独立子任务。

在工具主入口的路由逻辑中,当用户省略子智能体类型且 Fork 模式开启时,系统会触发 Fork 路径。

继承父对话的完整上下文和系统提示

Fork 子智能体的核心原则是字节级继承——子智能体必须与父智能体共享完全相同的 API 请求前缀,才能命中 prompt cache。

在 Fork 路径中,系统会优先使用父智能体已渲染的系统提示字节,避免因重建导致的缓存失效。只有在无法获取已渲染提示时才降级到重建路径。重建可能因特性开关的冷热状态不同而偏离,导致缓存失效,因此传递渲染后的字节是保证精确匹配的关键。

为什么是字节级而非语义级?

Prompt cache 是 Anthropic API 的底层基础设施优化,它通过字节前缀匹配来复用已处理的 token。这意味着即使两段文本"语义完全相同",只要在字节层面有任何一个字符的差异(哪怕是一个多余的空格或换行符),缓存就不会命中。这就是为什么 Fork 模式需要传递已渲染的原始字节而非重新构建——重新构建可能在空白字符、属性顺序等细微之处产生差异,而这些差异足以导致整个缓存前缀失效。

一个形象的比喻:prompt cache 就像 HTTP 缓存中的 ETag,它比对的是内容的精确哈希值。即使你把一段 HTML 重新格式化了缩进,虽然浏览器渲染结果相同,但 ETag 已经变了,缓存就失效了。

CacheSafeParams 五个维度

缓存安全参数类型定义了缓存命中的关键契约,包含五个维度:系统提示(systemPrompt)、用户上下文(userContext)、系统上下文(systemContext)、工具定义和模型(toolUseContext)、对话前缀消息(forkContextMessages)。

graph TD
    A["systemPrompt<br/>系统提示必须字节一致"]
    B["userContext<br/>用户上下文(CLAUDE.md 等)"]
    C["systemContext<br/>系统注入的上下文信息"]
    D["toolUseContext<br/>工具定义 + 模型选择"]
    E["forkContextMessages<br/>对话历史前缀消息"]

    A --> B --> C --> D --> E

    F["五维度全部一致<br/>缓存命中,节省 token 和延迟"]
    G["任一维度偏离<br/>缓存失效,完整重新处理"]
    E -.->|"命中"| F
    E -.->|"偏离"| G

    style A fill:#e74c3c,stroke:#c0392b,color:#fff
    style B fill:#e67e22,stroke:#d35400,color:#fff
    style C fill:#f1c40f,stroke:#f39c12,color:#333
    style D fill:#2ecc71,stroke:#27ae60,color:#fff
    style E fill:#3498db,stroke:#2471a3,color:#fff
    style F fill:#27ae60,stroke:#1e8449,color:#fff
    style G fill:#c0392b,stroke:#922b21,color:#fff

这五个维度对应 Anthropic API 缓存键的组成:system prompt、tools、model、messages prefix、thinking config。只有这五个维度完全一致时,子请求才能命中父请求的缓存。

Fork 路径通过 useExactTools 标志确保工具定义不变——当 Fork 模式激活时,直接使用父工具池而非重新解析智能体工具,从而保持工具定义的字节一致性。

buildForkedMessages 的巧妙设计

Fork 消息构建函数是缓存共享的核心。它的处理逻辑如下:首先保留完整的父 assistant 消息(包含所有 tool_use 块、thinking、text,但分配新的 UUID);然后收集所有 tool_use 块;为每个 tool_use 生成统一的固定字符串占位符 tool_result;最后构建单条 user 消息,包含占位符结果和子任务指令。

关键点在于占位符结果是所有 Fork 子智能体共享的固定字符串 "Fork started -- processing in background"。这意味着:

  • 所有 Fork 子智能体共享相同的前缀[...历史, assistant(所有 tool_use), user(占位符结果..., 指令)]
  • 只有最后的指令文本不同:最大化缓存命中面积
  • 结果结构为:[...history, assistant(all_tool_uses), user(placeholder_results..., directive)]

缓存效率分析示例

假设一个对话场景:父对话积累了 50,000 token 的历史消息,父智能体的 assistant 消息包含 3 个 tool_use 块(约 2,000 token),系统提示和工具定义约 10,000 token。

不使用 Fork 模式(传统子智能体): - 每个子智能体独立构建请求:50,000 + 2,000 + 10,000 = 62,000 token/次 - 3 个子智能体总计:186,000 input token

使用 Fork 模式: - 父请求:62,000 token(首次,建立缓存) - 3 个子智能体共享前缀:仅各自的"指令文本"部分是新的(约 200 token/个) - 3 个子智能体总计:3 * 200 = 600 新增 input token - 总计:62,600 input token(其中 62,000 命中缓存)

节省比例: (186,000 - 62,600) / 186,000 = 约 66% 的 token 节省

这种节省在大规模使用(比如一个会话中产生数十个 Fork 调用)时会更加显著。

递归 Fork 防护

Fork 子智能体保留了 Agent 工具以维持缓存一致的工具定义,因此必须防止递归 Fork。系统实现了双重检测策略:

  1. querySource 检查(主防线):设置在上下文选项上,不受自动压缩影响。这是一个运行时标记,在 Fork 子智能体的上下文中被设置,标识"我是被 Fork 出来的"。由于它不是对话历史的一部分,不会被上下文压缩机制清除,因此即使在长对话中也能可靠地防止递归。

  2. 消息扫描(后备):检测特定标签,应对 querySource 未传递的边界情况。这是一种防御性编程策略,处理那些 querySource 可能在序列化/反序列化过程中丢失的极端情况。

子任务指令生成函数会生成包含严格行为规范的指令,核心内容包括:声明自己是 Fork 工作进程而非主智能体、禁止生成子智能体、禁止提问或建议下一步操作等。

为什么不让 Fork 子智能体也拥有 Fork 能力?

表面上看,递归 Fork 可以实现"无限并行"的嵌套执行。但实际上这会带来严重的工程问题: - 资源爆炸:每个 Fork 都会创建一个新的 API 连接和工具实例,递归 Fork 会导致资源使用指数级增长 - 结果聚合困难:嵌套的 Fork 结果需要多层聚合,增加了结果丢失的风险 - 调试困难:递归结构使得错误追踪和性能分析变得极其复杂 - 缓存失效:每一层 Fork 的上下文都会与上一层略有不同,递归层级的缓存命中率会急剧下降


9.4 自定义智能体

.claude/agents/ 目录中的定义文件

自定义智能体通过加载合并函数加载。加载流程如下:

flowchart TD
    A["扫描 agents 目录"] --> B["解析每个 .md 文件"]
    B --> C["提取 YAML frontmatter → 智能体配置"]
    B --> D["提取 Markdown 正文 → 系统提示"]
    C --> E["并行加载插件智能体和内存快照"]
    D --> E
    E --> F["合并所有来源,按优先级去重"]
    F --> G["注册到智能体注册表"]
  1. 扫描 agents 目录中的 Markdown 文件
  2. 对每个 Markdown 文件解析智能体定义
  3. 并行加载插件智能体和内存快照
  4. 合并所有来源,按优先级去重

Markdown frontmatter 格式

一个完整的自定义智能体 Markdown 文件示例如下:

---
name: security-auditor
description: 分析代码中的安全漏洞并生成审计报告
tools:
  - Bash
  - Read
  - Grep
  - Glob
disallowedTools:
  - Write
model: haiku
effort: high
permissionMode: default
maxTurns: 30
color: orange
background: true
memory: project
skills:
  - /commit
mcpServers:
  - slack
hooks:
  PreToolUse:
    - matcher: Bash
      command: "audit-log.sh"
---

你是一位代码安全审计专家。你的职责是:

1. 分析代码中潜在的安全漏洞
2. 检查常见的攻击向量(XSS、SQL 注入、CSRF 等)
3. 生成结构化的审计报告
4. 提供修复建议

报告格式:
- **漏洞级别**:Critical / High / Medium / Low
- **影响范围**:受影响的文件和函数
- **修复建议**:具体的代码修改方案

parseAgentFromMarkdown() 函数解析这个 frontmatter,将 name 映射为 agentType,将 Markdown 正文作为系统提示。如果 memory 字段启用,系统还会自动注入文件读写工具以支持持久化记忆。

更多自定义 Agent 案例

案例一:文档生成 Agent

---
name: doc-writer
description: 为代码生成 API 文档和使用指南
tools:
  - Read
  - Grep
  - Glob
  - Write
disallowedTools:
  - Bash
model: haiku
effort: medium
maxTurns: 20
color: blue
background: true
---

你是一位技术文档专家。你的职责是:

1. 阅读代码中的函数签名、类型定义和注释
2. 生成结构化的 API 文档
3. 为复杂逻辑编写使用示例
4. 保持文档风格与现有文档一致

输出要求:
- 使用 Markdown 格式
- 包含参数说明、返回值说明、使用示例
- 标注 deprecated 的 API

案例二:测试生成 Agent

---
name: test-generator
description: 为指定模块自动生成单元测试和集成测试
tools:
  - Read
  - Write
  - Grep
  - Glob
  - Bash
model: inherit
effort: high
maxTurns: 40
color: green
background: true
---

你是一位测试工程师。你的任务是:

1. 阅读目标模块的源码,理解其公开接口和内部逻辑
2. 识别所有需要测试的代码路径
3. 为每个路径编写测试用例
4. 确保测试覆盖:正常路径、边界条件、错误处理

测试原则:
- 测试应该独立、可重复
- 使用有意义的测试描述
- Mock 外部依赖,不 Mock 被测模块内部函数

案例三:性能分析 Agent

---
name: perf-analyzer
description: 分析代码的性能瓶颈并给出优化建议
tools:
  - Read
  - Grep
  - Glob
  - Bash
disallowedTools:
  - Write
  - Edit
model: inherit
effort: high
maxTurns: 25
color: yellow
---

你是一位性能分析专家。你的任务是:

1. 识别代码中的性能热点(O(n^2) 循环、不必要的内存分配、频繁的 I/O)
2. 分析数据库查询效率(N+1 查询、缺失索引)
3. 评估并发和异步模式的使用是否合理
4. 给出具体的优化建议和预估的性能提升

报告格式:
- **问题编号**和严重程度(Critical/High/Medium/Low)
- **代码位置**:文件名:行号
- **问题描述**:为什么这里慢
- **优化建议**:具体的修改方案
- **预期提升**:量化的性能改善预估

交叉引用: 自定义智能体的 hooks 配置与第8章的 Hook 系统使用相同的格式。你可以为智能体定义 PreToolUse、PostToolUse 等生命周期钩子,实现诸如审计日志、自动格式化等横切关注点。


9.5 智能体工具隔离

全局禁止列表

系统定义了全局禁止列表,所有子智能体都不能使用这些工具,包括:防止递归获取输出的工具、Plan mode 相关工具(仅主线程可用)、Agent 工具(防止递归嵌套)、向用户提问的工具、任务停止工具(需要主线程任务状态)。

graph TD
    subgraph 第一层["第一层:全局禁止列表(所有子智能体)"]
        G1["Agent, ExitPlanMode"]
        G2["TaskOutput, TaskStop"]
        G3["AskUserQuestion"]
    end

    subgraph 第二层["第二层:异步白名单(后台智能体)"]
        W1["Read, Write, Edit, Bash"]
        W2["Grep, Glob"]
        W3["WebSearch, WebFetch + MCP 工具"]
    end

    subgraph 第三层["第三层:filterToolsForAgent(最终仲裁)"]
        F1["MCP 工具 → 始终可用"]
        F2["全局禁止 → 一律排除"]
        F3["异步智能体 → 白名单过滤"]
        F4["进程内队友 → 部分豁免"]
    end

    第一层 --> 第二层 --> 第三层

    style 第一层 fill:#ff6b6b,stroke:#c0392b,color:#fff
    style 第二层 fill:#f39c12,stroke:#d68910,color:#fff
    style 第三层 fill:#3498db,stroke:#2471a3,color:#fff

异步智能体白名单

异步(后台)智能体的工具集进一步受限为白名单集合,包括 Read、Write、Edit、Bash、Grep、Glob、WebSearch 等核心工具,但不包括 TaskOutput、Agent 等可能导致递归的工具。

filterToolsForAgent

工具过滤函数是最终的仲裁者,实现三层过滤机制:MCP 工具始终可用;全局禁止工具一律排除;异步智能体只能使用白名单工具,但进程内队友可以豁免部分限制。

设计考量:为什么 MCP 工具始终可用?

MCP 工具是由用户显式配置的外部服务,它们经过了用户授权(通过权限配置)。与内置工具不同,MCP 工具的行为由外部服务器定义,Claude Code 无法预知其功能范围。因此,系统选择不限制子智能体使用 MCP 工具,而是将安全控制交给 MCP 的权限系统(参见第12章)。


9.6 子智能体生命周期管理

子智能体从创建到销毁经历完整的生命周期。理解这个生命周期对于设计高效的多智能体协作流程至关重要。

生命周期状态转换

stateDiagram-v2
    [*] --> Created: Agent 工具被调用
    Created --> ContextBuilt: 解析智能体定义
    ContextBuilt --> Running: 构建系统提示、工具池、MCP 连接
    Running --> WaitingForTool: 等待工具调用结果
    WaitingForTool --> Running: 工具结果返回
    Running --> Completed: maxTurns 达到 / 任务完成 / 错误发生
    Completed --> CleanedUp: 生成任务通知
    CleanedUp --> [*]: 清理 MCP 连接、释放资源
    Running --> Killed: 用户中断
    Running --> AsyncComplete: 后台模式完成
    AsyncComplete --> [*]: 通知主智能体

    note right of Created
        智能体定义被解析
    end note

    note right of ContextBuilt
        构建系统提示、工具池、
        MCP 连接初始化
    end note

    note right of CleanedUp
        清理 MCP 连接、释放资源
    end note

上下文构建阶段

在子智能体开始执行之前,运行器会构建完整的执行上下文。这个过程包括:

  1. 系统提示渲染:根据智能体定义生成系统提示,注入工具描述、权限信息等
  2. 工具池组装:根据 allowedTools/disallowedTools 和全局过滤函数确定可用工具集
  3. MCP 服务器初始化:为智能体专属的 MCP 服务器建立连接
  4. Skill 预加载:加载智能体定义中指定的技能
  5. 内存注入:如果启用了 memory 选项,注入持久化记忆内容

资源清理阶段

子智能体完成或失败后,运行器负责清理资源:

  • 关闭智能体专属的 MCP 服务器连接(注意:非专属连接由父级管理)
  • 释放工具实例引用
  • 将结果格式化为任务通知传递给调用者
  • 如果是后台智能体,将通知排队等待主智能体下一轮处理

最佳实践:合理设置 maxTurns

maxTurns 控制子智能体的最大执行轮次。设置过低可能导致任务未完成就被中断;设置过高可能浪费 token 在无效的循环中。推荐的经验值: - 只读搜索任务:10-15 轮 - 代码生成任务:20-30 轮 - 复杂重构任务:30-50 轮 - 探索性任务:15-20 轮

对于后台运行的智能体,建议设置较低的 maxTurns 作为安全阀,防止失控的智能体持续消耗资源。


实战练习

练习 1:创建一个自定义代码审查智能体

在项目的 .claude/agents/ 目录下创建 code-reviewer.md,定义一个专门用于代码审查的智能体。要求: - 只读权限(禁止 Write、Edit) - 使用 haiku 模型以降低成本 - 输出结构化的审查报告 - 设置 maxTurns 为 15(审查任务不需要太多轮次) - 启用 background 模式(审查不需要阻塞主流程)

扩展挑战: 为代码审查智能体添加一个 MCP 服务器连接,接入一个外部的代码质量检测服务。

练习 2:分析 Fork 模式的缓存效率

假设父对话有 100 条消息(约 80,000 token),父智能体一次性发出了 3 个 Fork 调用。根据 Fork 消息构建机制,计算: - 第一个 Fork 子智能体的缓存前缀长度 - 第二、第三个 Fork 子智能体能复用多少缓存 - 如果不使用 Fork 模式,3 个子智能体需要传输多少 input token

思考题: 如果 3 个 Fork 子智能体的任务描述分别是 200、350、180 token,它们各自的新增 token 是多少?缓存命中率分别是多少?

练习 3:探索 Verification Agent 的对抗策略

查阅 Verification Agent 的系统提示设计理念,列出其中定义的所有"自我合理化借口",并思考为什么这些对 LLM 验证任务是必要的。

深度思考: 如果你要设计一个"代码审查 Agent",应该采用 Verification Agent 的对抗性风格,还是采用更协作的风格?在什么场景下对抗性更合适?在什么场景下协作性更合适?

练习 4:设计一个多智能体协作流程

设计一个完整的软件开发流程,使用以下内置智能体组合:

  1. 用户提出需求:"为用户认证模块添加 OAuth2 支持"
  2. 使用 Explore Agent 调查现有的认证代码结构
  3. 使用 Plan Agent 制定实施计划
  4. 使用 General Purpose Agent 执行实施
  5. 使用 Verification Agent 验证实现

写出每一步的输入和预期输出。


关键要点

  1. AgentTool 的三层智能体体系(内置、自定义、插件)提供了从开箱即用到深度定制的完整光谱,优先级机制允许企业级覆盖。自定义智能体通过 Markdown 文件定义,降低了创建门槛。

  2. 内置智能体各司其职:Explore 专注高速只读搜索,Plan 做结构化规划,General Purpose 是万能选手,Verification 采取对抗性验证策略。四者组合覆盖了软件工程的核心工作流。

  3. Fork 模式的核心创新是通过字节级继承实现 prompt cache 共享:相同的系统提示、工具定义、消息前缀加上统一的占位符结果,最大化缓存命中面积。在大规模并行场景下可节省 60%+ 的 token。

  4. CacheSafeParams 的五个维度(system prompt、user context、system context、tool context、messages)是缓存命中的充分必要条件,任何维度的偏离都会导致缓存失效。这就是为什么 Fork 路径优先传递已渲染的原始字节。

  5. 工具隔离的三层防线(全局禁止列表、异步白名单、filterToolsForAgent 过滤)确保子智能体不会产生递归、不会越权、不会在后台模式下阻塞用户交互。

  6. 子智能体生命周期管理从创建到清理经过完整的状态转换。合理设置 maxTurns、选择合适的 model、正确配置工具集,是高效使用子智能体的关键。

  7. 对抗性验证哲学不仅是一种技术选择,更是一种工程文化的体现——与其相信代码"应该能工作",不如主动尝试证明它不能工作。