教程Claude Code系列第 6 / 11 篇

Claude Code 子代理实战:写一个只读的测试评审员,让写的和查的分开

子代理是一份 Markdown 文件:给它一个名字、一段说明、一份工具清单,Claude Code 就能把一类任务交给它在独立的上下文里做。本站实测了一个只读的测试评审员,它查出了上一篇里补的测试的真问题。还试了让子代理换用便宜的模型,并记下通过中转站时遇到过的一条报错。

发布 · 约 11 分钟读完 · 经人工审核()· 本站的内容怎么来的

本站已实测,测试日期:2026-10-12。

本页目录(9 节)
  1. 你其实已经在用了
  2. 一个子代理就是一个文件
  3. 派它去干活
  4. 它查出了什么
  5. 工具清单是硬约束
  6. 让子代理用便宜一点的模型
  7. 另外两种调用方式
  8. 什么时候用子代理,什么时候不用
  9. 这次实测没有覆盖到的

这篇教程解决一个问题:有些活你不想让主对话自己干,或者不想让干活的那个自己检查自己,怎么办。

办法是子代理(subagent)。它是 Claude Code 在同一个会话里派出去的另一个“自己”:有独立的上下文、自己的一段系统提示词、一份你限定的工具清单。它做完之后只把结论交回来,中间读过的文件和跑过的命令不进你的主对话。

官方文档给的使用时机是:一件附带的任务会把你的主对话塞满搜索结果、日志、文件内容,而你之后不会再看它们,就交给子代理;同一类活你反复要派,就把它定义成一个自定义的子代理。

本站接着上一篇,写了一个只读的测试评审员,让它去审那篇里补的测试。

实测环境
日期 2026-10-11,子代理换模型一节在 10 月 12 日复测
版本 Claude Code 2.1.296
系统 Ubuntu 24.04
模型 Opus 5.5,auto 模式
连接方式 通过酷站AI 中转站,不是直连官方
示例仓库 Express 的源码,v5.3.0,带着上一篇新加的 6 个测试

你其实已经在用了

Claude Code 自带几个子代理,会在合适的时候自己派出去:

内置子代理 做什么 工具
Explore 搜索和分析代码库 只读,不能写和改
Plan plan 模式下替主对话做调研 只读
General-purpose 既要探索又要动手的多步任务 全部

所以你让它“看看这个项目的认证是怎么做的”,它很可能是派 Explore 去翻文件,自己只拿结论。这一篇讲的是怎么写自己的。

一个子代理就是一个文件

子代理是带一段头部信息的 Markdown 文件。放在哪决定谁能用:

位置 范围
.claude/agents/ 这个项目,可以提交给团队
~/.claude/agents/ 你的所有项目

我们在项目里建了 .claude/agents/test-reviewer.md:

---
name: test-reviewer
description: 审查测试代码的质量。新增或修改测试之后使用。只读,不修改任何文件。
tools: Read, Grep, Glob
model: inherit
---

你是一个只做审查的测试评审员。拿到要审查的测试文件后:

1. 读这个测试文件,以及它测的那段源码。
2. 逐个用例检查:
   - 断言检查的是对外的行为,还是实现细节?
   - 被测的代码如果坏了,这个用例会不会失败?有没有永远为真的断言?
   - 有没有把“碰巧如此”的行为写进断言?
   - 和文件里原有用例的写法是否一致?
3. 只报告确实值得改的问题。没有问题就直说没有,不要凑数。

输出格式:先用一句话给出结论,然后列出问题。每个问题写明:文件和行号、问题是什么、建议怎么改。
你不能修改文件。需要改的地方只给建议,由调用你的一方去改。

头部的几个字段:

字段 作用
name 名字,必填。调用时用它
description 必填。Claude 靠这句话判断什么时候该把任务交给它,所以要写清楚“什么情况下用”
tools 它能用的工具。不写就继承全部。这里只给了读文件和搜索,所以它改不了任何东西
model 用哪个模型。inherit 表示和主对话一样。换成别的模型的情况见下文

正文就是它的系统提示词。官方文档说明,子代理拿到的只有这段提示词加上工作目录这类基本信息,没有 Claude Code 完整的系统提示词。所以它该怎么做、输出什么格式,都要在这里写明白。

两个细节,都来自官方文档:

  • 一个项目里第一次建 agents 目录,要重启会话才能加载。 目录已经存在时,新增和修改文件几秒内就会生效。
  • description 占的是主对话的上下文。 我们这个子代理在 /context 里显示为 Custom agents: 73 tokens。子代理多了,说明文字要写短。

顺带一提:老教程里常说用 /agents 命令创建子代理。我们在 2.1.296 上输入它,得到的是:

The /agents wizard has been removed.

Ask Claude to create or update subagents for you (e.g. "create a code-reviewer subagent that ..."),
or edit the files directly:
  • .claude/agents/       (this project)
  • ~/.claude/agents/     (all projects)

这个向导已经去掉了。现在的做法是直接写文件,或者让 Claude 替你写。

派它去干活

在对话里点名就行:

用 test-reviewer 子代理审查 test/res.redirect.js 里最近新增的三个关于弃用警告的用例。等它做完,把结论告诉我。

屏幕上是这样的:

● Bash(git diff test/res.redirect.js)

● test-reviewer(Review redirect deprecation tests)
  ⎿  Backgrounded agent (↓ to manage · ctrl+o to expand)

● test-reviewer 子代理已经开始审查 test/res.redirect.js 里新增的三个弃用警告用例,结果还没出来。
  我让它重点核对这几件事:
  - 警告文本:断言里的消息是否和实现真正发出的一致。
  - 事件机制:process 上的 deprecation 事件会不会真的触发……
  - 第三个用例的写法:res.redirect('http://google.com', 301) 里的 301 是数字,可能根本测不到
    "status 不是数字"这个情况。
  - 断言质量:assert.ok 加 indexOf 是否不够精确,是否也该检查状态码和 Location 头。

✻ Waiting for 1 background agent to finish

  ● main
  ◯ test-reviewer  Review redirect deprecation tests                                    11s · ↓ 5.1k tokens

三点观察:

  • test-reviewer(...) 那一行就是委派。 括号里是主对话给这次任务起的简短说明。
  • 它是在后台跑的。 主对话没有被卡住,底部多出一个任务列表,能看到子代理在做什么、用了多少 token。官方文档说,交互会话里默认就是后台运行。
  • 任务说明是主对话写的。 我们只说了一句“审查这三个用例”,它替我们展开成了四个具体的检查点交给子代理。

1 分 8 秒后,子代理交回了结论。

它查出了什么

主对话转述的结论(有删节):

test-reviewer 审完了。三个用例都能通过,断言里的弃用消息文本也和 lib/response.js 的实现完全一致。但第 3 个
用例实际返回的是 500,测试没有发现。另外三个用例都没检查响应,也没检查到底发出了哪几条警告。下面是它读
代码得出的结论,我没有自己跑测试验证。

逐个用例
1. 缺少 url:会同时发出 'Provide a url argument' 和 'Url must be a string' 两条警告……
3. status 不是数字:只传两个参数时,'http://google.com' 会被当作 status,301 被当作 url。……
   - 但这次调用还会多发一条 'Url must be a string'。随后 this.status('http://google.com') 抛出 TypeError,
     最终响应是 500。
   - 测试里没写 .expect(状态码),supertest 不会把 500 当成错误,所以用例照样通过,这个问题被掩盖了。

修改建议
- 断言:现在的 assert.ok(indexOf !== -1) 太宽,失败时只报 false == true。建议改成 assert.deepStrictEqual
  比对完整的警告列表……
- 用例 3 最要紧:明确断言 .expect(500),并在用例名或注释里说明……

这是有分量的意见。上一篇里那三个用例通过了“改坏代码会失败”的验证,我们也复核过数字,但没有人注意到第 3 个用例的请求其实是以 500 结束的。写测试的那个会话知道这件事(它提过“参数写反会返回 500”),选择了不写进断言;评审员从另一个角度看,认为应该明确断言出来,否则这个 500 就一直藏着。

谁对可以讨论。要紧的是:这个分歧只有在换一个“人”来看的时候才会出现。

官方文档把这当作一种验证手段:让一个全新的模型去挑毛病,做事的那个就不是给自己打分的那个。

还要注意主对话加的那句话:“下面是它读代码得出的结论,我没有自己跑测试验证。”评审员是只读的,跑不了测试,它的结论来自读代码。主对话把这一点如实标了出来。

工具清单是硬约束

我们故意让它越界:

让 test-reviewer 子代理自己去改 test/res.redirect.js:给第 3 个用例加上 .expect(500)。你不要替它改。
这个没法照办:test-reviewer 被配置成只读,工具只有 Read、Grep、Glob,没有 Edit 或 Write,改不了
test/res.redirect.js。你又说了不要我替它改,所以我什么都没动,文件还是原样。

可以选一条路继续:
1. 由我来改……
2. 给 test-reviewer 加编辑权限:在 .claude/agents/test-reviewer.md 的 tools 里加上 Edit,它就能自己改了。
   但这样它就不再是只读的审查代理了。这个配置得你来决定,或者你明确让我改。
3. 另开一个有编辑权限的子代理(比如 general-purpose)去改这一处。

我们用 git diff --stat 确认过,文件没有变。

这和在提示词里写“请不要修改文件”不一样。没给它编辑工具,它就没有这个能力,不取决于它听不听话。

让子代理用便宜一点的模型

官方文档建议的一个省钱办法,是把简单的活交给便宜的模型,比如在子代理里写 model: sonnet 或 model: haiku。只用改头部的一行:

model: sonnet

主对话还是 Opus 5.5。让它先自己读一遍测试文件、说出判断,再派 test-reviewer 去审,子代理返回后主对话照常汇总,接着追问也正常。结束后看 /cost,两个模型是分开算的:

  Total cost:            $0.3573
  Usage by model:
       claude-opus-5-5:  1.3k input, 2.4k output, 174.6k cache read, 38.8k cache write ($0.2823)
     claude-sonnet-5-5:  242 input, 4.8k output, 18.4k cache read, 9.7k cache write ($0.0750)

评审那部分按 Sonnet 计价,这次是 0.075 美元。这一轮审的是另一个测试文件,活的大小和前面那次不一样,所以这个数不能直接拿去和前面比,只能说明子代理确实换了模型、也确实单独计了费。

model: haiku 我们没有试:这把 key 调不了 Haiku 5.5,见通过中转站用 Claude Code。

通过中转站时遇到过的一条报错

上面这个写法,我们第一次试的时候是坏的。派出子代理之后,主对话报了这个:

● test-reviewer(Review redirect deprecation tests)
  ⎿  Backgrounded agent (↓ to manage · ctrl+o to expand)

● API Error: 400 当前会话的 thinking 签名属于其他模型,请执行 /clear 新建空会话后重试

子代理自己跑完了,/cost 里两个模型都有用量。但主对话从派出子代理那一刻起,每一次请求都返回这条 400,再发消息也一样,子代理的结论拿不回来。

这条报错是中文的,来自中转站的网关,不是 Claude Code 也不是 Anthropic 的报错。它也不是每次都出:当时同样的配置跑了三次,两次报错,一次正常,差别在派出子代理之前主对话有没有产生过思考块。对照表放在通过中转站用 Claude Code那一篇里。

中转站一方随后做了修复。10 月 12 日我们专挑之前会出错的情况复测了三次,主对话都正常,上面那段 /cost 就是其中一次的。

如果你用的中转站出现这条报错,可以这样绕开:

  1. 按提示输入 /clear,清空对话。
  2. 把子代理文件里的 model: sonnet 改成 model: inherit,让它和主对话用同一个模型。

我们当时就是这么处理的,前面那次成功的评审用的就是 model: inherit,整个会话 /cost 显示 0.63 美元。代价是省不了钱,子代理和主对话一样用 Opus。

要说明这个结论的范围:

  • 只在酷站AI 这一家中转站上试过。 别家会不会这样,要看各自网关的实现。直连官方时,官方文档明确支持子代理用不同的模型。
  • 根因我们不知道,只确认了现象、它和思考块的关系、绕开的办法,以及修复之后三次都正常。
  • 官方文档里还有一个环境变量 CLAUDE_CODE_SUBAGENT_MODEL 可以给所有子代理指定模型,我们没有测它。

另外两种调用方式

按官方文档,除了在话里点名,还有:

用 @ 指定。 点名时由 Claude 决定要不要委派;用 @ 则保证是这个子代理来做:

@agent-test-reviewer 看一下 test/req.is.js 新增的用例

整个会话都用它。 启动时加参数,主线程自己就带上这个子代理的工具限制和模型:

claude --agent test-reviewer

这两种我们没有实测。

想让 Claude 更主动地用某个子代理,官方的建议是在它的 description 里写上“use proactively”这类说法。

什么时候用子代理,什么时候不用

官方文档的划分:

用主对话 用子代理
要来回讨论、反复调整 会产生一大堆你不需要留在主对话里的输出
计划、实现、测试几个阶段共用很多上下文 想硬性限制它能用的工具或权限
很快的小改动 任务自成一体,交回一个总结就够
在意等待时间:子代理是从零开始的,要花时间重新了解情况

几种相近的东西怎么分:

你想要的 用什么
一套可以反复用的步骤,在主对话里执行 skill
一个独立上下文里干活的角色,可以限制工具 子代理
对已经在对话里的内容问一句,不想留痕 /btw

还有一点成本上的提醒:子代理发的是它自己的请求,和主对话算在同一份用量里。它帮你省的是主对话的上下文,不是总的 token。

这次实测没有覆盖到的

  • 子代理换模型只在这一家中转站上测过,修复后跑了三次。model: haiku 没有测,直连官方时的表现我们也没有条件测。
  • 只测了点名调用。 @ 指定和 --agent 没有测。
  • 自动委派没有测。 我们每次都点了名,没有观察它会不会自己想起来用这个子代理。
  • 头部的其他字段没有测:permissionMode、maxTurns、skills、isolation: worktree 等,官方文档里有完整的列表。
  • 只跑了一次成功的评审。 它这次查出了真问题,不代表每次都有这个水平。

来源

通过 API 中转站使用这些模型?到 Veridrop 免费检测中转站给你的是不是它声称的模型,或者看看中转站排行榜前 100 名。

相关文章

  • Claude Code 的 agent view:一屏管住多个后台会话,以及让会话之间互相传话教程 · Claude Code · 约 14 分钟

    手上有几件互不相干的事要交给 Claude Code,不想开一排终端盯着。agent view 把它们变成一张表:谁在干活、谁在等你回答、谁做完了。本站在服务器上实测了派发、回答提问、进入对话、删除会话,以及让一个会话去问另一个会话。通过中转站都能用。

  • Claude Code 代理团队实战:让三个队友互相质疑着审同一段代码教程 · Claude Code · 约 12 分钟

    代理团队是一项实验功能:一个会话当组长,开出几个各有独立上下文的队友,队友之间能直接互发消息。本站实测了一个三人的评审团队:三个队友互发了 20 条消息,推翻了组长清单里的一条错误结论。也记下了协调上的毛病:有人没等别人的发现就交了报告,行号要组长复核。整个会话估算 1.51 美元。

  • CLAUDE.md、skill、hook、子代理、MCP、插件:Claude Code 的六种扩展各管什么,先用哪个教程 · Claude Code · 约 9 分钟

    Claude Code 的扩展方式有六七种,名字相近,容易用错地方。按官方的选型说明整理:每一种解决什么问题、什么信号出现时该加哪一种、相近的几种怎么区分、各占多少上下文。占用的数字用的是本站这个系列实测到的。

  • 从 Claude Code、Codex 过来:DeepSeek Harness 认哪些现成的配置教程 · DeepSeek Harness · 约 10 分钟

    按 DeepSeek Harness 官方文档整理:项目里的 AGENTS.md 和 CLAUDE.md 默认就读;技能要放在 .agents/skills;Claude Code、Codex 的钩子有实验性桥接,只支持部分事件;模组兼容层只是演示;还能把 Claude Code、Codex 当子智能体调用。