你选了 Opus,但干活的是 Haiku:从三个 md 文件看 Claude Code 的模型路由

本文基于以下三份资料:

  1. Claude Code v2.1.88 反混淆源码(sherry255/civil-engineering-cloud-claude-code-source-v2.1.88,2026年4月初)
  2. anthropics/claude-code 官方仓库 v2.1.88 tag 下的 plugin 文件
  3. 本地已安装的 Claude Code plugin marketplace

后续版本可能已有改动。文章经过一轮事实核对修订,subagent 路由这套机制本身在快速迭代,如发现与当前版本不符欢迎指正。

起因是我在看 Claude Code 的 plugin 目录时,发现了三个看起来都叫”code review”的文件:

1
2
3
plugins/code-review/commands/code-review.md
plugins/feature-dev/agents/code-reviewer.md
plugins/pr-review-toolkit/agents/code-reviewer.md

想弄清楚这三个文件和底层源码之间是什么关系——md 里写的 model: sonnet 到底是怎么被执行的,谁说了算。顺着这条线挖下去,发现了一些有意思的事情。


一、三个文件是三种不同的东西

文件 A:slash command(工作流编排 prompt)

code-review/commands/code-review.md 是你输入 /code-review 时触发的 slash command。严格来说,它不是”硬编码的工作流”,而是一段强引导的 prompt:命令正文被 loadSkillsDir.ts 加载后喂给主模型,主模型按正文里的指示去调用子 agent。不是编译期强制,是 LLM 按指令执行。

v2.1.88 tag 下实际的步骤(来自 官方仓库):

1
2
3
4
5
6
7
8
9
10
11
12
13
Step 1:  Haiku agent × 1  — 资格检查(PR 是否关闭/草稿/不需要 review 等)
Step 2: Haiku agent × 1 — 收集相关 CLAUDE.md 路径
Step 3: Sonnet agent × 1 — PR 摘要
Step 4: 4 个并行 agent:
- Agent 1+2: Sonnet × 2(CLAUDE.md 合规检查)
- Agent 3: Opus × 1(bug 扫描)
- Agent 4: Opus × 1(逻辑/安全问题)
Step 5: 每个 Step 4 发现的 issue 并行启动一个验证 subagent:
- bug 类 issue → Opus
- CLAUDE.md 类 issue → Sonnet
Step 6: 过滤未通过验证的 issue
Step 7: 输出摘要(有 --comment 才继续)
Step 8-9: 组织并发内联评论

也就是说,Opus 在这个流程里是参与的——step 4 的 bug agent 和 step 5 的 bug 验证都是 Opus,只是审阅维度被拆分成了”合规用 Sonnet、bug 用 Opus”。我之前写的”流程里没有一个子任务是 Opus”是错的,在此更正。

你的 /model 选择对这个流程的影响:命令正文里用”Launch a haiku agent”、”Launch 4 agents in parallel: … Opus bug agent”这种语言强引导主模型,主模型按指令调用 Agent 工具(源码里正式名是 AgentTool,旧名 Task,两者是同一个东西;下文统一写成 Agent/Task 工具 避免混淆)并传对应的 model 参数。用户手动 /model sonnet 不会直接覆盖这些传参——因为 Agent/Task 工具的 model 参数优先级高于父模型继承(见第二节的优先级表)。不过这不是”编译期写死”,理论上主模型可以违背指令,只是正常情况下它会遵守。

文件 B 和 C:subagent(被调用的积木)

另外两个是 subagent,结构简单:接收代码 diff,返回 confidence ≥ 80 的 issue 列表。它们不是给用户直接触发的,是被别的 command 内部调用的。

文件 B(feature-dev) 文件 C(pr-review-toolkit)
模型 model: sonnet model: opus
触发时机 /feature-dev Phase 6,并行起 3 个做质量检查 /pr-review-toolkit:review-pr 命令正文里默认总会包含它
使用节奏 开发内循环,频繁 PR 提交前,低频

文件 A 和文件 B/C 完全没有关系。 /code-review Step 4 里的 4 个并行 agent(2 Sonnet + 2 Opus)是 inline 临时 agent,不是 code-reviewer subagent。三个文件只是名字相似,彼此独立。

为什么 B 和 C 各自带一份而不共用?

Claude Code 的 plugin 系统其实支持跨包依赖——src/utils/plugins/dependencyResolver.ts 有完整的 manifest.dependencies 解析、依赖校验、跨 marketplace 依赖规则。所以说 plugin 系统”禁止跨包依赖”是错的,这是我之前的误读。

但 feature-dev 和 pr-review-toolkit 的作者选择了各自带一份 code-reviewer,可能的原因是:两个 plugin 的使用场景节奏不同,需要不同的模型默认值和 system prompt,直接复用会导致行为冲突;而且 plugin 作者之间没有协调,各写各的更简单。

所以两份文件实际上有差异

  • feature-dev 的 code-reviewer.md 默认 model: sonnet,用于开发循环的快速检查
  • pr-review-toolkit 的 code-reviewer.md 默认 model: opus,用于提交前的精细审阅

内容大约 95% 相同,但 frontmatter 的 model 字段和 description 里的 “proactive use” 措辞不同。


二、md 文件里的 model 字段,在源码里怎么执行

这是我最想弄清楚的问题。

源码在 src/utils/model/agent.tsgetAgentModel() 函数,优先级从高到低:

1
2
3
4
1. CLAUDE_CODE_SUBAGENT_MODEL 环境变量(全局强制覆盖)
2. Agent/Task 工具调用时显式传的 model 参数
3. Agent 定义里的 model 字段(md frontmatter 或代码里写死)
4. 默认:'inherit',继承父 agent 的模型

所以 md 文件里写 model: sonnet,就是第 3 层。优先级不算高,可以被第 2 层(Agent/Task 工具调用时的显式 model 参数)覆盖——这也是 /code-review 能做到”调用同一个 agent 类型但传不同模型”的机制。

需要说明的是:Agent/Task 工具的 model 参数允许每次调用时动态传入src/tools/AgentTool/AgentTool.tsx:86),它的优先级高于 md frontmatter。主模型是否主动传参,取决于当前 prompt 有没有引导它这样做——比如 slash command 正文里明确写”Launch a haiku agent”时,主模型就会传 model: haiku。所以 md frontmatter 的模型设定不是板上钉钉,只是在没人覆盖时的默认值。

同族继承是一个细节:如果你选了 claude-opus-4-6,md 里写 model: opus,subagent 继承的是精确型号 claude-opus-4-6,而不是 provider 默认的 opus 版本。这防止了 Vertex/Bedrock 用户意外降级。


三、md 文件是怎么被加载和调用的

只有理解了完整的加载和调用链,才能回答下一个关键问题:哪些 md 文件什么时候会被实际触发?

加载链路

启动时 Claude Code 扫描所有 plugin 的 agents/ 目录,调用链如下:

1
2
3
4
5
6
7
8
9
10
main.tsx:2029
→ getAgentDefinitionsWithOverrides(currentCwd)
→ loadPluginAgents() // src/utils/plugins/loadPluginAgents.ts:231-344
→ loadAllPluginsCacheOnly() // 从 pluginLoader 拿到启用的 plugin 列表
→ 遍历每个 plugin 的 agentsPath
→ walkPluginMarkdown() // src/utils/plugins/walkPluginMarkdown.ts:21-69
// 递归找所有 .md,记录 namespace 子路径
→ loadAgentFromFile() // loadPluginAgents.ts:65-229
→ fs.readFile + parseFrontmatter
→ 组装 AgentDefinition 对象

关键:agentType 的命名规则loadPluginAgents.ts:89-90):

1
2
const nameParts = [pluginName, ...namespace, baseAgentName]
const agentType = nameParts.join(':')

所以 feature-dev/agents/code-reviewer.md 加载后的 agentTypefeature-dev:code-reviewer。两个 plugin 各自有 code-reviewer.md 时,因为 plugin 名前缀不同,agentType 不会冲突。

AgentDefinition 数据结构

每个 md 文件加载后变成一个对象(loadAgentsDir.ts:106-159),核心字段:

字段 来源 用途
agentType plugin名 + namespace + name 精确匹配的 ID
whenToUse frontmatter 的 description 给主 agent 看的”何时使用我”
model frontmatter 的 model subagent 用什么模型
tools frontmatter 的 tools subagent 能用哪些工具
getSystemPrompt md 正文(去掉 frontmatter) subagent 启动时的 system prompt

加载完后存在 toolUseContext.options.agentDefinitions.activeAgents 里,所有 Agent/Task 工具调用都从这里查。

主 agent 怎么知道有哪些 subagent

这是最关键的一步。源码在 src/tools/AgentTool/prompt.ts:43-46

1
2
3
function formatAgentLine(agent: AgentDefinition): string {
return `- ${agent.agentType}: ${agent.whenToUse} (Tools: ${toolsDescription})`
}

每个 plugin agent 被拼成一行:

1
2
3
- feature-dev:code-reviewer: Reviews code for bugs, logic errors... (Tools: Glob, Grep, Read, ...)
- pr-review-toolkit:code-reviewer: Use this agent when you need to review code... (Tools: ...)
- pr-review-toolkit:silent-failure-hunter: ...

这些行会通过两条路径之一被主 agent 看到(具体走哪条由 feature flag 控制,见 prompt.ts:190attachments.ts:1490):

  1. 直接注入到 Agent/Task 工具的 tool description 里——主 agent 在每次需要调用 Agent/Task 工具时能看到这个完整列表
  2. 作为 agent_listing_delta attachment 单独发送——不放在 tool description,而是作为消息 attachment 传递给主 agent

不管走哪条路径,最终效果相同:主 agent 能看到这个 agent 列表,并据此选择 subagent_type 参数。

主 agent 怎么决定调哪个

它读 description 做语义判断。 不是规则匹配,是 LLM 自己决定。

主 agent 看到这一长串 agent 列表,根据当前用户的需求(”帮我审一下这段代码”),自己挑一个最合适的 agentType,然后传给 Agent/Task 工具:

1
2
3
4
Agent({
subagent_type: "pr-review-toolkit:code-reviewer",
prompt: "...",
})

匹配是精确字符串相等AgentTool.tsx:345):

1
const found = agents.find(agent => agent.agentType === effectiveType)

找不到就报错 Agent type 'X' not found

两条完全独立的触发路径

所以 plugin subagent 会在两种情况下被调用:

路径 A:slash command 内部显式调用

slash command 的 md 正文里写”用 X agent 做这件事”,主 agent 按指令调。比如 /feature-dev 的 Phase 6 写明:

Launch 3 code-reviewer agents in parallel with different focuses

主 agent 看到这条,去找 feature-dev:code-reviewer,调三次。

路径 B:主 agent 自主语义判断

没有 slash command,纯日常对话。你说”帮我看看这段代码有没有问题”,主 agent 看到 pr-review-toolkit:code-reviewer 的 description 写着”should be used proactively after writing or modifying code”,可能会决定调它——但也可能判断这个任务太简单,自己直接看就行。不需要你输入任何斜杠命令

这意味着:你装的 plugin 的 agents,其中一部分会被暴露到主 agent 的”工具列表”里。实际暴露前还会经过几层过滤(见 loadAgentsDir.ts:359attachments.ts:1503):

  • MCP 依赖过滤:agent 声明依赖的 MCP 工具不可用时会被跳过
  • 权限 deny 规则:被 deny 列表禁用的 agent 不会出现在列表里
  • allowedAgentTypes 白名单:如果配置了白名单,只有白名单里的 agent 会被暴露

通过过滤的 agent 会出现在主 agent 看到的列表里,主 agent 可能在你不知道的情况下调用它们——那个 agent 用什么模型,取决于 md frontmatter 和调用时传的 model 参数。

怎么追踪某次任务实际调了哪些 agent

如果你想知道每次 Claude Code 实际调了哪些 subagent,最直接的方法是配一个 PostToolUse hook。PostToolUse hook 的输入是 stdin 的 JSON(不是环境变量),需要用 jq 读:

1
2
3
4
5
6
7
8
9
10
11
{
"hooks": {
"PostToolUse": [{
"matcher": "Agent|Task",
"hooks": [{
"type": "command",
"command": "jq -c '{tool_name, subagent_type: (.tool_input.subagent_type // null), model: (.tool_input.model // null), prompt: (.tool_input.prompt // null)}' >> /tmp/agent-calls.log"
}]
}]
}
}

这样每次 Agent/Task 调用都会记录 tool_namesubagent_typemodelprompt。特别是 model 字段——它会告诉你这次调用实际传了什么模型参数,和 frontmatter 默认值是否一致。


四、所有 plugin subagent 的全貌

之前只看了三个 code-review 相关的 md,是因为搜关键词搜出来的。但实际上本地 claude-plugins-official marketplace 子树里有 19 个 subagent,分布在 7 个不同的 plugin 里。

统计范围:仅限 claude-plugins-official 这一个官方 marketplace 的本地安装目录快照(路径 ~/.claude/plugins/marketplaces/claude-plugins-official/plugins/)。如果你装了其他 marketplace(比如 claude-plugins-community),它们下的 subagent 不在本次统计内,数字会不一样。

统计快照:2026-04-15。统计命令:

1
2
3
4
find ~/.claude/plugins/marketplaces/claude-plugins-official/plugins -path "*/agents/*.md" | while read f; do
model=$(grep "^model:" "$f" | head -1 | awk '{print $2}')
echo "${model:-inherit} | $f"
done

官方 marketplace 还在持续更新,数字可能随时变化。

把所有 agents/*.md 的 model 字段全部扒出来:

模型 数量 哪些 agent
硬编码 sonnet 6 feature-dev:code-architectfeature-dev:code-explorerfeature-dev:code-revieweragent-sdk-dev:agent-sdk-verifier-pyagent-sdk-dev:agent-sdk-verifier-tsplugin-dev:agent-creator
硬编码 opus 3 pr-review-toolkit:code-reviewerpr-review-toolkit:code-simplifiercode-simplifier:code-simplifier
inherit(继承主模型) 10 hookify:conversation-analyzerplugin-dev:plugin-validatorplugin-dev:skill-reviewerpr-review-toolkit:comment-analyzerpr-review-toolkit:pr-test-analyzerpr-review-toolkit:silent-failure-hunterpr-review-toolkit:type-design-analyzerskill-creator:analyzerskill-creator:comparatorskill-creator:grader

几个观察

  1. feature-dev 整个 plugin 全是 Sonnet。只要被触发,feature-dev 的 subagent 默认跑 Sonnet(除非调用方传了 model 参数覆盖)。
  2. pr-review-toolkit 是混合。核心的 code-reviewercode-simplifier 是 Opus,其余四个分析器都是 inherit。
  3. inherit 不是多数。10/19 是 inherit,剩下 9 个在 frontmatter 里写了具体模型。

这里要小心一个推论。“装 plugin 越多,/model 能影响的范围越小” 这个说法不严谨

  • 首先,一个 agent 即使 frontmatter 写了具体 model,调用方仍然可以在 Agent/Task 工具里传参覆盖
  • 其次,新装的 plugin 可能全是 inherit
  • 最后,很多 plugin agent 可能因为过滤或根本不被主 agent 选中,完全不影响实际路由

更准确的说法是:你不容易预先知道”当我选 Opus 时,哪些子任务会用 Opus”——因为这取决于 plugin 作者在 md 里写的默认值 + 主 agent 当时的调用决策 + 调用时是否传了 model 覆盖参数。除非你开 hook 日志实际跑一遍,否则只能靠猜。


五、Explore agent:外部用户和内部员工的不同待遇

挖源码时发现了一个更有意思的地方。

src/tools/AgentTool/built-in/exploreAgent.ts,第 76-78 行:

1
2
3
// Ants get inherit to use the main agent's model; external users get haiku for speed
// Note: For ants, getAgentModel() checks tengu_explore_agent GrowthBook flag at runtime
model: process.env.USER_TYPE === 'ant' ? 'inherit' : 'haiku',

ant 是 Anthropic 内部员工的简称——Anthropic 缩写成 ant。公司内部项目代号是 Tengu(天狗),源码里的 feature flag 大多以 tengu_ 开头(比如 tengu_amber_stoat)。

要诚实地说明几件事

  1. 上面代码里的第二行注释说 “For ants, getAgentModel() checks tengu_explore_agent GrowthBook flag at runtime”,但我查了 src/utils/model/agent.tsgetAgentModel() 完整代码,没有任何对 tengu_explore_agent 的实际判断。这句话只是注释,不是验证过的实现。可能是注释忘了更新,也可能是运行时判断在别的地方。我之前把注释当作已验证的事实引用,不严谨。

  2. 同样要修正的是关于 USER_TYPE 的说法。src/tools/REPLTool/constants.ts 的注释说 “USER_TYPE is a build-time –define”,但这只针对 ant-native binary 的特定场景。源码里其他地方(比如 src/tools.ts:214)仍然直接运行时读 process.env.USER_TYPE === 'ant' 来决定是否加载某些工具——也就是说 USER_TYPE 同时存在编译时 define 和运行时读环境变量两条路径。我之前断言”运行时无法修改”是过头了。

那事实层面到底是什么?

可以确定的:在 v2.1.88 exploreAgent.ts 这一行,process.env.USER_TYPE === 'ant' 是在模块定义时求值的(ES module 顶层),所以如果你拿到的二进制在启动时 USER_TYPE 不是 'ant',这里就是 'haiku'

不能确定的:是否存在某种运行时配置能绕开这里。你可以试着用 USER_TYPE=ant claude 启动看看 Explore agent 会不会变成 inherit——我没有实测过。

确定的底线:这行代码在两种 USER_TYPE 下行为不同,默认的外部发布构建里,Explore agent 的 model 字段是 'haiku'

  • USER_TYPE === 'ant':Explore agent 的 model 字段是 'inherit'
  • 默认(外部发布):model 字段是 'haiku'

这不意味着”搜代码一定用 Haiku”

需要澄清一个我之前写过头的地方。我之前说”Explore agent 是日常编码里最高频的 subagent——搜文件、找实现,背后全是它在跑”,这是错的。

主 agent 本身就有 BashGlobGrepFileRead 这些工具(见 src/tools.ts:195-220),完全可以自己搜索代码,不一定要通过 Explore subagent。Explore 只是一个可选的委托对象,不是搜索的必经路径。

主 agent 什么时候会选择 Explore?通常是:需要大范围探索、希望保护主上下文不被污染、或者明确知道要快速返回结果时。简单的”grep 一下这个字符串”,主 agent 大概率会自己用 Grep 工具做,不会委托。

怎么绕过 Explore 的 Haiku 硬编码

两种方式:

  1. 全局覆盖(粗粒度)

    1
    CLAUDE_CODE_SUBAGENT_MODEL=opus claude

    或在 settings.json 里设 env.CLAUDE_CODE_SUBAGENT_MODEL。所有 subagent 强制 Opus,没有细粒度。

  2. Agent/Task 工具调用时显式传 model(细粒度)
    Agent/Task 工具 schema 允许传 model: 'opus' | 'sonnet' | 'haiku',优先级高于 agent frontmatter/代码硬编码(见第二节优先级表)。但这需要主 agent 主动传参,你没法直接控制。

第 1 条是目前最稳妥的绕过方式。


六、把这些串起来

从三个 md 文件出发,挖到的东西是:

md 文件是 Claude Code plugin 体系的”声明层”,用 frontmatter 的 model 字段声明这个 agent 用什么模型。源码的 getAgentModel() 是”执行层”,负责把声明翻译成实际的 API 调用模型,同时处理优先级覆盖、同族继承、跨平台前缀等逻辑。

两层之间的关系是清晰的,md 里写什么基本就是什么——除非被更高优先级的机制覆盖。

但有一类 agent 不走 md,而是在 TypeScript 代码里直接定义:内置 agent(Explore、Plan、general-purpose 等)。这些 agent 的模型设定在源码里写死,其中 Explore agent 在默认情况下对外部用户是 Haiku——不过它仍然可以被 CLAUDE_CODE_SUBAGENT_MODEL 环境变量、Agent/Task 工具调用时的 model 参数、或用户/项目/策略层的同名 agent 文件覆盖(见第二节优先级表和第九节的 activeAgents 覆盖链)。

所以 /model opus 的实际影响范围是:

  • ✅ 主对话(Claude 直接回答你的问题)
  • ✅ general-purpose agent(model 字段未设定,默认 inherit)
  • ✅ Plan agent(model: 'inherit' 写死在代码里)
  • ✅ plugin subagent 里写了 model: inherit 或不写 model 的
  • ⚠️ Explore agent:默认外部构建下是 'haiku',除非你跑 USER_TYPE=ant 或设 CLAUDE_CODE_SUBAGENT_MODEL
  • ⚠️ /code-review slash command 里的子任务:正文用”Launch a haiku agent / Opus bug agent”这种语言强引导主模型去传 model 参数。主模型通常会照做,但这是 prompt 引导不是编译期强制
  • ⚠️ plugin subagent 里写了具体模型的(feature-dev:* 是 sonnet,pr-review-toolkit:code-reviewer 是 opus 等):这些是默认值,调用方可以传 model 参数覆盖

你以为选了 Opus 就是全程 Opus。实际上,主 agent 是 Opus,但它干很多事情是通过委托完成的,委托出去的那部分用什么模型,取决于 plugin 作者、slash command 正文、和主 agent 当时的调用决策。


七、如何强制全程 Opus

既然 /model opus 管不到 subagent,真正的解决方案是用优先级最高的环境变量——在 ~/.claude/settings.json 里设:

1
2
3
4
5
6
{
"env": {
"CLAUDE_CODE_SUBAGENT_MODEL": "opus"
},
"model": "opus"
}

优先级关系CLAUDE_CODE_SUBAGENT_MODEL 是源码里第一层判断,任何 md frontmatter 里写的 model: sonnet、代码里硬编码的 model: haiku,都会被它覆盖。

代价:无法细粒度控制。/code-review 里原本被指示用 Haiku 的资格检查、原本用 Sonnet 做 CLAUDE.md 合规检查的任务,全部会变成 Opus。如果你按 token 付费,成本会显著上升。如果是 Max 订阅,无所谓。

重启生效settings.json 在 Claude Code 启动时读取,已经在跑的进程需要退出重启(Ctrl+C 后重新运行 claude)。


八、四种配置组合的完整对照

用户能控制的旋钮只有两个:/model(主对话模型)和 CLAUDE_CODE_SUBAGENT_MODEL(subagent 全局覆盖)。但用户不知道自己日常的每个操作背后触发的是哪个 agent。下面把两层都展开。

情况对照表

配置 主对话 Explore(搜代码/找文件) general-purpose / Plan feature-dev code-reviewer pr-review-toolkit code-reviewer /code-review inline agents
环境变量 opus + /model opus Opus Opus Opus Opus Opus 全 Opus
环境变量 opus + /model sonnet Sonnet Opus Opus Opus Opus 全 Opus
环境变量 sonnet + /model opus Opus Sonnet Sonnet Sonnet Sonnet 全 Sonnet
环境变量 sonnet + /model sonnet Sonnet Sonnet Sonnet Sonnet Sonnet 全 Sonnet

无环境变量时(默认):Explore 的 model 字段是 haiku(外部构建),feature-dev subagent 默认 Sonnet,pr-review-toolkit 的 code-reviewer/code-simplifier 默认 Opus,general-purpose/Plan 默认 inherit 主模型。但这些都是”默认值”,调用方传的 model 参数会覆盖它们。

用户能感知的操作 → 可能触发什么

需要说明:主 agent 是否委托给 subagent,是它自己的决策。以下是”在主 agent 选择委托时,通常会调哪个 agent”——不是每次都一定发生。

你在做什么 可能触发的 agent(也可能主 agent 自己处理)
直接问 Claude 问题,它回答 主对话(没有委托)
让 Claude 搜文件、找实现 主 agent 自己用 Grep/Glob/Read;只在大范围探索时才委托给 Explore
让 Claude 做复杂研究、跨文件分析 可能委托给 general-purpose
让 Claude 制定实现方案 可能委托给 Plan
/feature-dev 开发功能完成后的质量检查 按 slash command 正文指示调 feature-dev:code-reviewer 若干个
/pr-review-toolkit:review-pr pr-review-toolkit:code-reviewer + 其他专项 agent
/code-review review GitHub PR Haiku×2(资格/路径)+ Sonnet×3(摘要+CLAUDE.md 合规)+ Opus×2(bug 扫描/逻辑问题)+ 可变数量的验证 subagent(bug 用 Opus、合规用 Sonnet)

两个反直觉的组合

情况2(环境变量 opus + /model sonnet):主对话是 Sonnet,但所有 subagent 跑 Opus。搜个文件比你直接问问题还贵。

情况3(环境变量 sonnet + /model opus):你选了 Opus 主对话,但所有委托出去的任务——包括 pr-review-toolkit 里本来写了 model: opus 的 code-reviewer——都被压到 Sonnet。主 agent 是好的,但干活的全是次一级的。


九、未展开的三个更深入口

本文讲的是”模型路由”——声明层 md 和执行层 getAgentModel() 之间的关系。但 Claude Code 的真实调度架构还有几条本文没展开的线,下面只点到为止,细节留给后续文章。

1. activeAgents 的六层覆盖链src/tools/AgentTool/loadAgentsDir.ts:193

主 agent 实际看到的不是”plugin 写了什么就是什么”,而是 built-in / plugin / userSettings / projectSettings / flagSettings / policySettings 六层合并后的 activeAgents。后写的覆盖前写的——你可以在 ~/.claude/agents/ 放一个同名 agent 覆盖内置的 Explore,但企业策略层(policySettings)又可以反过来压你的用户层。不是”用户总能覆盖”,是”合并优先级”。

2. slash command frontmatter 里的 model 字段src/utils/processUserInput/processSlashCommand.tsx:864

本文第一节讲的是”命令正文通过 prompt 引导子 agent 用什么模型”。但 slash command 的 frontmatter 自己还能带一个 model 字段——processSlashCommand 把它作为本次 query 的主轮模型返回。也就是说,一条 /my-command 可以在自己的 frontmatter 里写 model: opus,直接把这次对话切到 Opus,不经过 /model。这和 subagent 的模型是两层不同的东西:一个是”这条命令本身用什么模型跑”,一个是”命令里再委托出去的 agent 用什么模型”。

配套还有一个 disableModelInvocationsrc/types/command.ts:189),决定命令是否出现在主模型的 SkillTool 可调用列表里——也就是控制”主模型能不能自己想到这招”。这个属于能力暴露边界,不属于模型路由。

3. 隐式 fork subagentsrc/tools/AgentTool/forkSubagent.ts:47

不是所有委托都走命名 subagent。当 Agent/Task 调用的 subagent_type 为空且 fork 实验 flag 开启时,Claude Code 会合成一个 FORK_AGENT——继承父模型、继承父的完整工具池、权限冒泡回父终端,完全不读 plugin frontmatter,不在 builtInAgents 列表里。它的存在是为了 prompt cache 共享(父和 fork 子的 API 请求前缀字节一致)。这条路径绕开了本文第二到第五节讲的所有分析框架。

注意 fork 是 feature flag 驱动的,不是默认路径——大多数会话不会触发,但如果你开了 hook 日志看到 subagent_type=null 的 Task 调用,那就是它。


合起来看,Claude Code 的委托架构不只是一套”模型路由”,它还有覆盖链(多源合并)、能力暴露边界(slash command 的 model + disableModelInvocation)、和隐式委托路径(fork)三层机制。本文只讲了路由这一层,剩下三条每一条都够写一篇。


附录:原始文件

附录 A:三个 md 文件

A1. plugins/code-review/commands/code-review.md(slash command,v2.1.88 官方仓库实际内容)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
---
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh pr comment:*), Bash(gh pr diff:*), Bash(gh pr view:*), Bash(gh pr list:*), mcp__github_inline_comment__create_inline_comment
description: Code review a pull request
---

Provide a code review for the given pull request.

**Agent assumptions (applies to all agents and subagents):**
- All tools are functional and will work without error.
- Only call a tool if it is required to complete the task.

To do this, follow these steps precisely:

1. Launch a haiku agent to check if any of the following are true:
- The pull request is closed
- The pull request is a draft
- The pull request does not need code review
- Claude has already commented on this PR
If any condition is true, stop and do not proceed.

2. Launch a haiku agent to return a list of file paths (not their contents) for all relevant CLAUDE.md files.

3. Launch a sonnet agent to view the pull request and return a summary of the changes.

4. Launch 4 agents in parallel to independently review the changes:
Agents 1 + 2: CLAUDE.md compliance sonnet agents (parallel)
Agent 3: Opus bug agent (parallel with agent 4) — scan for obvious bugs in the diff
Agent 4: Opus bug agent (parallel with agent 3) — security/logic issues in the changed code

5. For each issue found in step 4, launch parallel subagents to validate the issue.
Use Opus subagents for bugs and logic issues, and sonnet agents for CLAUDE.md violations.

6. Filter out any issues that were not validated in step 5.

7. Output a summary of the review findings to the terminal.
If `--comment` was NOT provided, stop here.

8. (If --comment) Create a list of comments to leave.

9. (If --comment) Post inline comments for each issue.

完整内容见 anthropics/claude-code 仓库 v2.1.88 tag


A2. plugins/feature-dev/agents/code-reviewer.md(subagent,Sonnet)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
---
name: code-reviewer
description: Reviews code for bugs, logic errors, security vulnerabilities, code quality issues, and adherence to project conventions, using confidence-based filtering to report only high-priority issues that truly matter
tools: Glob, Grep, LS, Read, NotebookRead, WebFetch, TodoWrite, WebSearch, KillShell, BashOutput
model: sonnet
color: red
---

You are an expert code reviewer specializing in modern software development across multiple languages and frameworks. Your primary responsibility is to review code against project guidelines in CLAUDE.md with high precision to minimize false positives.

## Review Scope

By default, review unstaged changes from `git diff`. The user may specify different files or scope to review.

## Confidence Scoring

Rate each potential issue on a scale from 0-100. Only report issues with confidence ≥ 80.

A3. plugins/pr-review-toolkit/agents/code-reviewer.md(subagent,Opus)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---
name: code-reviewer
description: Use this agent when you need to review code for adherence to project guidelines, style guides, and best practices. This agent should be used proactively after writing or modifying code, especially before committing changes or creating pull requests.
model: opus
color: green
---

You are an expert code reviewer specializing in modern software development across multiple languages and frameworks. Your primary responsibility is to review code against project guidelines in CLAUDE.md with high precision to minimize false positives.

## Issue Confidence Scoring

Rate each issue from 0-100. Only report issues with confidence ≥ 80.

Group issues by severity (Critical: 90-100, Important: 80-89).

附录 B:源码引用(v2.1.88)

B1. src/tools/AgentTool/built-in/exploreAgent.ts(关键行)

1
2
3
4
5
6
7
8
9
export const EXPLORE_AGENT: BuiltInAgentDefinition = {
agentType: 'Explore',
// ...
// Ants get inherit to use the main agent's model; external users get haiku for speed
// Note: For ants, getAgentModel() checks tengu_explore_agent GrowthBook flag at runtime
model: process.env.USER_TYPE === 'ant' ? 'inherit' : 'haiku',
omitClaudeMd: true,
getSystemPrompt: () => getExploreSystemPrompt(),
}

B2. src/utils/model/agent.ts(完整 getAgentModel 函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* Get the default subagent model. Returns 'inherit' so subagents inherit
* the model from the parent thread.
*/
export function getDefaultSubagentModel(): string {
return 'inherit'
}

export function getAgentModel(
agentModel: string | undefined,
parentModel: string,
toolSpecifiedModel?: ModelAlias,
permissionMode?: PermissionMode,
): string {
// 优先级 1:环境变量全局覆盖
if (process.env.CLAUDE_CODE_SUBAGENT_MODEL) {
return parseUserSpecifiedModel(process.env.CLAUDE_CODE_SUBAGENT_MODEL)
}

// 优先级 2:Agent/Task 工具调用时显式传的 model 参数
if (toolSpecifiedModel) {
if (aliasMatchesParentTier(toolSpecifiedModel, parentModel)) {
return parentModel
}
const model = parseUserSpecifiedModel(toolSpecifiedModel)
return applyParentRegionPrefix(model, toolSpecifiedModel)
}

const agentModelWithExp = agentModel ?? getDefaultSubagentModel()

// 优先级 3:agent 定义里的 model 字段,'inherit' 则继承父模型
if (agentModelWithExp === 'inherit') {
return getRuntimeMainLoopModel({
permissionMode: permissionMode ?? 'default',
mainLoopModel: parentModel,
exceeds200kTokens: false,
})
}

// 同族继承:父是 opus-4-6,子写 model: opus,则继承精确型号而非 provider 默认值
if (aliasMatchesParentTier(agentModelWithExp, parentModel)) {
return parentModel
}
const model = parseUserSpecifiedModel(agentModelWithExp)
return applyParentRegionPrefix(model, agentModelWithExp)
}

B3. src/tools/REPLTool/constants.ts(USER_TYPE 说明)

1
2
3
4
5
/**
* USER_TYPE is a build-time --define, so the ant-native
* binary would otherwise force REPL mode on every SDK subprocess regardless
* of the env the caller passes.
*/