谁来判定「真的做完了」?
本课一问:第 3、4 课讲的都是循环怎么停。可模型停下来、写了一段「问题已修复」,就算做完了吗?谁来判定「真的做完了」?
这一课先不碰:评测怎么设计、怎么算通过率(A9);子 agent 评自己的活可信吗(A7 第 4 课)。
从已有出发
循环的出口是「模型不再调工具」。这一刻模型通常会写一段总结。本课项目的做法是:循环一结束,harness 自己跑一遍任务的测试,并检查测试文件没被改过——这是 verify 事件,和模型说了什么无关。
先自己设计:模型说做完了,就是做完了
- ① 朴素方案
- ② 它在哪失败
- ③ 改进方案
数据:本课项目 miniagent 的真实运行(src/data/project/agent-traces.json,2026-09-26 导出;模型 Qwen/Qwen3-8B-MLX-4bit,本机 mlx_lm.server)。token 数取自服务端 usage;思考截取约 600 字、正文与工具输出约 900 字(保留头尾)。耗时受本机同时运行的其他任务影响,只能粗看;token 与轮数才可比。
最常见的失败是这样:修完第一处 bug,没有重跑测试就写总结。第二处 bug 要重跑测试才看得见,模型没跑,所以它真心以为做完了。这不是模型撒谎,是循环的出口只看它想不想继续。
补救有两个方向:让模型更谨慎(改提示词),或者让别的东西来判(harness 验收、独立评估器、Stop hook)。
改进一:在提示词里写死「没看到测试通过就不许说通过」
本课项目的 harness v2 只改了两处,其中一处就是在 system prompt 末尾加一条完成规则:没在工具结果里亲眼看到测试通过,不许说通过;每次修改后必须重跑测试。同一个任务:v1 10 次通过 1 次,v2 10 次通过 5 次 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条项目实测:两组各 10 次,除提示词外 v2 还加了「服务端丢弃调用时提醒重发」,所以差异不全归功于这一条规则;单一模型、单一任务。 出处待补 。这条规则帮了很多,但它仍然是请模型自觉:v2 里模型自己停下、验收仍没通过的还有 3 次。
改进二:让别的东西来判
三种做法,都把「能不能停」从模型手里拿走一部分:
| 做法 | 谁在用 | 怎么判 | 出处(pin) |
|---|---|---|---|
| harness 自己验收 | 本课项目;评测框架(A9) | 循环结束后跑确定性的检查(测试、文件哈希);不通过就记为失败——本课项目目前不把结果喂回模型再跑 | 04-实践项目/miniagent/runner.py |
| 独立评估器(goal loop) | learn-claude-code s17;Hermes goals | 模型每次想停,另一个没有工具的模型读对话记录,判断目标条件是否满足;不满足就阻止停止、让它继续 | lcc s17_goal_loop/code.py:212-268,:354-422;Hermes config_defaults.py:1788-1802 |
| Stop hook | Codex | 模型想停时运行用户配置的 hook,hook 可以否决并给出继续的提示 | Codex turn.rs:472-497 |
Hermes 的目标循环是同一个思路,上限设得更紧:goals.max_turns: 20,配置注释写明它防的是「评估器假阴性(目标其实达成了,评估器却说继续)」,以及模糊目标上无止境的花费;评估器调用出错时放行(fail open),由轮数上限兜底(config_defaults.py:1788-1802)。
模型停下只说明它不想再调工具了。本课项目 21 次自己停下里 13 次没做成;评估器能拦住一部分,但它读的也只是对话记录,它自己也会误判——所以 learn-claude-code 给否决次数设了上限,Hermes 给目标循环设了轮数上限。真正确定性的判定只能来自模型之外可执行的检查(测试、编译、验证脚本),这正是 A9 评测的起点。
🔍 为什么本课项目的 harness 不把验收失败喂回模型、让它接着修?
可以这么做,MILESTONES 里 A9 把它列成一个待拍板的决策:「停止前 harness 自动跑测试、不过就把结果喂回模型」。代价有两个:一是测试本身成了模型能看到、能针对的信号(评测时要小心,A9);二是每次喂回都多一轮甚至多轮,循环的上限问题(第 4 课)又回来了。本课项目现在只验收、不喂回,这样 trace 里的失败就是真实的失败形态,A9 能拿来做失败归因。
证据三问
| 问 | 本课的回答 |
|---|---|
| ① 它解决什么问题? | 把「模型不想继续了」和「任务确实完成了」分开判 |
| ② 为什么可能有效? | 完成条件由模型之外的检查或另一个没有工具的模型核对,不依赖干活的模型自评;但评估器看的仍是对话记录,只是换了一个会犯错的判断者 证据等级 · 有理论动机 有数学或机制上的理由说明它可能有效,但没有证明它一定更好。最后是否采用,仍然要看实验。 本条设计动机见 lcc s17 评估器提示词与 Hermes 配置注释(源码直证)。「独立评估器决定停止」是 C 报告观察到的跨仓趋势,不是被实验证明的最佳做法。 出处待补 |
| ③ 实验上呢? | 本课项目:模型自己停下 21 次,验收没过 13 次;加完成规则后贯穿任务 1/10 → 5/10 证据等级 · 实验验证 由对比实验得出的经验结论,只在出处写明的模型、数据和规模下成立。换了条件可能不再成立,引用时要带上实验设置。 本条项目实测,两组 10 次、同时改了两处,只能说明方向。独立评估器本课项目没有实现,没有自己的数据。 出处待补 |
演进地图:你在这里
- 解决了不需要任何协议:模型本来就会写命令。卡在真实基线 T1a:同一个模型、同一个任务跑 8 次,执行抠出来的命令后测试通过 0 / 8;其中 6 次抠出的 GNU 写法
sed -i在 macOS 上直接报错,改成 BSD 写法再跑也 0 次通过——一次回复只看得见第一处 bug。 - 解决了格式由模型训练与服务端解析器负责,harness 拿到的是 name + JSON 参数;执行结果以
role: tool回填,模型能看见后果。卡在一次调用只能走一步:贯穿任务的真实成功运行,最少也用了 6 轮(读 → 改 → 测出第二处 → 改 → 再测)。协议之下仍然是文字:24 次原生调用的运行里,服务端解析器 7 次把 JSON 不合法的调用整条丢掉。 - 解决了能连续走几步了,而且一定会停。卡在贯穿任务的 20 次真实运行里,3 轮以内收工的 0 次;只调 3 轮,6 次成功里有 6 次变成失败(重放前 3 轮的 edit 后实跑验收)。
- 解决了轮数由任务决定,不由写死的数字决定;状态只有一个 messages[],每轮整份重发。卡在「没有工具调用」不等于「做完了」:harness v1 有 3 次运行因服务端丢了调用而误停;26 次里模型自己停下 21 次,其中 13 次验收没通过。
- 解决了finish_reason 和消息体都看:说要调工具却没给调用、被截断、报了不认识的结束原因,都不当成「做完了」。卡在补救也会失败:pd-v2-nothink-s1 连续 3 次发出被丢弃的调用,提醒 2 次无效后以
dropped_tool_call停;而且它不回答「要不要强制停」。 - 解决了死循环有了兜底;到上限时可以返回显式原因,或让模型先收尾。卡在上限 4:2 次运行都在第 4 轮刚改完第一处时被截断,模型毫不知情;不设上限的 6 次全部自己停下(最长 8 轮)。重放:上限 6 碰巧救回 2 次(截在重复 edit 之前),而最长那次成功运行要到第 19 轮才修好。
- 解决了用户不必等整次运行结束才能纠偏。卡在插话时机本身是设计决策:同一条事件序列里,4 个生产循环把插话送进下一次请求,3 个教学 / 项目循环要等这次运行结束。
- 解决了压缩检查、提醒注入、权限中断恢复都能插在两次调用之间。卡在循环要自己维护:Codex 把它拆成四层(submission / task / turn / stream),教学蓝本只写了其中一层。
- 解决了「模型说做完了」交给另一个没有工具的模型核对。卡在评估器也会错:Hermes 给目标循环设了 20 轮上限(pin 56dc01d9 config_defaults.py:1801),注释写明是防评估器「假阴性」;每轮多一次模型调用。
一句话
回主线
循环转起来了,模型怎么说清「改哪里」? → A2 工具系统。「谁来判定做完了」在 A7 第 4 课(自评可信吗)和 A9(评测)兑现。
你可能会问
- 「评估器用同一个模型行不行?」 learn-claude-code 的 s17 默认先用环境变量指定的评估模型,没有就退回一个小模型(Haiku),再没有才用干活的同一个模型(
code.py:836-840);无论哪种,评估都是一次没有工具、只读对话记录的独立调用。评估者与被评估者该有多大的差别,是 A7 第 4 课的问题。 - 「为什么 v1 的模型会说『测试通过了』?」 它只修了第一处、没重跑测试,就按「修完了应该通过」写了总结;项目 README 记录有一次它在思考里编出了「用户跑了测试、失败已解决」。这正是评估器提示词里那句「除非结果出现在对话里,否则不要假设命令成功了」要防的。