编码 Agent 最危险的失败模式不是"写错代码",而是"写错代码却报告说完成了"。人类工程师有反馈回路:编译、跑测试、看报错。如果不给 Agent 建立同样的回路,它就只能凭语言模型的"感觉"判断对错——而这种感觉,在长程任务里会系统性地偏向乐观。验证 harness(verification harness)要解决的,正是这个"自证清白"的问题。
为什么必须有自检,而且要按节奏跑
短任务里,模型写完一段代码、人类立刻验证,问题不大。但长程编码任务——重构一个模块、实现一个跨多文件的特性——动辄几十上百步。如果只在最后一步验证,中间任何一步的偏差都会沿着后续步骤放大,等到终点才发现,往往已经积重难返、难以定位。
所以验证不能是"最后做一次",而要按节奏穿插在执行过程中。一个实用的模式是:每隔若干步,就对照规约(spec)跑一遍检查——编译、单测、或者一个轻量的契约校验。
1 | SPEC = load("spec.md") |
节奏的意义在于把"错误传播距离"压短。每 4 步一个检查点,意味着任何 bug 最多污染 4 步就会被逮住,定位范围天然被框定,回滚成本也低。
让验证者是另一个 Agent,而不是同一个
写代码的 Agent 去验证自己刚写的代码,存在和人类一样的盲点——它带着"我刚才是这么想的所以这是对的"的预设。把验证交给一个独立的、fresh-context 的子 Agent,效果通常更好。
1 | # 写代码的主 Agent 完成一个 checkpoint 后 |
这个验证者只拿到规约和待验对象,不知道之前作者绕过的弯路,因此不会替作者的思路背书。它跑测试、读真实输出、逐条对照——它的判断建立在工具结果上,而不是对代码的"印象"。
系统提示里最该写死的一条:报告前先自证
即便有了检查节奏和独立验证者,模型仍可能在叙述里"幻觉出完成状态"——说"测试已通过"“功能已实现”,但其实它根本没跑,或者跑了没看结果。对抗这种编造,最有效的不是更复杂的流程,而是在系统提示里写死一条强约束:
在报告任何"完成 / 已修复 / 测试通过"之前,必须先用本会话内的工具结果自证。没有对应的工具输出作为证据,就不许声称成功。
这条规则把"成功"从一个可以随口说出的形容词,变成了必须有证据支撑的断言。它的威力在于改变了举证责任:模型不能再用流利的措辞蒙混过关,它得拿出那一次 pytest 的真实退出码、那一行 Build succeeded。实践中,仅这一条就能大幅减少"假完成"——因为模型一旦被要求"贴出证据",就不得不真的去执行验证步骤。
1 | # 反例(系统提示没约束时模型爱说的话) |
验证不等于过度验证
需要提醒的反向陷阱:验证 harness 是为了抓真实风险,不是为了表演严谨。如果只是改了一行日志文案,没必要跑全量回归套件、没必要派验证者、更没必要为"万一以后日志格式要变"提前加一层抽象。验证的强度应当与改动的风险匹配——这也呼应了 effort 的思路:低风险改动用低强度验证(够信息就收手),高风险改动才动用完整的"对照规约 + 独立验证者 + 跑全套测试"。把简单 bugfix 顺手升级成大重构、为不会发生的场景预埋防御,本身就是一种应该被克制的过度工程。
一个可落地的最小 harness
把上面几块拼起来,一个朴素但管用的验证 harness 是:
- 开工前把验收标准固化成
spec.md(可被反复对照的事实源)。 - 执行中每隔若干步设检查点,跑 build + 相关单测。
- 关键 checkpoint 派 fresh-context 子 Agent 做独立验收。
- 系统提示写死"报告完成前先用工具结果自证"。
- 验证强度随改动风险伸缩,不搞一刀切。
这里还藏着一个常被忽视的细节:检查点之间,最好让 Agent 维持一个"最近的绿色状态"作为可回退的锚点。一旦某次检查失败,与其在脏状态上继续打补丁、越改越乱,不如回到最近一次全绿的快照,从那里重新出发。这等于把版本控制里"小步提交、随时回滚"的纪律内化进了 Agent 的执行循环——每个绿色检查点都是一次隐式的提交,错误最多波及一个检查点区间,而不会沿着整条执行链扩散。配合前面"报告前先自证"的约束,Agent 既不会谎称成功,也不会带着未被发现的回归一路狂奔。
让 Agent 自己跑测试、自己审自己的核心,不是多套一层流程,而是把"成功"重新定义为一件必须拿证据说话的事——一旦做到这点,编造的完成状态就失去了生存空间。