ENZH

别让 agent 自己说"我搞定了"

Agent 最危险的产出不是烂代码,是"我搞定了"这几个字。

挂掉一个测试的烂代码是个非事件:你看见了,你修了,没人会把它上线。真正伤到你的,是裹在一句自信的"我已经实现并验证它能跑了"里送来的烂代码,因为写它的那个 agent,同时也是给它打分的那个 agent,而且打分手松。它是带着"这代码是对的"这个信念写出来的。让它检查自己的活,等于让它再确认一遍它一开始就有的那个信念。它当然过。

这是 给 agent 盖一个能维护的仓库 系列的最后一篇,讲的是前三篇一直在悄悄铺垫的那半个问题。结构 告诉 agent 去哪儿干活。契约 框住它能弄坏什么。闸门 把散文规则换成墙。这一切都为了最后一件事:让"做完"变成一个 agent 必须从仓库里挣来的裁决,而不是一句它能自己给自己写的话。

四个动作,一层叠一层。

给它一个真能证明的东西

从代码本身说起,因为有的代码可证、有的不可证,这个区别决定了 agent 自己能验证多少。

一个纯函数,输入进、输出出,没有文件系统、没有网络、没有数据库、没有时钟、没有随机、没有环境变量,能被完整、即时地检查,只要一个单测。Agent 能写这个测试、跑它、看着它过或挂,然后在自己的循环里知道,不用 provision 任何东西。这就是那个确定性的修复靶子:一个 agent 能无人监督地迭代到正确的地方,因为那个 oracle 就在手边,查一次不要钱。

现在把同一条业务规则,抹进一个还顺手拉数据、读 localStorage 的 React 组件里,或者一个还顺手开事务、调 Stripe 的 controller 里。要验证它,agent 需要一个浏览器、一个数据库、一套凭据、一张网。它通常没法在自己的循环里把这些都搞齐,于是它做它能做的那件事:读 diff,判断看着挺对,宣布做完。逻辑从头到尾没错,是架构让它变得不可验证,而不可验证,正是假完成住的地方。

这才是分层真正要紧的原因:纯 core、做编排的 application、当接口的 ports、有副作用的 adapters、被隔离的 generated。它不是审美。你每往纯核心里下沉一条规则,就是一条 agent 自己能证明的规则。每一条被困在副作用层里的规则,是一条它只能断言的规则。六边形那个形状,本质上是一台机器,用来把你系统里"agent 不 provision 整个世界就能真正验证"的那部分比例最大化。把真相保持纯净,把 IO 推到边缘,你就把最大的那块可信任表面交到了 agent 手里。

把验证收成一条命令

第二个动作,是让"这做完了吗"只有一种回答方式。

如果检查一个改动,意味着要知道这种编辑得跑单测套件、那种得跑契约测试、视觉回归只在 CSS 动了时才管事、架构规则还有个单独的 lint 配置,那验证本身就是默契,而我们已经知道默契摆在 agent 面前会怎样。它被跳过、被跑一半、或者被猜。

所以你把它收拢:一条命令,make verify 或者 pnpm verify,把整套闸门跑一遍:类型、lint、单测、契约、集成、架构检查、巨文件检查、安全检查,返回一个单一的通过或失败。CI 跑一模一样的命令。现在"做完"有了一个精确又无聊的定义:命令以零退出,或者你拿确切的输出记下那个确切的阻塞点。没有判断,没有感觉,没有"我看着挺好"。Agent 没资格定义做完,你也没有。命令说了算。

让这条立得住的纪律小但绝对:*做完是那个 exit code,不是那种感觉。*一个 agent 说"我验证过这个改动了",却没贴出那条用来验证的命令的输出,它什么都没验证。声明和证据,得是同一个东西。

别让作者批自己的卷子

第三个动作直接对付"批自己作业"那个问题,因为哪怕一个绿色的 verify,也可能盖住一个作者根本没想到要查的缺口。

修法是把改动的那个上下文,跟它的那个上下文分开。实现的 agent 泡在自己的假设里,它知道自己想表达什么,所以倾向于确认自己想表达的。一个全新的上下文,或者一个不同的判官,不共享那些假设,会注意到那个没被重跑的文件、那个没被覆盖的情形、那个悄悄退化了的行为。具体下来,这是一个干净 checkout 上的 CI,或者一个对实现毫无记忆的验证 subagent,或者一个 review bot,或者就是在一个作者没搭过的干净 worktree 里把闸门跑一遍。机制各异,原理不变:判断作者有没有做完,作者是最差的那个判官。

我觉得这是整个 agent 时代最被低估的想法,而且它能推广到代码之外。这也是我从不让自己当 agent 产出唯一审查者的原因:我留一道单独的关,它的全部工作就是当怀疑论者,因为生成的那个脑子和验证的那个脑子,不该是同一个脑子。谁来 review AI 写的代码,最后会变成那个决定这一切能不能 scale 的问题。同一个 agent 既写又批的时候,你没有验证,你有一个自信的解说员。

让证据熬过会话

最后一个动作,是给那些一次坐不下来的活准备的,而真实的活大多是这种。

Agent 在会话之间会丢记忆,哪怕在一个长会话里,上下文也会被压缩,早期的推理被摘要掉、腾地方。如果你"做了什么、验证了什么"的证据只活在那个易失的上下文里,它会恰好在一个跨天的 migration 或者一次大重构最需要它的时候蒸发掉。下一个会话,或者压缩后的续接,半瞎着开局,要么把干完的活重做一遍,要么更糟,把没干完的活标成做完了,因为那条失败已经滚出屏幕了。

修法是把证据写进仓库,让它留得住:一小套 agent 边走边维护的账本。一个进度文件,记当前检查点和下一步。一个证据文件,装命令的输出,真正跑过的那些绿色记录,不是一句"它们过了"的摘要。一个决策文件,记架构选择和它们的取舍,好让未来的会话不去重新吵、也不悄悄跟它矛盾。证据账本那条不可商量的规则是:它装的是证明,不是貌似。一段贴出来的绿色运行、一个干净的 diff、一个以零退出的检查。"我相信这能跑"不算一条记录。整件事的意义,是让一道冷启动的验证,不管是人还是 agent,能只凭仓库就把"做完"重建出来,不用信任任何人对那场对话的记忆。

整件事的形状

退一步看,这四个动作是同一个动作换了身衣服:把裁决权从模型手里拿走。

纯核心给 agent 一个能证明、而不是只能断言的东西。一条命令的验证,给"做完"一个 agent 改不了措辞的定义。一个独立的判官,不让作者批自己的卷子。一份证据账本,让证明活得比产出它的上下文更久。它们每一个,都是一次权力转移:从模型里那个自信的解说员,转到模型外一个确定性的检查。这次转移就是全部的赛点,因为模型的自信跟正确性不相关,而且永远不会相关,模型再强也一样。

这也是整个系列的那根主线。仓库是 agent 编程时要对着写的接口。契约让它的边界看得见。闸门让它的规则成真。验证让它的声明可查。这些没有一样是为了让 agent 变聪明,agent 是租来的,它在按一个你管不着的节奏自己变聪明。它们是在 agent 周围盖一个底座,好让一个又快、又死板照字面、又失忆、还有点过度自信的工人能在规模上干活,而系统接得住它漏掉的东西。

Agent 会越来越擅长写代码。我们的活,是越来越擅长盖那个判断代码对不对的东西,并且确保那个东西,永远不是 agent 对它自己的评价。


这是 agent 可维护仓库系列的最后一篇:仓库即 agent 接口默契这东西 agent 不懂CLAUDE.md 管不住 agent,和这一篇。它跟 代码开始自己写自己了 押着同一个韵:一个 agent 能验证的仓库,才是一个 agent 能放心一直改下去的仓库。


© Xingfan Xia 2024 - 2026 · CC BY-NC 4.0