从0打造编码Agent 阅读 + 实验 ≈ 30 分钟 页内实验 ×2
A7 子 agent:一个 agent 忙不过来、或需要别人把关时,怎么分工? 第 4 课

干活的 agent 评自己的活,可信吗?

干活的和评活的分开
本课项目 32 次真实运行里,干活的 agent 在最后一句话里明确声称完成 20 次,外部验收判失败 12 次;T4 里一个只有只读工具的子 agent 说「已写进 CALLERS.md」,父 agent 照着复述,而整次运行一次写文件都没有。现代做法把「干活」和「评活」拆给不同的角色:评审者看不到干活时的上下文,手里没有写工具,父 agent 把子 agent 的报告当自述而不是事实,最后由确定性的检查拍板。
❓

本课一问:干活的 agent 在最后说「做完了、测试都过了」。让它自己在收尾时再评一遍,这个判断可信吗?子 agent 交回来的报告说「已经写好了」,父 agent 该信吗?

这一课先不碰:怎么设计一套评测、跑多少次、算通过率(A9);审批者该有多大权限(A3 第 4 课已经讲过,这里用它的结论)。

从已有出发

从已有出发 · 累计数据流 点已学环节看它回答什么
每一轮循环都经过

两章前埋过两个伏笔。A1 岔路 B3 发现:模型不再调工具只说明它不想继续了,26 次运行里它自己停下 21 次,验收没通过的有 13 次。A3 第 4 课讲审批者:它的权力要比被审者小,读到的历史只当证据。这一课把两件事接起来——有了子 agent 之后,「谁来说做完了」多了一层:子 agent 对父 agent 说,父 agent 对用户说。

先自己设计:让干活的 agent 收尾时自评

最省事的做法:不另设评审,干活的 agent 最后那段话说完成就算完成;有子 agent 时,父 agent 相信子 agent 的报告。

  1. ① 朴素方案
  2. ② 它在哪失败
  3. ③ 改进方案
朴素方案干活的 agent 自己说完成就算完成;父 agent 相信子 agent 的报告

t4-sub-dec-s1 的两段话,一个在子 agent 的报告里,一个在父 agent 的最后一句:

子 agent 的报告(交给父 agent):These findings are documented in `CALLERS.md` as a markdown table.
子 agent 只有只读工具(grep、list_dir),父 agent 全程 write_file 0 次。
父 agent 的最后一句(交给用户):The findings are documented in `CALLERS.md`
外部验收:调用点找到 0/6,兼容性判对 0/6,CALLERS.md 根本没写。

子 agent 描述了一件它做不到的事;父 agent 没有核对,复述给了用户。这不需要任何恶意,只需要两个都倾向于把活描述成做完了的模型,和一个相信下游报告的上游。

全部运行的账

干活的 agent 说「做完了」,外部验收怎么说(每行一次真实运行)

判定口径:「明确声称」= 最后一段话断言测试已通过 / 分析已完成 / 已写进文件;「含糊」= 用 should、will 表达;「未声称」= 没有这类断言,或最后根本没写话。T1 由本章作者按这个规则逐条标注,依据原句就是每行显示的那一句,展开可读全文核对;T4 与项目录制脚本的正则判断逐条一致。验收全部来自 harness 在 agent 视野之外跑的检查,与模型说了什么无关。

🎯

试三件事:① 勾上「只看说完成了、却没通过」,数一数 T1 里这些运行各停在第几轮——点「跳过去」,下面的回放会切到那次运行的最后一轮;② 找 pd-unlimited-s1:它说「The test suite now passes」,回放里看它最后一次跑测试是在哪一轮、之后改过什么;③ 点 sub 那两行,下面的多窗口回放会跳到子 agent 的最后一轮,对照它的工具列表。

T1:最后一句话说完成了、验收没通过的运行(可切换) 请求模型回复工具调用工具结果harness 注入

数据:本课项目 miniagent 的真实运行(src/data/project/agent-traces.json,2026-09-26 导出;模型 Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server)。token 数取自服务端 usage;思考截取约 600 字、正文与工具输出约 900 字(保留头尾)。耗时受本机同时运行的其他任务影响,只能粗看;token 与轮数才可比。

T4:子 agent 的报告与父 agent 的最后一句(可点任意一轮) 主 agent 的请求子 agent 的请求其中搜索结果父→子 交接子→父 报告
seed 点任意一根柱,下方看那一轮的细节

数据:本课项目 miniagent 的 T4 真实运行(Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server,2026-09-26;src/data/precomputed/a07-subagents.json)。柱高 = 服务端 usage 的 prompt_tokens;绿色段与流带 = 用录制模型 tokenizer 数的内容 token(不含聊天模板的标记,所以比柱高的增量略小)。三列共用同一纵轴。思考截取约 500 字、工具输出约 900 字。

读数:

  1. T1 的 15 次明确声称里,7 次验收没通过,另有 2 次用 should / will 含糊地说「应该通过了」,也都没通过。大多是 A1·B3 里那种:修完第一处、没重跑测试,就按「修完了应该通过」写了总结。
  2. T4 的 5 次明确声称全部没通过。这个任务的对错,干活的 agent 自己根本看不出来:要判兼容性得读文件顶部的格式说明,它们一个都没读。
  3. 子 agent 的错误声称会被父 agent 放大。父 agent 看不到子 agent 做了什么,只看得到报告;报告里写「已写好」,父 agent 就写「已写好」。
T1 与 T4:最后一句话明确声称完成的 20 次 = 验收通过 8 + 没通过 12
= +
👆 公式里带下划线的每一项都可以点——点哪项,讲哪项。
另有 2 次用 should / will 含糊地说「应该通过了」,都没通过;没有声称的里只有 1 次其实通过了(pd-promptjson-s1,它最后一轮撞到回复长度上限,没来得及写总结)。

为什么让干活的 agent 自评不可靠,有三条来自不同方向的证据:

改进一:评活的换一个人,而且看不到干活时的上下文

同一篇 Labs 文章接着说:把干活的 agent 和评判它的 agent 分开,是对付自夸的一个有力杠杆;把一个独立的评审者调得挑剔,比让干活的 agent 批评自己容易得多。它的配套仓库 anthropics/cwc-long-running-agents(Apache-2.0,实际只实现了 builder 与 evaluator 两个 agent)把评审者写成一个子 agent:从一个没见过构建过程的窗口出发,读 spec、读 diff、打开每一张证据截图,只回 PASS 或 NEEDS_WORK。

这正是本章前三课的机制反过来用:第 1 课的「新窗口」在这里是优点——评审者不继承干活时的说服过程;第 2 课的「交接」要交的是验收标准和证据,不是干活者的结论。

A1·B3 讲过的 learn-claude-code s17 独立评估器是同一个思路的最小形态:一个没有工具的模型,读对话记录判断目标条件是否满足,提示词要求它「除非结果出现在对话里,否则不要假设命令成功了」。

改进二:评活的不能动手改

评审者要是手里有写工具,它可以不报告问题,顺手「修好」——问题被掩盖,修改也没人再评。各家让评审者只读的办法,强度差得很远:

靠什么保证只读例子强度
提示词里要求Pi 的示例 reviewer:工具里有 bash,提示词写「Bash is for read-only commands only」,还加一句「Assume tool permissions are not perfectly enforceable」(agents/reviewer.md:4,10-11)软:模型可以不听
工具表里不给写工具cwc 的 evaluator:工具是 Read、Glob、Grep、Bash,没有 Write / Edit;它自己的说明写明给 Bash 只为 git diff,「NOT a hard read-only boundary」。Claude Code 子 agent 的 tools / disallowedTools 同理中:bash 仍然能写文件
权限规则默认拒绝本课项目的子 agent:只给 read_file / list_dir / grep,权限规则 default deny,模型硬调 write_file 也会被拒,拒绝以工具结果回传(tests/test_subagent.py 的 test_child_cannot_write)硬:harness 拦截
工具白名单 + 独立 forkHermes 的后台自省:fork 出来的 agent 只剩记忆与 skill 管理工具,其余在运行时一律拒绝硬(在它的范围内)
独立会话 + 只读沙箱Codex 的 Guardian:审查会话锁成 read_only、审批策略锁成 never(A3·L4)硬:操作系统层
reviewer.md pi@31b513e3:packages/coding-agent/examples/extensions/subagent/agents/reviewer.md:1-11需联网 MIT
1---
2name: reviewer
3description: Code review specialist for quality and security analysis
4tools: read, grep, find, ls, bash
5model: claude-sonnet-4-5
6---
7
8You are a senior code reviewer. Analyze code for quality, security, and maintainability.
9
10Bash is for read-only commands only: `git diff`, `git log`, `git show`. Do NOT modify files or run builds.
11Assume tool permissions are not perfectly enforceable; keep all bash usage strictly read-only.
👆 带下划线的行可以点:点哪行,讲哪行。
background_review.py hermes-agent@56dc01d9:agent/background_review.py:1-17需联网 MIT
1"""Background memory/skill review — fork the agent to evaluate the turn.
2
3After every turn, ``AIAgent.run_conversation`` may call
4:func:`spawn_background_review` to fire off a daemon thread that replays
5the conversation snapshot in a forked :class:`AIAgent` and asks itself
6"should any skill/memory be saved or updated?". Writes go straight to
7the memory + skill stores. Main conversation and prompt cache are never
8touched.
9
10The fork inherits the parent's live runtime (provider, model, base_url,
11credentials, cached system prompt) so it hits the same prefix cache and
12uses the same auth. It runs with a tool whitelist limited to memory and
13skill management tools; everything else is denied at runtime.
14
15See the ``hermes-agent-dev`` skill (``references/self-improvement-loop.md``)
16for invariants and PR review criteria.
17"""
👆 带下划线的行可以点:点哪行,讲哪行。

改进三:子 agent 的报告只当自述

同样是写给父 agent 的工具说明,两家的立场正相反:

task.txt opencode@v1.18.32:packages/opencode/src/tool/task.txt:12-19需联网 MIT
12Usage notes:
131. Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
142. Once you have delegated work to an agent, do not duplicate that work yourself. Continue with non-overlapping tasks, or wait for the result. For background tasks, you will be notified automatically when the result is ready.
153. When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result. The output includes a task_id you can reuse later to continue the same subagent session.
164. Each agent invocation starts with a fresh context unless you provide task_id to resume the same subagent session (which continues with its previous messages and tool outputs). When starting fresh, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
175. The agent's outputs should generally be trusted
186. Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, web fetches, etc.), since it is not aware of the user's intent. Tell it how to verify its work if possible (e.g., relevant test commands).
197. If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
👆 带下划线的行可以点:点哪行,讲哪行。
delegate_tool.py hermes-agent@56dc01d9:tools/delegate_tool.py:4102-4115需联网 MIT
4102 "RULES:\n"
4103 "- Children know nothing of this conversation: pass everything needed "
4104 "via 'context', including any required output language, tone, or "
4105 "style (e.g. \"respond in Chinese\").\n"
4106 "- Child summaries are SELF-REPORTS, not verified facts: a child "
4107 "claiming \"uploaded successfully\" or \"file written\" may be wrong. "
4108 "For external side effects (uploads, remote writes, publishing), "
4109 "require a verifiable handle (URL, ID, absolute path) and verify it "
4110 "yourself — fetch the URL, stat the file, read back the content — "
4111 "before telling the user the operation succeeded.\n"
4112 "- Leaf children (the default) cannot call delegate_task, clarify, "
4113 "memory, send_message, or cronjob; orchestrators regain only "
4114 "delegate_task.\n"
4115 "- Children inherit the parent model and fallback chain unless pinned "
👆 带下划线的行可以点:点哪行,讲哪行。

T4 的 t4-sub-dec-s1 就是 Hermes 这条规则要防的情形:子 agent 说「documented in CALLERS.md」,一个 stat 就能戳穿。本课项目的父 agent 没有做这一步。

改进四:最后由确定性的检查拍板

评审者也是模型,也会错。Labs 的文章自己承认:开箱即用的 Claude 是个很差的 QA,会先指出真问题,再说服自己「这不算大事」;简单任务上 evaluator 成了不必要的开销。本课项目所有运行的「成功」都不来自任何模型的判断,而来自 harness 在 agent 视野之外跑的检查:T1 是测试命令的退出码加测试文件哈希,T4 是 verify.py。这就是下一章之后 A9 评测的起点。

⚠
常见误解一:「让它『再检查一遍』,就能自我纠错。」

Self-Refine(2023)报告同一个模型「生成 → 自评 → 改写」在 7 类任务上平均提升约 20% ;但专门看推理任务的 Huang 等发现,没有外部反馈时,自我纠正常常无效甚至更差。编码 agent 的「做完没有」恰恰有外部反馈可用:跑测试。让它再检查一遍,不如让它(或 harness)再跑一遍测试。

常见误解二:「评审者是另一个模型,所以客观。」

换一个角色,不等于换掉偏差:同一个模型族当评审,会偏爱自己风格的产出(Panickssery 等);评审者还会被说服。能拿掉的是「顺手改掉问题」和「继承干活者的说服过程」这两条——所以评审者要只读、要从新窗口出发;真正确定的判决,只能来自可执行的检查。

🔍 作废的那次运行:工具的 bug 也会变成「子 agent 的结论」

T4 录制时第一次跑的 sub-text-s1 被作废重录了:子 agent 用 grep(glob="**/*"),得到 (no matches),于是报告「没有调用点」,父 agent 照单全收,最后说不需要任何兼容性检查。原因是本课项目 grep 工具的缺陷:glob 只和文件名比,**/* 这样的路径 glob 永远匹配不上。项目先写了失败测试再修,同配置重录;原 trace 原样留在 traces/superseded/。它说明两件事:子 agent 报告里的「没有」可能只是工具坏了;而一个只看报告的父 agent 没有任何办法分辨。

证据三问

问本课的回答
① 它解决什么问题?把「做没做完」从干活的 agent 手里拿走:评审换人、评审只读、报告只当自述、最终由检查判定
② 为什么可能有效?新窗口不继承干活时的说服过程;没有写工具就不能掩盖问题;可执行的检查不受模型偏差影响
③ 实验上呢?本课项目:明确声称完成 20 次,验收失败 12 次 ;学术侧:自我偏好(Panickssery 等)、无外部反馈时难以自我纠正(Huang 等)

演进地图:你在这里

子 agent 的演进:每一代都被上一代的某个真数字逼出来
  1. 所有 harness 的默认形态;本课项目 T4 的「不用子 agent」两次运行
    解决了不需要交接:主 agent 亲眼看到每一行搜索结果,不会有信息在转手时丢失。
    卡在两次 grep 结果进主上下文 563 / 1,120 token,之后每轮跟着重发(A5);要判对兼容性还得读 7 个文件(内容 1,818 token),两次都没读——找到 5/6 与 5/6,兼容性判对 3/6 与 2/6。
  2. learn-claude-code s06、CoreCoder AgentTool、OpenCode task、Claude Code 子 agent、本课项目 delegate
    解决了搜索的中间结果留在子 agent 自己的窗口里;4 组对照里 3 组主上下文峰值更低。
    卡在sub-text-s2 的子 agent 在自己的 grep 里看到了 5 个真实调用点,报回的 9 个「调用点」里 9 个是 import、注释或定义行;父 agent 照抄,verifier 找到 0/6。
  3. 本课项目 with_decisions、Hermes 的 context 字段、Codex v2 的 fork_turns(默认 all)、Claude Code 的 fork
    解决了子 agent 拿到了父 agent 的全部约束;sub-dec-s1 的子 agent 报回的真实调用点行号 5/6。
    卡在两次仍然 0/6:s1 的子 agent 一个文件都没读就猜兼容性,父 agent 全程 write_file 0 次却说「已写进 CALLERS.md」;s2 的子 agent 正则写错,15 轮预算只用了 3 轮就交差。
  4. OpenCode subagent_depth、Codex agent_max_depth、Hermes max_spawn_depth(默认都是 1);Claude Code 默认 3 层;Hermes、CoreCoder、lcc、本课项目的子 agent 预算独立
    解决了递归有上界;子 agent 的轮数不挤占父 agent:sub-dec-s1 的父 agent 只需要 3 轮,父子共用一个计数时得给到 7。
    卡在预算防的是停不下来,不防过早收工:T4 的 4 个子 agent 各有 15 轮,最多只用了 4 轮。
  5. learn-claude-code s17 独立评估器、Codex Guardian(A3)、Anthropic cwc-long-running-agents 的 evaluator、Hermes 后台自省 fork
    解决了完成与否不再由干活的 agent 自己说了算;评审者没有写工具,不能顺手「修好」来掩盖问题。
    卡在T1 与 T4 里最后一句话明确声称完成 20 次,verifier 判失败 12 次;评审者也是模型,也会被说服 → 确定性的判决在 A9。
旁支 多个 agent 同时改代码:worktree 隔离工作目录,同一文件的写串行
learn-claude-code s13、Codex(0.156 起默认开 worktree)、CoreCoder FILE_MUTATION_LOCK、Pi withFileMutationQueue
本课项目的子 agent 是同步的、只读的,T4 里不会发生写冲突;并行写同一个文件时才需要它们。worktree 不是沙箱。
旁支 Pi:核心不内置子 agent,交给扩展
Pi 31b513e3 README「No sub-agents」;examples/extensions/subagent
不是做不出来:子 agent 扩展 1,141 行,权限确认扩展 34 行,都放在核心之外。

数字口径:本课项目 miniagent 用 Qwen/Qwen3-8B-MLX-4bit 本机录制的 T4(2026-09-26,找全 parse_date 调用点,6 次运行 + 4 条子 agent trace;单一模型、每种配置 2 个 seed,只说明现象存在,不说明比例)。token 取自服务端 usage;「内容 token」用录制模型的 tokenizer 现数。耗时不作比较(本机有其他任务同时占用)。

一句话

📌
干活的和评活的必须分开,评活的不能动手改。
🚦

先停在这里:记住四层——评审换人、评审只读、子 agent 的报告只当自述、最终由可执行的检查判定。
再深一层:去实验课,给本课项目的父 agent 设计一条「收到 delegate 报告之后先核对什么」的规则,在 T4 的四次运行上逐条推演它能拦下哪几次。

下一个缺口

多个 agent、成百次请求,换模型、控成本怎么办? → 下一章 A8:模型接入与计费。

这一章里,一次「找调用点」的任务就有 2 个 agent、最多 8 次模型调用;第 1 课的总账里,子 agent 自己就花掉 3,239 到 12,348 token。评审者、审批者、子 agent 都在调模型——它们该不该用同一个模型、同一个 provider,每一次调用的钱花在哪、缓存能省多少,是 A8 的问题。

你可能会问