agent 自己说做完了,不算数
agent 跟你说「我搞定了」的时候,这句话值多少?我只能说,基本不值钱。挂测试的烂代码反而是小事:你看见它红了,修掉,这事就过去了,没人会把一个红着的东西推上线。真正会伤到你的是另一种情况:代码是坏的,但它裹在一句很自信的「我已经实现好了,也验证过能跑」里送过来。因为写这段代码的 agent 和给这段代码打分的 agent 是同一个,而且它打分很松。它写的时候就带着「这个代码是对的」这个信念,你让它自己检查,等于让它把这个信念再确认一遍,那当然是过的。所以 agent 的自评基本没有信息量,它说搞定了,和它真的搞定了,是两件不相关的事。
这篇是 给 agent 盖一个能维护的仓库 系列的最后一篇。前三篇其实一直在铺垫这个问题:结构 管的是 agent 去哪儿干活,契约 框住它能弄坏什么,到 闸门 那篇,讲的是怎么把写在文档里的规则变成真的会拦人的检查。这些加起来就是为了最后这件事:「做完」不能由 agent 自己说,得从 repo 里挣出来。具体大概是四个动作,一层叠一层。
「我搞定了」不算数,exit 0 才算数
1. 先给它一个能证明的东西
第一个动作跟架构有关。代码分两种,一种 agent 能自己验证,一种它验证不了,这个区别是架构决定的,不是模型能力决定的。
一个纯函数,输入进去输出出来,不碰文件系统、不碰网络、不碰数据库,没有时钟、没有随机、没有环境变量。验证它只要一个单测。agent 自己写测试、自己跑、自己看它过还是挂,整个闭环都在它自己的 loop 里,不用 provision 任何东西。这种代码就是 agent 最好的修复靶子:oracle(判对错的那个东西)就在手边,查一次不要钱,它可以无人监督地一直迭代,迭代到对为止。
但你把同一条业务规则抹进一个 React 组件里,这个组件还顺手拉了数据、读了 localStorage;或者抹进一个 controller 里,它还顺手开了事务、调了 Stripe。这时候要验证,agent 需要浏览器、数据库、凭据、网络,这些它在自己的 loop 里一般凑不齐。凑不齐它会干嘛?它会做它唯一能做的事:把 diff 读一遍,觉得看着挺对,宣布做完。注意这里逻辑本身可能从头到尾都没错,坏就坏在架构把这条规则弄得没法验证了,而假完成基本都是从没法验证的地方长出来的。
所以分层这个事——core 保持纯、application 做编排、ports 当接口、adapters 装副作用、generated 单独隔离——它真正的价值不是审美,是可验证的比例。你每往纯 core 里下沉一条规则,就多一条 agent 能自己证明的规则;每一条困在副作用层里的规则,agent 对它只能断言,断言就是「我觉得对」。六边形架构讲白了就是在干这一件事:把系统里「agent 不用 provision 整个世界就能真正验证」的那部分做大,越大越好。
2. 验证收成一条命令
第二个动作,是让「这做完了吗」只有一种问法。
很多 repo 的验证是这样的:改这类文件要跑单测,改那类要跑契约测试,动了 CSS 才要跑视觉回归,架构规则又在另一个 lint 配置里。这套东西没写在任何地方,全靠懂的人心里有数,这就是 tribal knowledge。tribal knowledge 摆在 agent 面前的下场前面讲过了:被跳过,被跑一半,或者被瞎猜。
所以要收拢成一条命令,make verify 或者 pnpm verify。一次跑完整套:类型、lint、单测、契约、集成、架构检查、巨文件检查、安全检查,最后给一个 pass 或者 fail。CI 跑的也是同一条命令。这样「做完」就有了一个特别无聊但特别硬的定义:这条命令以 0 退出;退不了 0,就把确切的报错原样记下来。不需要判断,不需要感觉,agent 没资格定义做完,讲道理你也没有,命令说了算。
配套的纪律就一条:agent 说「我验证过了」,但没贴出那条验证命令的输出,就按没验证处理。声明和证据得是同一个东西,缺一半都不算。
3. 写的和判的不能是同一个脑子
第三个动作对付的是「批自己卷子」的问题。因为哪怕 verify 全绿,也可能盖住一个作者压根没想到要查的缺口——全绿只说明查了的都过了,不说明该查的都查了。
修法是把写改动的 context 和判改动的 context 分开。写实现的 agent 泡在自己的假设里,它知道自己想干什么,所以看什么都像自己想干的那个样子。换一个全新的 context,或者干脆换一个来验的,这些假设就没了,它会看到那个没被重跑的文件、那个没覆盖到的情形、那个悄悄退化掉的行为。具体做法很多:干净 checkout 上的 CI,一个对实现过程零记忆的验证 subagent,一个 review bot,或者就在作者没碰过的干净 worktree 里把闸门重跑一遍。机制随便挑,原理是同一个:作者做没做完这件事,让作者自己来判是最不靠谱的。
写的脑子和判的脑子必须分开:别批自己的卷子
我觉得这可能是 agent 时代最被低估的一个想法,而且不止能用在代码上。我自己从来不当 agent 产出的唯一审查者,我一定留一道单独的关,这道关的全部工作就是怀疑,因为生成的脑子和验证的脑子不该是同一个。谁来 review AI 写的代码 这个问题,我感觉最后会变成决定整件事能不能 scale 的那个问题。同一个 agent 自己写自己验,那不叫验证,它只是把写的时候的那个信念又复述了一遍。
4. 证据得活得比 session 长
最后一个动作,是给那些一次干不完的活准备的,而真实的活基本都是一次干不完的。
agent 跨 session 是失忆的,就算在同一个长 session 里,context 也会被 compact,早期的推理被摘要掉腾地方。如果「做了什么、验证过什么」的证据只活在 context 里,它就会在你最需要的时候蒸发:一个跨好几天的 migration,或者一次大重构,干到一半 context 没了。下一个 session 半瞎着开局,要么把干完的活重干一遍,要么更糟,把没干完的活当成干完了,因为那条失败早就滚出屏幕了。
修法是把证据写进 repo,让它留得住。具体就是几个 agent 边干边维护的文件。一个进度文件,记当前到哪了、下一步干什么。一个证据文件,装命令的输出,真正跑出来的那些绿色记录,不是一句「测试都过了」的转述。一个决策文件,记架构上做过什么选择、当时怎么取舍的,免得后面的 session 把同一个问题重新吵一遍,或者不知情地跟它对着干。证据文件只有一条硬规则:装证明,不装「看起来像证明」。贴出来的绿色运行算,干净的 diff 算,以 0 退出的检查算,「我相信这能跑」不算。这套东西的意义是,任何一个冷启动的验证者,不管是人还是 agent,只凭 repo 就能把「做完」重建出来,不用信任任何人对那场对话的记忆。
做完不做完,轮不到 agent 自己说
这四个动作各管一段,合起来,「做完」这件事就不归 agent 自己说了。纯 core 给它能真正证明的活,不用停在「我觉得对」。verify 收成一条命令之后,「做完」的定义就焊死在退出码上,措辞再漂亮也绕不过去。验收换一个脑子来做,作者给自己打分这条路就断了。最后证据落在 repo 里,证明能活得比产出它的那个 context 长。这几样每一个都是在把裁决往模型外面挪:从模型里那个很自信的声音,挪到一个确定性的检查上。挪这一下就是重点,因为模型的自信跟对不对基本不相关,这一点我不觉得模型变强能改。
这也是整个系列的主线。repo 是 agent 编程时对着写的那个接口。契约把边界摆到明面上,闸门把文档里的规则变成真的会拦人的检查,验证再往前一步,让「我做完了」这种声明变成可以查的东西。这里头没有一样是在让 agent 变聪明。agent 是租来的,它自己会变聪明,节奏你管不着。这些是在 agent 周围把底座盖好,让一个跑得快、只认字面、隔个 session 就失忆、还总觉得自己没问题的工人能放量干活,它漏掉的东西系统能接住。
agent 写代码只会越来越强,这个不用操心。要操心的是判断代码对不对的那套东西是谁盖的、靠不靠得住。我现在能确定的只有一条:这套东西不能是 agent 自己评自己。剩下的,边做边看吧。
这是 agent 可维护仓库系列的最后一篇:仓库即 agent 接口、默契这东西 agent 不懂、CLAUDE.md 管不住 agent,和这一篇。之前写过的 agent 开始自己改自己的代码了 聊的其实也是这一摊事,可以搭着看。