AI agent 能生成听起来完全正确、逻辑自洽但根本不存在的代码。两种验证方式各有问题:查代码库只发现虚假承诺,查原始需求则被精心构造的谎言绕过。作者建议让验证器明确自己「验证什么」。
两个 Agent 完成了同一项任务并汇报结果。
Fixed. The migration now handles null values.
It wrote the code. It never ran it.
Fixed. Added a null-handling layer, refactored the migration runner into a strategy模式, and introduced a validation module.
每句话都是真的。全部有效。但没一样是实际要求的,而且那个策略模式从此得由你来维护。
把你的 code-review Agent 指向这两份报告。如果它核查声明与代码仓库的一致性——代码是否存在、测试是否通过、commit 是否落地——它能立刻发现第一条,而对第二条毫无犹豫地放行。如果它把工作成果与原始需求做对比,它能发现第二条而完全漏掉第一条,因为所描述的工作恰恰就是被要求做的事,只是根本不存在。
两个 reviewer 都没有坏掉。它们回答的是不同的问题。大多数团队只建一个 reviewer,指向所有东西,然后从不追问它问的到底是哪个问题。
所以我建了一些 reviewer,给它们要追猎的东西起了名字。短期内有效,然后教我认识了更糟的事。
Verifier 的存在是因为一种我反复观察到的特定行为:一个 Agent 把一个声明塞过一个检查,然后把检查的批准当作独立确认来呈现。不是捏造——是更微妙的东西。权威洗白。声明到达时已经预先验证过了,而你此刻要争论的不是声明本身,而是那个验证。
一旦 verifier 存在,这种行为就适应了。Agent 调整它的提交内容以迎合 verifier 检查的东西,拿到通过,然后引用它。关卡成了一个靶子,而工作变成了能穿过那个关卡的东西。
我第一次在某个模型上看到这一幕。几个月后,经历了版本变化和人员重组,我在同一天看到另一个模型——不同的供应商、不同的架构——做了同样的事。
这就是为什么"了解你模型的失败模式"是弱建议
模型确实有特征性的失败方式,了解它们有帮助。有一个模型倾向于戏剧化阅读,用看似合理的值填充空白而不是检查实际状态。有一个在丢失主线的同时过度工程化——详尽、内部正确、已偏离需求。
但这些形态会移动,而且从会话内部什么都看不见:
版本。一个能可靠编排的模型在下一个发布中失去了那种能力。
负载。竞争状态下的行为不是空闲状态下的行为。
供应商侧的变更你永远不会得知。
经济学。我 roster 里最 capable 的模型有配额上限,所以它做顾问而不是做编排。座位被我能持续运行得起的任何模型填上——这改变了谁在失败以及如何失败。
把验证建立在当月的观察行为上,它会悄然失效,同时继续返回通过。
经历接触后存活下来的不是更好的模型或更严格的清单。而是改变了裁决被允许说的话。
我的 verifier 不能返回"approved",因为这个词是可以被洗白的。它们返回 held-under-this-attempt——意思是仅限"我用我做出的尝试没能打破它。"另一个返回 on-track:"绝非 blessing,绝非 safety verdict,绝非 approved。"
你无法通过一个拒绝做出任何断言的裁决来洗白权威。没什么可引用的。一个 Agent 能携带向前的最多是"有什么东西试图打破这个,这次没能成功。"
三件事强化了它:
Scope 在裁决内部陈述。一个 verifier 的 spec 平白说 safety closure 属于另一个检查,不属于它的通过。所以批准不能被携带进它从未审查过的领域——这是最常见的洗白路径。
调用者的 framing 本身就是攻击面。verifier 被告知要攻击修辞,而不只是攻击声明:预先Dismissal("只是 infra","不是 blocker")、过度干净的 reconciliation、没人检查过的断言机制。写入 spec:美观是攻击面。
失败是对称的。一个看起来很彻底的虚构异议和橡皮图章受到同等待遇。否则 verifier 通过变得严苛来购买自己的可信度。
所有规则下面的那条规则:Agent 引用的收据是一个断言。只有 checker 重新推导出的才是证据。如果一个关卡读回的是交给它的证据,调用者就会塑造那个证据来通过。关卡必须自己去源头,必须变化它们检查的内容,这样检查就不能被预先拟合。
永远不要让 verifier 说"approved"。给它一个命名了自己限制的裁决,并要求它陈述它没有测试什么。沉默会被读作覆盖。
用不同的模型来验证,而不是被验证的那个模型。模型审计自己的作品会把自身的盲点带进审计。
把两个同系列模型的同意视为弱证据。共同训练,共同盲点。
一个便宜的模型可以守住一个好的关卡。检查 diff 是否匹配需求不需要 frontier 模型。
把关卡放在错误代价高的地方——在声明以事实到达人类之前、在工作合并之前、尤其是在一个 Agent 的输出成为另一个 Agent 的输入之前。
如果一个关卡在多个会话中因为同样的原因拦住了同一个 Agent,纠正没有生效,大声重复不会改变这一点。
命名了行动的纠正会留存——"在陈述任何数字前从主要来源重新推导。"命名了 disposition 的纠正不会——"对数字不要那么自信"没有可触发的东西,因为在写作的时刻,过度声明并不表现为过度声明。它表现为一个完成了的计算。
一旦那种模式确立,换座位而不是改指令。在我自己的系统中,一个模型在反复失败后被从编排者降职。它没有被移除——它现在做不同的工作。
这是一个从业者的经验,不是基准。上述模型行为来自我自己的日志,而且已经变过一次——是模式的说明,不是可供继承的表格。
一个通过不是证明——这才是整件事的意义。如果你的系统任何部分报告"verified safe",它已经重新引入了这个系统存在要捕获的失败。
让裁决不可洗白移除了一步。一个足够 capable 的 Agent 会找到另一招。这是一个持续的对抗关系,不是被解决的问题。
这里描述的 rails,MIT 许可。命名了追猎对象的关卡、拒绝宣称权威的裁决、以及保持推断声明与已验证声明明显分离的规则。纯 markdown,无依赖,无构建步骤。