
百万行 Lua,自研框架,模型几乎没见过——代码让给 AI 之后,人退到判断上,把取事实的活交给机器。
前言:AI 已经能端到端交付了
这个季度我们做了一个内部平台,给我们的 coding agent 做支撑:七个服务,三个月,一千八百多次提交,十六万行生产代码、十一万行测试代码、八万多行前端,没有一行是人写的,设计文档也全部由 AI 落笔。
这个平台是对照组:通用技术栈,全新项目,没有历史包袱,对 AI 最友好的那种环境。我们先在对照组里把”代码全让给 AI”真正跑了一遍。这也意味着,我们的业务代码离 AI 端到端交付,已经不远了。
人做了什么?做了大量的决策。
(一)人的工作量没有减少,只是从落笔挪到了拍板
我原本以为 AI 全权接过代码之后,人会轻松下来。实际上完全没有。每天要读的东西一点没少,只是从”读代码”变成”读方案、读结论、读证据”;要拍的板一点没少,只是从”这段怎么写”变成”这条路走不走、这个失败算不算问题”。
仓库里现在留着七十多篇设计文档,全部由 AI 落笔,每一篇的取舍都是人定的。但真实产出远不止这个数——海量的 spec 文档最后被我删掉了,删完项目照跑,没有任何损失。 留下来的这些,还有一部分是我专门让 AI 照着已经落地的实现反推重写的:文档跟着代码走,不是代码跟着文档走。这里有个区分值得说清——动手前那批用来对齐的 spec,用完就可以扔;事后沉淀的设计说明得留着,因为它是给下一个要改这块代码的人建立心智模型用的。 两者长得像,寿命完全不同。
这件事顺手回答了我在上一篇里没想透的一个问题。SDD 流行之后,很多人把 spec 文档当成核心资产。可如果文档真是核心,那我删掉的就该是核心,项目该出事——它没有;而核心工作既然已经被 AI 接走,人的工作量该降下来——它也没降。
两头都对不上,说明核心从来不是 spec 这个载体,而是产出它的过程中被迫想清楚的那些事。spec 的价值在于逼你在动手前把问题定义清楚;一旦想清楚了,文档本身是可丢弃的。 真正不可替代、也无法委托给 AI 的那一步,是想清楚它——brainstorming。
(二)AI 犯错不是概率问题,是规模问题
即便在那个最友好的环境里——Go 加 TypeScript,全新项目,工具生态齐全——三个月下来,它照样攒了一份事故清单。资产入库卡在中间状态四次,每次都在提交一个空 commit;覆盖率看板把”采集失败”和”没有数据”显示成同一个东西;重试队列里的僵尸任务把整条调度锁死;需求提取上线第一晚积压四千多条。
这些事故让我把一个模糊的直觉想清楚了:不管什么技术栈,AI 产出都需要大量保障工作,因为出错的绝对数量不由单次准确率决定,由累积量决定。 迭代次数足够多、产出代码足够长,再低的错误率也会积成一堆真实事故。而人的审查能力,一点都没跟着涨。
所以那种说法——模型越强,harness 就该越薄——是想当然。模型变强只会让产出更多、更快,人这一端的上限一动不动,缺口只会更大。
(三)有一类错,AI 特别容易犯
这些事故里有一类特别值得单独说:不是功能不通,而是失败不可见。
AI 写得出完整的状态机,但最初漏掉了”合并失败”这一态,于是失败没有名字,只能卡在”正在合并”里原地打转。AI 写得出覆盖率看板,却把采集失败和真的没覆盖显示成同一个 0%。AI 写得出重试队列,却让僵尸任务把调度锁死。
AI 交付功能的能力,明显强于它交付”让失败可见”这种纪律的能力。 因为前者是需求,后者是价值观——而正如后面会讲到的,AI 的训练是应试式的,它目前还理解不了价值本身。
这就是人的决策不可替代的地方。人在这些事故里做的判断是”这个失败必须有名字””这两种 0% 必须分开”。它们不写在任何需求里。
(四)三个发现指向同一句话
机器提供事实,人做判断。
它是一把可以直接用的尺子:手上这件事,是事实,还是判断?是事实就交给机器,别让人去核对,也别听 AI 声明;是判断就留给人,别指望机制替他拍板。
这三个发现都是对照组给的。我们真正要天天干活的地方是另一边:百万行 Lua,自研框架,市面上的模型几乎没见过,历史债一层叠一层。连对照组都要这么兜底,这边只会更需要。
所以下面五件事都是在这边摸出来的,它们是一条递进的链:AI 那一端得顶得住,提示词顶不住的沉进框架;框架顶不住的,继续往下沉到 runtime;干得好不好,要能量出来;机器顶住了、量得出了,才知道人该退到哪里;最后把这些判据变成每天都在跑的流程。每一步的动作其实是同一个——把某个环节从”信声明”搬到”信事实”。
全文大纲
| 部分 | 我们曾经信 | 后来改信 |
|---|---|---|
| 一、提示词的尽头是基础设施 | 再强调一遍,AI 就该记住了 | 注意点堆在提示词里模型会变笨,沉进框架才越厚越强 |
| 二、编排的尽头是 runtime | 官方机制够用,skill 能补上 | 机制残缺补不动,runtime 必须可审计、可干预 |
| 三、分数不可信,问题在题集 | 评测得建成一套完整的系统 | 判据必须可执行,工程量不代表可信度 |
| 四、人退到决策点 | 人逐行 review 兜得住 | 有些事实只有机器取得到,门禁 AI 绕不过 |
| 五、把判据变成流程 | 一次会话做到位,信 AI 的自跑报告 | 大环套小环多重检查,环与环之间只传事实 |
以上五条是本文的核心内容,也是五个故事,下文会按这个顺序逐条讲。
第一部分:提示词的尽头是基础设施
本部分要点
- 提示词的边际收益会耗尽:注意点越堆越多,模型注意力越碎,主任务反而越差。
- 约束要沉进框架:声明式 mock 自动还原、trace 超时分析包,是用框架“取消这个错误的存在”,把 AI 的注意力还给主任务。
- 报错通道本身就是提示词注入通道——最好的提示词是恰到好处的反馈;但带错方向的反馈比没有更糟。
- 下沉的标准:判据是确定事实才做硬门禁,是代理指标最多做提醒;信号要从外部取;误报会逼人把代码写得更差。
先看 AI 那一端怎么顶得住。这一部分讲一个所有做垂类 agent 的团队都会撞上的现象:提示词的边际收益会耗尽。
AI 时代,几句提示词就能做出像模像样的 demo。但往生产可用做深,最后都会走到同一个地方:基础设施。
以我们的测试 agent 为例。先交代背景:项目技术栈特殊,我们专门开发了端到端测试框架,给 AI 用之前已经内部用了数月,框架成熟之后才交给 AI。测试 agent 的提示词和流程编排又打磨了数月——然后很快进入瓶颈期:不管怎么调提示词,都调不出初期那种立竿见影的效果。
我当时的第一反应还是继续写提示词。 措辞再强硬一点,规则再细一点,例子再多给一个。这个方向撞了很久的墙,直到我把手头所有测试会话摊开来分析(用的是自研会话分析工具 tracesift),才看清剩下的问题在提示词和流程层面根本解不掉。举两个例子。
(一)例一:mock 还原
测试的经典范式是 setup/teardown:开始前 mock,结束后还原。提示词里反复强调”setup/teardown 必须成对出现”,AI 仍有约一半概率漏掉 teardown。后果是用例互相污染,整个验证过程乱到不可控。
常规思路是加 lint hook,但这个问题很难用规则式 lint 查出来,AI 也仍有概率绕过。后来我换了个思路:为什么不在框架层解决?就像自动 GC 一样,我给框架补了声明式 mock 和自动还原——mock 声明出来,用例结束框架自动还原。之后 AI 在这里几乎不再犯错。
写测试要同时顾的点太多,这个任务对 AI 其实很重。用框架消掉一整类错误,等于把 AI 的注意力还给主任务。
(二)例二:trace 链路分析
AI 跑测试最常撞的问题是链路超时。这类问题要读大量代码、追调用链,在写跑测这种本就繁重的任务里再塞进重度分析,AI 经常耗时极长,还有一定概率上下文过载、注意力涣散,彻底解不出来。
我重构了框架内的 trace 实现:链路超时报错时,自动返回准确原因和链路分析包。
裸超时只能告诉你”没等到回包”。所以框架在超时那一刻反向探测各个服务的队列和限频登记表,按探测到的现场把超时分类:还在排队、出队了但业务代码没回包、在分发层被限频丢掉、回包到了但当时没人接。每一类后面跟的不是现象描述,是下一步动作——比如”检查前置请求是否用完了限频配额”,连该去看哪个配置字段都点出来。分析包里还有一节叫代码执行路径:框架打开业务源码,把这条链路最后执行到的那几行原样贴进报错,末行标一个箭头。AI 不必再猜卡在哪,它直接看到卡在哪一行。
这个设计有个值得单独说的副产品:报错通道本身就是提示词注入通道。 顺着它可以返回任何 AI 需要的信息,而且时机精准——它正好在 AI 卡住的那一刻到达。
最好的提示词,是恰到好处的反馈。
但这条通道也会反噬。框架里曾经有一个告警:AI 等一个不在协议表里的协议名,就提醒它一句。后来我们把它删了,理由写在文档里——协议表是按线上流量生成的,冷门协议本来就收不全,登陆窗口期的协议更是全都不在表里;于是这个告警信噪比很差,还会把 AI 带偏。注入的信息不是越多越好,会带错方向的反馈,比没有反馈更糟。
(三)两个例子的共同点
解法都不在 agent 层,在 agent 底下的框架层。提示词层面你只能”叮嘱”,框架层面你可以”取消这个错误的存在”。
最直接的证据是一条纪律的消失。自动还原上线之前,我们靠人肉纪律维持秩序:会改脏环境的那批用例必须排到最后跑,跑完还得重启服务。等自动还原和静态闸门都到位,交接文档里写下的是——这条纪律”自此作废”。不是 AI 变听话了,是这类错误不再可能发生,纪律没有了存在的理由。
(四)注意点加不下去了
测试 agent 的那个 skill 叫 ut-tester。SKILL.md 四百多行,排坑、模板、注意点铺了十几份文档。2 月到 6 月每个月都在往里面加;7 月之后几乎停了。不是做完了,是加不下去了。注意点越多,模型要同时记住的事越多,注意力越碎,写测试这个主任务反而越差。
这就是”harness 越厚,agent 越笨”真正成立的地方。开发环节充斥的就是这种约束:skill、SOP、流程说明,靠模型按你写的走。它要有——领域里那些稳定的事实、能力边界,不写模型不知道。但它有上限。再厚,厚的是模型的工作记忆。
打补丁这一档尤其不要写。模型走错一次,就加一条”以后必须走这条路”。我们写过”严禁 mock ss,只有一种正确写法”,八天后对着仓库里 529 个存量单测才发现,这条从落笔那天就和事实相反。它不是用久了才坏,是生来就在拿有限规则覆盖无限问题。
同一时期力气转到了 ut 框架。6 月框架侧的提交大约是 skill 的四倍,7、8 月还在加覆盖率制品、把 trace hook 往更底层沉、给隔离运行补协议。ut-tester 最后一次实质改动,标题就是”对齐 UT 框架近期改动”。mock 自动还原、超时分析包,都是把原来写在 skill 里的叮嘱从模型脑子里挪走。挪走一件,主任务就清静一点。
所以有两种”厚”,别混。写在提示词里,越厚模型越笨;写进框架,越厚模型越省心,主任务上反而更强。验证是第三件事,发生在产出之后:核它交出来的测试是不是真能抓 bug、报告有没有撒谎。那是后面评测和验收的事,不是开发环节的 SOP。
生产级 agent 建设,最终都归到基础设施建设。AI 时代基础知识不会过时;相反,框架设计、编译原理、协议栈这些硬功夫,正是决定你的 agent 能不能走出深水区的东西。
(五)那是不是什么都该往下沉?
约束型的注意点不该无上限地堆,但不等于能沉的都该立刻沉。判断标准就摆在我们自己的一个 hook 文件里——同一个拦截器里的两条规则,待遇完全不同。
一条是”禁止读 protocols 目录”,判据是路径前缀比对:目标路径在不在那个目录下,要么在要么不在。它至今是硬拦截,AI 撞上就是撞上。
另一条是”编辑 .lua 之前必须先读编码规范”。它一开始也是硬拦截,判据是扫会话记录,看 AI 有没有读过那两份规范文件——上线两周后降成了警告。因为这个判据不是事实,是代理指标: 读过不等于遵守,没扫到也不等于没读过。它自己还翻过一次车,代码注释里留着结论——主会话累积了所有历史 Read,会把”已读”这个状态污染给之后的每一个子 agent。
判据是确定的事实,才能做硬门禁;判据是代理指标,最多做提醒。 这跟前言那把尺子是同一把,只是换了个用法:那里分的是交给机器还是留给人,这里分的是做成机制还是留在提示词。
顺着这条标准,还有两件事值得说。
一是信号要能从外部取到,不能靠 AI 自述。 前言那个平台的测试跑批上有一道硬失败:脚本报告成功、报告文件也生成了,但覆盖率是 0/0 行——直接判失败。它防的是一类很隐蔽的假测试:AI 把被测函数的内部逻辑抄出来,用本地变量重算一遍再断言。断言全过,覆盖率零,测不出任何回归。这种错误提示词防不住,因为 AI 自查也会认为自己做对了;只有从外面看”被测函数的行一次都没被执行到”,才戳得穿。
二是门禁误报的代价比想象的高。 ut-lint(我们写的 Lua 测试静态检查)里有两条规则明知易误报,我们始终没敢改成阻断,只在 reviewer 的提示词里要求:每条 lint 结果都要读源码二次确认,采纳还是判为误报,都得写明理由。因为误报的真实代价我们见过——改进其中一条规则时,目的写得很直白:不再为了过 lint 把 helper 拆开内联。门禁误报的后果不是浪费一次提醒,是逼着人和 AI 为了过检查,把代码写得更差。
最后补一件不太顺利的事。ut-lint 最早只写在 reviewer 的提示词里,要求它评审前先跑一遍——还在约束层,靠模型记得去跑。想让它往下沉,第一个想法是做成 SubagentStop hook:子 agent 一结束就自动跑,结果直接塞回上下文。做不成——Claude Code 和 codebuddy 的 hook 协议里,SubagentStop 只支持”阻断并给出理由”,不支持往上下文里注入内容。我们要的是提示,协议只给阻断。于是方向改成把 lint 触发塞进测试执行脚本本身:跑测试就自动跑 lint,脱离 agent 框架,CI 也一起受益。这条还没落地,但它比 hook 那条路更彻底——同一套规则,从”叮嘱模型去跑”变成”环境里跑测试就会跑”。
这件事透出一个更麻烦的问题:能不能往下沉,一半不由我说了算,取决于我脚下那层给不给接口。
第二部分:编排的尽头是 runtime
本部分要点
- workflow-engine 换来了稳定,但 token 消耗、体验、主 agent 闲置三个问题越来越扎眼。
- 两项 agent teams 实验:完整流程太脆没推广;收窄到测试流程的 /module-test 跑稳了,上百次失败会话熬成 agent-teams skill。
- skill 能缓解机制的不完善,缓解不了机制的残缺;三起黑盒事故证明 agent loop 的每个环节都必须可审计、可干预。
- 编排问题追到根上是 runtime 主权问题;选型的真问题不是“选哪个产品”,是主权的边界划在哪。
框架层能解的都解了,剩下的要么解不掉,要么像 ut-lint 那样被底座的口子卡着。编排怎么走、上下文怎么压缩、工具参数怎么拼接——这些到底在哪一层?
(一)workflow-engine 的三个遗留问题
上一篇收尾时,我们的多 agent 编排停在 workflow-engine:主 agent 做成状态机,按固定流程 spawn 子 agent。当时的结论是”真正贵的不是 token,真正贵的是失控”,所以坚定选了稳定。
跑了一个季度,稳定是换来了,但三个问题越来越扎眼:
- token 消耗大子 agent 每次从干净上下文出发,同一份代码反复探索。多轮循环里尤其明显——review 打回三轮,implementer 就把相关代码读三遍。
- 体验不佳编排逻辑复杂,上手门槛高;子 agent 干活的过程不透明,用户没有掌控感。
- 主 agent 浪费了它只维持秩序,最值钱的能力——直接和用户对话——闲置着。
所以这一季度我们重新打开了当初关掉的那扇门。不是因为”失控比 token 贵”这个判断错了,而是因为光有”稳”已经不够用了。两个方案并行推进:
- 方案一:需求分析、架构设计这两个交互最重的环节收回来,主 agent 亲自做;拉子 agent 时优先续接(resume)上一轮的,不重新 spawn。好处有三:交互最多的环节,体验向单 agent 看齐;主 agent 派活时能带上更多有效上下文;子 agent 带着上轮记忆接着干,不用重新探索。这条路风险低,已经落进现在的 /dev 流程。
- 方案二:Claude Code 独有的 agent teams。本质是网状的多 agent 通信:成员常驻,互相直接发消息,共享任务看板。常驻天生适合多轮循环,网状通信把编排的灵活度拉满。单看理论,这是解上面三个问题的绝佳方案。
方案二风险大——直到今天,agent teams 除了官方文档几乎没有公开实践,官方也一直把它标为实验特性。这条路由我来蹚。
(二)第一项实验:把整个开发流程搬上 Team
五月底我做了第一项实验(experiments/2026-05-29-team-driven-dev),思路很激进:
- 去掉
/dev命令和 workflow-engine 状态机换成两个 skill 串联:brainstorming(主 agent 交互式设计,产出 arch.md 和 plan.md)→ team-execution(Team 驱动执行)。 - 产物驱动状态不用 state.json:arch.md 不存在就从头开始,plan.md 存在就直接执行。中断恢复靠查磁盘产物,不靠状态机。
- 七个角色进 Teamplanner、implementer、reviewer(可多实例)、test-planner、test-engineer、explorer、guardian。审查循环点对点直连——implementer 和 code-reviewer 互发消息对审,不经主 agent 中转。
- guardian 异步守护一个只盯”用户说的话和主 agent 的理解是否一致”的旁观者,防长对话里曲解需求。
设计在纸面上很美,实跑下来,提交历史里全是补丁。踩的坑分四类,从浅到深说。
第一类:模型根本不认识这套机制。 Anthropic 没把 agent teams 训练进模型,所有行为全靠提示词现教。于是:主 agent 拿到需求直接开聊,忘了先建 Team——只好在 skill 里加”STEP 0 强制前置,不可跳过”;工作流目录命名在 kebab-case、CamelCase、中文之间摇摆——只好把占位符直接写死成 snake_case;guardian 看到”通过 tracesift 工具读取日志”,跑去内置工具列表里找 tracesift,找不到就放弃走兜底——只好改成”tracesift 是 shell 命令,用 Bash 调用”。每一条都是一次失败会话换来的提示词补丁。
第二类:中断活锁,栽得最狠的一个。 一次完整实跑(约 2.5 小时的真实需求)后复盘,planner 反复”莫名 idle”,产不出 plan.md。用 tracesift 解析会话记录,才挖到根因:主 agent 大约一分钟催一次 planner,而 planner 单轮热身——重载方法论 skill、读 arch.md、补做被打断的验证——就要超过一分钟。每条催促都打断它在途的工具调用,本轮作废,输出一轮比一轮少:580 token → 278 → 最后只剩 1。planner 在会话记录里留下原话:”我的 skill 加载和验证查询又被消息打断了。”更糟的是,主 agent 把这误判成”Team 在 resume 后失效”,于是重建团队接着催,活锁就这么自己养活自己。
这次复盘换来三条铁律,后来都进了 skill:不催正在工作的 agent,只认显式 DONE/BLOCKED 信号,idle 是正常态(我们叫它 PROTOCOL-A);planner 这类角色改成任务来了一次性 spawn、自包含干完;测试断言从架构文档的验收标准提前冻结,实现完成后只填占位符,防止有偏差的实现反过来污染测试。
第三类:成员的生命周期靠不住。 进程内的成员随主进程消亡,会话续接后活体状态全丢,主 agent 却还在给不存在的成员发消息。对策只有一个:一切以磁盘产物为准,失联就拿磁盘上的 arch.md / test-plan.md 无损重建成员,不指望常驻永远活着。
第四类:机制本身反直觉。 你其实强杀不了一个不配合的成员——强杀工具要 taskId,你 spawn 拿到的却是 agentId,两者不互通。成员不配合关停时,解散团队的调用会一直失败,唯一解法是手动改 ~/.claude/teams/ 下的 config.json,把成员摘掉。任务一旦指派了归属成员,成员之间的任务窃取就关掉了,它卡死之后名下的活没人能接。这些没有任何文档写,全靠翻源码和实机踩坑。
完整流程的编排最终没有推广——不是跑不通,是太脆,我没信心交给其他同学用。但它留下了一样值钱的东西:agent-teams skill——从上百次失败会话里熬出来的运维知识库。内容就是把上面那些坑一条条理清楚:怎么读懂成员状态(idleReason 三态);失败怎么按 failureReason 语义分流——限流就退避重试同一成员、绝不换人,认证失效就停手找人;卡死判据用”超时没消息”,不看状态符号;防消息风暴和中断活锁的通信纪律。
注意这几条纪律的共同点:全部是”不信声明,信事实”。 不信状态符号,信超时有没有消息;不信活体成员,信磁盘上的产物。
(三)第二项实验:收窄战场,只在测试流程用 Team
完整流程靠不住,那就收窄:只把测试流程搬上 Team,并且严格限定通信方式和流程。这就是现在的 /module-test:
- 主 agent 亲自写 spec——spec 是”自带答案的交接物”,协议锚点、读回方式、验证点矩阵全部代码核实过,不许留空。
- Team 里只有两个成员:test-engineer(写计划、写代码、跑测试)和 reviewer(独立审查)。两人点对点直连互审,主 agent 不中转、不代审,只按 agent-teams skill 监督进度、容错兜底、三轮不收敛就升级给用户。
- spec 的验证点矩阵是审查的权威基准,早早冻结。测试红了且对应 spec 验证点,按代码 bug 处理,绝不软化断言求绿。
这套编排我跑了几十遍各类任务。有 agent-teams skill 和 test-generator skill 加持,整个流程能比较稳定地走完,还保住了 agent teams 最大的优点:我随时能和任意 agent 直接对话,自由干预任何环节——这正是 workflow-engine 给不了的掌控感。
但它有个编排层面无解的缺点:reviewer 常驻,复审时免不了被自己上一轮的意见带着走,只盯上轮问题,审不全。彻底解决只有一条路:每轮评审都 spawn 新 agent。权衡评审时长和 token 消耗,我们最终留下了常驻 reviewer——明知有损的取舍。
两项实验下来,我的判断是:agent teams 方向是对的(网状通信、常驻成员、自由干预),当前实现是残缺的——模型没被训练过这套机制,工具的提示词和行为对 LLM 不友好,建在它上面的编排逻辑,怎么写都脆。skill 能缓解机制的不完善,缓解不了机制的残缺。
那”残缺”到底缺在哪?缺在我们对 runtime 没有主权。空口无凭,接下来三件真实事故就是证明。
(四)三起黑盒事故
第一件:Claude Code 的遥测事件。 claude code 会用隐蔽手段检测使用者是否来自中国,命中后在提示词和工具调用里投毒。细节这里不展开,但它直接动摇了”把生产流程建在一个黑盒 CLI 上”的信任基础。
第二件:CLI 写长中文文件时的 UTF-8 损坏。 我们用 CLI 批量生成中文技术文档,37 份产出里 36 份损坏、累计 370 处 U+FFFD,根因在 CLI 拼接工具调用参数时按字节切断了多字节字符(第四部分讲验收时还会从另一个角度撞上它)。这段代码在黑盒里,我改不了。
第三件:压缩 bug 导致的 11 小时空转(完整复盘见团队博客《一个 agent 空转 11 小时的复盘》)。一个只读探索任务跑了 11 个小时、6556 次工具调用、1.23 亿 input token,一无所获。四个因素叠在一起:max_turns 限额没生效;压缩阈值卡在 100K,模型 200K 的窗口只用了一半;压缩摘要把”生成摘要”这条指令当成了用户请求,对恢复最关键的”当前进度””待办事项”两节经常只剩标题;于是每轮压缩后 agent 都失忆,从同一组关键词重新 Grep。203 轮压缩,就是 203 次从头再来。
(五)合流:自建 agent runtime
两条线到这里合上了。编排实验证明:机制残缺,skill 补不动,要改只能改 runtime 本身。三起事故证明:agent loop 里的每个环节——上下文怎么压缩、工具参数怎么拼接、限额怎么执行、遥测发了什么——都必须可审计、可干预。 claude code 和 codebuddy 都不开源,开发者只能靠提示词和 hook 从外面伸手,出了问题,定位都要靠逆向会话记录。对垂类场景,这种定制能力太弱了。
自建 agent runtime,有必要。
那成本呢?会不会重复造轮子?把选项摆开:
- 从 trpc-agent-go 这类框架写起:真的重复造轮子,agent loop、工具协议、上下文管理全得自己做。
- 基于 trpc-agent-go 做薄封装:必然有别的团队在做同样的事——说实话我的第一反应也是它,笑。
- 基于 claude/codebuddy 的 agent SDK:底层还是黑盒,三起事故一件都躲不掉。
- codex CLI / SDK:开源,可定制,但体量大,理解成本高——而且它的开源没有看上去那么彻底,下面单独说。
- pi agent:MIT 开源,自称”最小 agent harness”,系统提示词不到 1000 token,子 agent、plan mode 这类功能故意不内置——要就自己用 TypeScript 扩展写,扩展能碰到工具、命令、事件和整个 TUI,还能打成包分发。它挂在首页的卖点里有一条正对我们的痛处:极简系统提示词加可扩展性,让你真正做上下文工程,控制窗口里放什么、怎么管理。边界也摆得明白:能改的是它预留的那些扩展点,agent loop 本身在 core 里,是代码,不是插件。
- DeepSeek Harness:写这篇的时候才开源的 v0.1 开发者预览,MIT 协议,”一切皆插件”——模型、工具、技能、会话、沙箱、存储、循环、调度、UI 全部是插件,底下的 Cordis 元框架只负责插件的加载卸载和依赖关系。不动它的源码,就能替换其中任一能力,包括 agent loop 自己。
codex 值得多说一句,它暴露了一个更隐蔽的问题:开源不等于主权。它新版的多 agent 通信把子 agent 的任务载荷加密,密文只有 OpenAI 服务端解得开——父子 agent 都接 OpenAI 才能通,子 agent 换别家模型,密文就跨不过去;本地调试连真实任务文本都看不到;第三方想提供兼容的 Responses API 服务,也过不了这道解密。名义上支持多 provider,核心协议却焊死在一家服务上。说难听点,这是假开源:仓库开着,协议锁着,主权还是别人的。
codex 这条顺带把选型的真问题挑了出来:不是”选哪个产品”,而是主权的边界划在哪里——落到实处,就是插件化能划到哪一层。三起事故就是量这条边界的尺子,它们要求的可替换性一条比一条深:
- 工具参数怎么拼接——把 UTF-8 按字节切断这种事,我得能自己接管那段代码。这是最外层,多数底座都够得着。
- 上下文被注入了什么、遥测发了什么——模型看到的每一样东西,都得能按来源回看。这是审计的底线。
- 上下文怎么压缩——压缩策略得能整个换掉,而不是去猜它的阈值卡在多少、摘要模板长什么样。这条最深,因为它长在 agent loop 里面。
对照那起 11 小时空转:根因是压缩摘要把”生成摘要”当成了用户请求、对恢复最关键的两节只剩标题——这是靠逆向会话记录才拼出来的。如果每一次注入本来就有据可查,这种复盘不必从考古开始。
这把尺子一量,pi agent 和 DeepSeek Harness 的差别就不是新旧,是边界划在哪:pi 把 loop 和压缩策略留在 core 里,DSH 连这两样都推成插件,元框架只管装卸。审计那条 DSH 也接得住——模型看到的一切都进 append-only 会话日志:系统提示词、思维链、工具调用与结果、子 agent 调度,以及每一次上下文注入,都能按来源回看;恢复、分叉、回放共用同一份事件流。
架构上 DSH 更彻底,但选型不能光看架构。这两个选项在我手上的证据强度差得远:pi 我跟了几个月,也上手试过,虽然试出来的东西还微不足道;DSH 我手上只有一篇官方博客——”一切皆插件”目前还只是它自己的说法,我还没亲手量过。
这里正好用得上本文那把尺子:声明归声明,事实归事实。 顺序也就不是偏好问题了:
- pi agent 先动:几个月的接触打底,扩展门槛低,而它自我定位的重心正是上下文治理,恰好压在我们的伤口上。垂类扩充从改提示词、加工具开始,一路撞到 core 的边界就记一笔账——记下来的每一笔,都是下一步的需求清单。
- DeepSeek Harness 跟进:按”一切皆插件”的设计思路,它是可预期更好的底座,连 loop 都能换掉。但”可预期”这三个字得自己去验,不能听它说;核心插件和基础接口还在快速迭代,现在就把整条生产流程押上去,等于拿生产环境替它做测试。
要守的原则很清楚:元框架只管装卸,能力全部做成可替换的插件。路径也清楚:从最小 harness 起步,撞到墙就换掉撞墙的那块,不必一开始就背上整个框架的复杂度。真正的垂类扩充还没排上工期——那是工期问题,不是要不要做的问题。怎么走,留给下一篇。
编排问题追到根上,是 runtime 主权问题。这个认知不是拍脑袋想的,是两项实验、三起事故加一次源码审计,一起买来的。
第三部分:分数不可信,问题在题集
本部分要点
- 十七万行的评测平台只留下两个分数;16 次跑数的小实验改掉了一个至今在用的策略——差别不在工程量,在判据能不能执行。
- 业界调研的结论不留情面:有论文支撑的方法全站得住,唯一自研的挖空回填恰恰塌掉。
- V2 把主指标落在变异击杀率:不问“找没找到我藏的东西”,改问“你写的测试实际能发现多少 bug”。
- 评测的核心工作是构建题集,不是搭平台;凡是能脚本化的环节,都不值得当成工程去建设。
- 评测能帮你改进 agent,但成本业务团队很难扛:先想清楚目的,再决定上不上。
前两部分,AI 那一端顶住了:测试 agent 能干活,编排追到了 runtime。跟着的问题就是:它写出来的测试到底有没有查错能力,能不能量出来?这一部分回答它。
评测,谁都说该做,很少有人讲怎么做,更少有人算过账。这里摆两次推导:一次撞了墙,一次走通了。两次的投入差了两三个数量级,可教训落在同一个地方。
(一)先说撞墙的那次
先说清一件事:做这件事的判断本身是对的。那时候 agent 的能力确实在涨,但涨了多少、哪里还弱,全凭感觉——没有度量就没法迭代,这是当时团队里的共识,我也是其中一票。方向没错,错的是形态。
今年三月底到七月中,我们给编码 agent 建过一套很重的评测系统。107 天,评测目录累计增加十七万行(其中三万多行是题集里的参考实现)。建成的东西相当完整:从建隔离工作区到上传 trace 的六阶段流水线,四层加权评分,React 加 FastAPI 的 Web 平台(连登录鉴权都做了),为了看 trace 自部署了六个容器(Langfuse 加它依赖的 PostgreSQL、ClickHouse、MinIO、Redis),跑完自动推企微,综合分低于 90 还会触发七类根因归因。
题集是 25 道真实需求。每道题的”答案”是一份人写的百分制评分表,判定标准是自然语言——比如”某个请求从同步直接返回改为异步过滤后返回”,满足给 12 分,部分满足给一半。谁来判满不满足?LLM 逐项判。
结果:代码库里能核实的完整跑数只有两次,相隔一天,得分 69.9(D)和 51.9(F)。两次都是同一个样子——四层评分里有一层整个被跳过,报告末尾写着”归因摘要生成失败”。二十天后,这两份日志作为”清理冗余”被删掉了。
回头看,问题出在四个地方。前三个是技术性的,第四个不是:
第一,判据不能执行。 判据就是”凭什么说这次做得对”。最初我们拿人写的参考实现当答案,逐行比对文件和函数覆盖率——agent 换一种同样合理的架构实现同一需求,就被判成”重大偏离”。后来改成 checklist 逐项打分,主指标就整个落在了 LLM 的语义判断上。这笔账在后来的方法论审计里被算清了:四层评分里确定性最强的那层(跑冻结单测),25 道题里有 22 道根本就没有,它一缺,确定性判分的占比从 65% 掉到 40%,LLM 判分的影响力升到 60%——主指标实质上交给了一个 Cohen’s Kappa 只有 0.10 到 0.21 的裁判,而这个区间在统计学上叫”几乎不存在一致性”。事后看,答案其实早就写在同一个代码库里:那份说明文档解释过为什么不给需求和架构文档打分——”LLM 打分误差大于信号”。这个判断当时是对的,只是没人想到,它对代码同样成立。
第二,判分器自己有 bug。 有一份跑数记录,因为 checklist 的提取格式错了,得分是 0;格式修好之后,同一份日志重新判成了 98 分。同一份产物,前后两个分数差了 98 分——那个阶段的分数,还不到能当信号用的程度。
第三,把流水线维持住,本身就是一份全职工作。 那套系统留下的运维文档,讲的几乎全是它自己的事故:日志管道死锁让任务永远显示”执行中”、后台进程收到 SIGTTIN 卡死 23 分钟、子进程变僵尸、端到端单测卡死、子 agent 加载不到插件。没有一条跟”AI 写代码的能力”有关。这里没有谁不用心的问题——要让一条跨进程、跨容器、还要驱动另一个 agent 干活的流水线稳定跑起来,本身就是很硬的工程;只是它硬在了和评测目标无关的地方。
第四,卷子追不上考生。 107 天里,评测目录搬了三次家,判分方式换了三套(跑单测 → 比对参考实现 → checklist 打分);而被评的那套开发流程自己也在动——评测还在照着旧流程出题的时候,被评的那个命令已经被合并进另一个命令里了。平台越重,它追赶被评对象的速度就越慢,而 agent 的迭代速度恰恰是这个领域里最快的变量。
有个细节我记得很清楚:把评测代码拆出去独立成仓的那天,讲解这套平台怎么用的页面还在更新,设计文档的状态栏写着”已实施,持续迭代中”。当时没有人觉得这件事该停下来,包括我。
同一段时间里还有另一次实验,成本低得不成比例。 为了回答”知识库该怎么让 agent 检索”,我们做了个 A/B:8 道题,两种检索方案各跑一遍,一共 16 次跑数,一天跑完,产物就是会话记录加一份分析。结论直接落成了一次提交——不再让 agent 先读路由文档再跳转,改成直接 grep 知识库,寻路步数从 12 步降到 2 步。那个改动至今还在生产里跑。
十七万行的平台留下的那两个分数,最后没有变成任何一次改动;16 次跑数的小实验,改掉了一个至今还在用的策略。差别不在工程量,在判据能不能执行、结论能不能变成一次提交。
还有一件事值得单独记下来。方案本身很早就成型了:三月底的一份思路整理里,pass@k 与 pass^k、题库分成能力评测和回归评测两套、编辑振荡检测(同一文件同一区域被改超过两次)、越界修改检测(实际改的文件对照架构文档要求的文件)、文件命中率(访问过的文件里有多少是该访问的)——都写到了可以直接开工的程度,四天后就有了第一笔代码。可到六月做方法论审计的时候,pass@k 还挂在”待改进”里,重复跑数一次没跑成。这不是没人想做,是那会儿光维持流水线就占满了手——平台越重,想往里改一个方案就越难。想清楚一件事只要一次思考,把它落进一个十七万行的系统,是完全另一回事。前言里删掉海量 spec 文档那件事,和这里是一个道理:值钱的是想清楚,不是承载它的那个载体——文档如此,平台也如此。
吃过这个亏,后面给测试线做评测就立了一条纪律:一行平台代码都不写——只有题集、配置、脚本和跑数记录。下面是那条线的推导过程。
(二)测试线的第一版方案,和一个卡死的数字
评测对象很明确:AI 写的测试管不管用。第一版方案(V1)设计了两条轨道:
- 闸门漏斗:照 Meta TestGen-LLM 的范式,AI 产出的测试连过几道硬闸门——lint 干净、能跑、连跑三次稳定、覆盖率达标;
- 挖空回填:从测试资产里故意删掉若干条目,让 agent 做覆盖审计,看它能找回多少,召回率就是分数。
几条纪律从第一天就立下:考卷和答案独立存仓,被评 agent 的运行环境里看不到——考卷不能和课本放一起;插件版本、业务代码版本、测试框架版本、模型,全部锁死在一个配置文件里;主指标全部确定性判分——让代码的运行结果当裁判,不靠 AI 互相打分。
方案跑起来了,闭环也转起来了:跑数 → 看分 → 定向改 skill → 再跑。直到一个数字把一切卡住:某个模块的审计召回率从 100% 跌到 40%,连改两轮 skill,纹丝不动。
改 skill 改不动,就该怀疑分数本身了。扒开评分器复盘,两个问题:
第一,评分器量错了东西。 评分按文件逐个核对,挖掉的条目必须回到原来那个文件才算找回。可会话记录里写得明明白白:那个条目 agent 找到了——只是写进了同模块的另一个文件。评分器把”放错文件”记成”没找到”。40% 量的不是”能不能发现覆盖遗漏”,是”和标准答案的文件划分是否一致”。两轮修复救不回来,因为病根不在 skill,在评分器。
第二,单次跑数在追噪声。 同一批跑数里,一个模块跌 40 个百分点,另一个涨 20 个,方向互相打架。每个模块只挖 5 个条目,召回率按 20% 一跳,±1 条的随机波动和真实回归根本分不开。此前”单点回归 → 定向改 skill”的迭代,一半是在追噪声。
这一课的学费很贵:评测系统自己也是软件,也会有 bug;而错误的评测信号比没有更危险——它会把你的迭代引向根本不存在的问题。这已经是同一个坑的第二次:那套重平台栽在判分器的格式解析上,这里栽在文件粒度上。上一篇里我们对覆盖率说过同样的话,这次轮到评测自己。
(三)带着问题做调研,V1 的死因水落石出
数字不可信了,接下来不是修数字,是回头补课。我们做了一轮业界调研,五十多篇论文和基准过了一遍:SWE-Bench 系列怎么构造任务、防污染,Meta TestGen-LLM 怎么设闸门,TestGenEval 怎么算变异分数,SWT-Bench 怎么做 Fail-to-Pass。然后拿业界的尺子量自己的方案,结论不留情面:
四个核心方法里,有顶会论文直接支撑的三个全站得住;唯一自研、没有业界对标的挖空回填,恰恰是塌掉的那个。
它塌得不冤。调研里翻出一项构造效度研究:有实验里模糊测试工具杀死了 100% 的人工注入 bug,却没发现任何一个真实 bug——人造缺陷普遍比真实缺陷好发现。挖空回填考”你能不能猜出我故意删掉的东西”,本质是合成任务;召回率还能靠全量上报刷满,没有任何惩罚。方法论上两头都站不住。
由此推出第二版(V2)的设计主张,一句话:判据必须是执行结果,不能是合成清单。 不再问”你找没找到我藏的东西”,改问”你写的测试实际能发现多少 bug”。
Anthropic 后来那篇讲 agent 评测的文章(Demystifying evals for AI agents)把判分器分成三类——基于代码的、基于模型的、人工的,并指出基于代码的优势是客观、可复现、易调试。我们第一天立的那条”确定性判分、不靠 AI 互相打分”,和它是同一个结论。
(四)V2:把”能不能发现 bug”变成执行事实
V2 的主指标落在变异击杀率上——这正是本部分开头那个问题的直接答案。
AI 写的测试要闯五道闸门:有产出 → lint 干净 → 能跑 → 连跑三次稳定 → 杀死多少变异体。前四道沿用 Meta TestGen-LLM 的范式,最后一道才是真正的区分闸。为什么不用覆盖率?TestGenEval 有个实测:GPT-4o 生成的测试覆盖率 35.2%,变异分数只有 18.8%——覆盖率能靠无断言的测试刷出来,杀变异体必须有真断言。
变异体就是故意埋进业务代码的单点 bug:比较符翻转、and/or 互换、删掉 or 0 这类兜底、把状态写入整句注释掉。测试跑在变异后的代码上失败,算杀死;照常通过,说明断言没盯住这个点。算子里的后四种直接取自我们的代码评审 checklist——那是这个项目真实 bug 的分类学,照它出题,埋进去的 bug 才长得像真实事故。
而埋什么、埋在哪,才是整套评测真正费工夫的地方——选什么函数,就是在考什么能力。
V1 的十个函数是按”长相”选的——行数、if 个数、模块分散,还受限于”哪些函数恰好有现成用例”。三个后果:偏科纯计算函数(LLM 最擅长的题型,成绩虚高);现成用例全是 AI 生成的,没有人写的标准答案,”对标人写测试”这个语义根本不成立;没考虑代码活跃度,一个函数被重构,挂在它上面的 12 个变异体一次团灭。
V2 的选题改成三层:场景定业务代表性(发奖、任务、活动、成就、排行榜、聊天六大类),原型定失效模式覆盖(校验闸门、资源结算、时间窗口、状态机、兼容兜底、遍历聚合六种,同样来自评审 checklist),四轴定难度配平(分支复杂度、依赖构造成本、副作用、函数可见性)。再加六道硬门槛,每条都写明教训来源:函数体 15~80 行(太短没变异空间,太长单测成本失控)、分支密度不低于 3、依赖可 mock、可变异点不少于 6 个。并发和性能两个维度明确不入题——单测考不了。
(五)出题这条链上,人和机器各站在哪
题集建成的过程,是我做过的最干净的一次人机分工。整条链是这样的:
人给方案 → 六个 sonnet 子 agent 从业务代码取素材(一个场景一个,先读知识库的文件地图锁定范围,再进代码核验,逐条回答硬门槛,交回 34 个候选)→ 人终审定稿(按场景、原型、mock 轻重三维配平,34 选 16,排除理由全部留痕)→ 脚本造变异体(按算子轮转配额,保证各类失效模式机会均等)→ 三级验证(sonnet 初审剔掉明显等价的,参照测试执行验证定生死,人工只复核两边不一致的)→ deepseek-v4-pro 答题,同一道题答三遍取平均。
这条链上有三处最见分工:
第一,检索为什么不用脚本。 因为叫 check_ 的可能是纯转发,叫 update_ 的反而是真校验——规则匹配不出”这个函数考不考验能力”这种判断,所以这一步交给 agent。但紧接着人要复核它取回来的事实:agent 报”这个文件近一年改了 48 次”,落到候选函数上其实一年不超过 1 次——它报的是文件级,不是函数级。这就是前言说的那件事的又一次:AI 交付功能没问题,它交付的”事实”本身却常常失真,所以终审前必须补一道函数级核验。
第二,”什么算真 bug”只有人能定。 造变异体时最麻烦的敌人是等价变异——改了代码但行为不变的”伪 bug”,混进分母就永远杀不死,冤枉所有考生。真正难的不是识别它,是定义它。我们最后定下两条政策:日志差异不算可观测(否则会逼出”断言日志输出”的测试,那不是我们要考的能力);可杀性以自然构造为准(入参和缺省字段可以构造,但要把环境常量 mock 成非法类型、或者传语义荒谬的值才杀得死的,判等价)。这两条是纯粹的价值判断,没有任何脚本能替你拍板,机器只能在拍板之后照着执行。
第三,验证者自己也会错,所以分工不可倒置。 LLM 初审在三个地方栽过:and/or 翻转后短路保护失效、运行时报错其实是可观测的,它判错三个;多分支条件只分析了一支;最有意思的是同一行有两个相同符号时,它的判断理由指着隔壁那个——结论碰巧同向,也算坏留痕,所以审查提示词里硬性要求按列号指认变异点。参照测试自己也有 bug:一次全量验证里 8 个变异体没被杀死,5 个是参照测试写错了(pcall 闭包里声明的局部变量,闭包外读到的恒为 nil,断言值恰好与预期重合时会假 PASS,整类变异全漏杀),修好后全部杀死;只有 3 个是真的等价,而且这三个的形态在语法层面完全看不出来。
三级验证之所以不能颠倒顺序,原因就在这里:LLM 审查省的是机时,执行验证定的是生死。 最终 184 个变异体终裁,172 个确认可杀入题、12 个等价剔除,每一条判定都带 LLM 初评和人工复核两层留痕,终裁以人工那层为准。
开考前还有一步:用 deepseek-v4-pro 把 16 道题各跑一遍,校准区分度。击杀率跨度 12% 到 100%,宏平均 61%——题不太难也不太简单,这才敢开考。
(六)跑数纪律,和对评测自己的评测
正式跑数 16 道题、每题跑三遍,48 次跑数、约 19 小时机时。统计纪律照业界执行:只看均值不看单次;参照 Anthropic 实测的约 6 个百分点基础设施噪声,差距小于 3 个百分点不下方向性结论,3 到 6 个百分点标”可能显著”,超过 6 个百分点才拿来当改 skill 的依据。这条纪律是被 V1 的教训逼出来的,所以我们把它写进了 runner:默认三遍,只跑一遍的模式仅限空跑。
有一个细节能说明这套跑数为什么可信:统计 agent 有没有自己跑过测试,我们只认会话记录里的工具调用,不认它在文字里的自述。同一条纪律连自己家也没放过——我们那份命令配置声明里写着已经剔除 Task 工具,实际的工具列表里它还在。声明与事实不符,在自己家里也会发生。
跑完之后,我们还给评测自己做了一轮评审。最尖锐的一条是:被评的 agent 始终带着完整的测试 skill,缺一个不给 skill 的裸模型条件,”这套插件到底有没有用”答不上来。这条批评对不对?看评测的目的。目的是向别人证明插件的价值,缺基线就是硬伤;目的是优化——找 agent 的短板、指导下一步改进——就没必要答这个问题,多跑一个基线对优化没有任何输入。所以设计评测前先想清楚目的:目的决定要回答什么问题,也决定哪些问题可以不答。
(七)五类短板,和一个被数据推翻的分类学
结果里真正值钱的不是分数本身,是失败归因:把没被杀死的存活变异体聚类,聚出五类通病——不测缺省兜底(最大一簇)、不测边界相等值、只断言返回值不查状态写入、不踩非法入参、不枚举模式常量。每一类都能直接转成测试 skill 的通用规则:每个 x or default 写一条字段缺失用例,每个不等式写一条恰好相等用例,有状态写入的函数二次调用读回断言。
说句老实话:这些方向,我在推导题集时就已经想到了——选失效原型、设计变异算子,本质就是在回答”AI 写测试最容易漏什么”,答案在出题时就冒出来了。数据的作用是把判断变成带数字的清单、给短板排轻重;方向本身,不是数据告诉我的。
但有一件事是我想不到、只有数据能告诉我的:我精心设计的那套原型分类,预测不了成绩。 同挂”校验闸门”标签的两道题,一道 91%,一道 14%;”时间窗口”里是 91% 对 12%,跨度全部拉满。真正决定分数高低的不是原型标签,是”杀死它需不需要构造缺省值、边界值、状态型输入”。以后给题目标难度、做配平,得按这个来。
一套自认为合理的分类学被自己的数据推翻,我认为这是评测最该产出的东西——它比任何一个分数都值钱。
还有一条纪律必须记下来:这 16 道题既是校准题也是正式题库,照着存活变异体去改 skill,再用同一批题跑分,提升幅度一定被高估。 要么把改动限制在不含具体函数名的通用规则并在报告里披露,要么留几道题不反哺,当留出集。这叫用考题练考题,是评测最容易自欺的地方,而机器不会主动提醒你。
(八)推到最后,两件事确定了
评测的核心工作是构建题集,不是搭评测平台,也不是搭评测流程。 整个推导走下来,方案的每次进化都发生在题上:挖空清单被推翻、选题从”看长相”改成三层考纲、变异体三级验证、开考前校准区分度。而跑数、判分、报告这些”流程”,自始至终就是一堆脚本——它们的确定性正是它们的全部价值,而确定性不需要架构。凡是能脚本化的环节,都不值得当成工程去建设。这一部分开头那套平台,十七万行几乎全花在了这类环节上。
这个结论有个更大的呼应。姚顺雨老师在《The Second Half》里的判断是:AI 的下半场,重心从”解决问题”转向”定义问题”,评估比训练更重要。出题就是定义问题。 我们从工程实践里推出来的”题集是本体”,和他从研究视角给出的判断,说的是同一件事。
还有一条,说出来可能得罪人:评测确实能帮你改进 agent,但不做评测你一样能优化 agent,而评测的成本,业务团队很难扛。 帮助是真的——五类短板每类都能转成 skill 规则,原型分类学被推翻这种发现更是白捡的;可评测最贵的那部分产出,和认真分析问题本身的产出高度重合。账是实打实的:一轮校准跑数 500 多块、10 多个小时,正式跑数 48 次约 19 小时机时;题集构建的成本更高,光 token 就花了数千美元,AI 辅助之下人力成本照样下不来。
这里要把两件事分开:“是不是常”说的是资产保值,”值不值得做”说的是投入产出。 题集确实是常——真实 bug 变成的变异体、按真实事故分类学定的考纲、三级验证的流水线、那两条政策口径,换什么模型都接着用。但”保值”不等于”现在就该投”。什么时候值得上评测?要在两个方案之间做高成本取舍,或者要向别人证明改进真实存在。日常迭代,靠真实需求反复跑、靠会话分析定位短板(第一部分就是这么干的),一样走得动。
第四部分:人退到决策点
本部分要点
- AI 接过写代码之后,人退到决策点上,不再站在产出线上;验收是决策点最密集、也最麻烦的一处。
- 问题不是“要不要逐行 review”,是哪些必须人看、哪些交给机器;更麻烦的是有些错误人肉审查在原理上就发现不了——有些事实只有机器取得到。
- 能信 AI 写的测试吗?恰恰要重点检查测试:离线靠变异击杀(第三部分),在线靠平台独立重跑(第五部分)。
- harness 之于 coding agent,犹如 IDE 之于人类程序员;会话内的约束 AI 有办法绕,会话外的流水线它绕不了。
机器那一端顶住了、也量得出了,现在可以回答标题里那个问题了:AI 把写代码这件事接过去之后,人在流程里还干什么?
我的答案是:人退到决策点上,不再站在产出线上。 需求和验收是决策点最密集的两处。
需求那一侧,前言已经交代过了:文档可以全部交给 AI 落笔,想清楚这一步不行。AI 能把方案写得很完整,但”这条路走不走”它替你答不了——它会把你说的每个方向都论证得头头是道。所以这一节专讲另一侧,验收。它更麻烦,因为验收要面对的不是一份方案,是几万行代码。
(一)为什么不是”人应该看得更快”
人验收 AI 的产物,无非两条路:评审代码,测试验证。两条路各有一个尖锐的问题。
问题一:还有必要逐行 review 代码吗?
这个问题今年吵得很凶,而且给出激进答案的,恰恰是两位最有资格保守的人。
Redis 之父 antirez 那篇文章的标题就是《控制想法,不控制代码》。他的判断是:如果你掌控了软件的想法,逐行看代码是次优的、往往无意义的。 三条理由:一天 5000 行,谁看得完;LLM 很擅长写局部最优的代码,不擅长大的设计取舍,所以逐个函数扫过去收益不大,更该做的是把你心里的设计讲出来,再反过来问它”这部分的设计到底是什么、怎么运转的”,然后判断这个模型对不对;工作日只有 8 小时,读代码就是在挤占今天最该做的事——想清楚这软件要往哪走,以及大量 QA。他自己确实还在逐行审 Redis 的 AI 代码,但明说这”多半没有意义”,坚持做是出于对用户的尊重:Redis 是公共基础设施,很多人会打开文件手改。
他还有一句很扎的话:那个内存优化 PR,前沿模型的 review 会比他自己的 review 发现多得多的错误和微妙竞态。 这句话不是猜想,我们的评审服务每天都在验证它——两分钟轮询一次提交,多趟 AI 评审,结论直接推给提交人。
《代码整洁之道》作者 Robert C. Martin 说得更直接:”我目前的策略是完全不读 agent 写的任何代码。只有这样,我才能真正利用它们带来的生产力提升。” 但下半句才是重点——他在 agent 周围架了一圈极严的约束:单元测试、Gherkin 验收测试、QA 流程、质量指标、变异测试、覆盖率,代码必须跑完这一整套闸门,他才对它有信心。请注意清单里的变异测试:这篇文章第三部分推导了一个季度才落定的主指标,就是它。
而他后来补的一条推文划出了更精细的边界:agent 写的单元测试他不看;agent 写的验收测试和 QA 流程他亲自看,关键功能全审、普通功能抽查,此外定期做一次最终的人工测试。换成这篇文章的说法:他放掉的是实现,握住的是判据。
反方的质疑同样有力,而且正好戳在这篇文章花两个部分去解决的地方:AI 遵循指令并不稳定,凭什么相信它一直待在护栏里面?如果它擅自改测试来迁就自己的实现,或者制造出”测试通过”的假象,人怎么察觉? 还有更根本的一条——测试只能证明程序满足了被写进测试的那些条件,它证明不了这些条件本身完整、正确。
所以我的看法是:问题已经不是”要不要 review”,而是哪些必须人看、看到什么程度、哪些可以交给机器。这两位的答案,方向和我们一致——关口从人的眼睛挪到机器的闸门。
但我还想补一条他们都没细说的,它比”量太大”更麻烦:有些错误,人肉审查在原理上就发现不了。
举一个我们真实撞上的例子——就是第二部分黑盒事故清单里的第二件,这里完整讲一遍。我们用 CLI 批量生成中文技术文档,41 条任务、37 份产出,其中 36 份损坏,累计 370 处:汉字变成 U+FFFD,而且是一个字的三个字节各自变成一个 U+FFFD。根因在 CLI 拼接工具调用参数时把 UTF-8 多字节字符按字节切断了,不是模型写错字。
这个 bug 对使用者极其隐蔽:文件是合法 UTF-8,正文读着通顺,只是偶尔缺一个字。 不做字节级排查,你只会觉得”模型偶尔手抖”。让人一份份看,看一百遍也看不出来。
同类的还有:测试声称跑通而覆盖率为空;断言被悄悄放松之后测试依然全绿。这些都不是”人不够努力”,是人的感官在这些维度上取不到事实。
所以”机器提供事实”不是为了省人力,是因为有些事实只有机器能取到。
那次事故最后变成了一道门禁:产物入库前扫 U+FFFD,命中直接拒绝,需求文档、知识候选、评审报告一视同仁。人看不见的那类损坏,从此不必再靠人看。
问题二:能信 AI 写的测试吗?
测试验证是最硬的验收——上面那套”不读代码”的策略,整个压在它身上。但这里有个死结:测试代码也是 AI 写的,而 AI 为了跑通测试会悄悄放水——断言改松一点,边界少测一条,测试就绿了。Uncle Bob 说单元测试他不看,前提是他有变异测试和一整套指标在后面兜着;少了那层兜底,”不看单测”就只是把信任凭空交出去。
所以要重点检查的,恰恰是测试。怎么检查?答案分两半,两半都要落成可执行的事实:
- 离线的一半:不问”你写的测试覆盖了多少”,改问”你写的测试实际能发现多少 bug”——往业务代码里埋真实形态的缺陷,看它杀掉几个。这是第三部分。
- 在线的一半:不信 AI 自己跑出来的那份报告,入库前用平台的容器独立重跑一遍,把逐条执行结果摆到审批页上。这是第五部分。
(二)harness 之于 coding agent,犹如 IDE 之于人类程序员
一直有种说法:模型越强,harness 该越薄。前言里说了我为什么认为这是想当然。还有一种对称的说法:harness 越厚,agent 越笨。两句话把三种不同的东西揉成了一个词。第一部分已经拆开讲过,这里再补一个类比。
把 AI 当成人就想明白了。用记事本写代码的人确实存在,也确实厉害,但实际开发大家都用 IDE,还有很多人乐于打造独属于自己的 IDE,因为那是最高效的方式。程序员越强,对趁手工具的要求越高,不是越低。
回顾人类怎么一步步提高编程效率的,就两个方向:
- 改进语言:OOP 给了拆解复杂度的思维范式,类型和 GC 减轻了心智负担。
- 完善工具链:LSP、lint、格式化工具、静态检查、性能分析,每一样都在降错误率。
类比到 AI,答案是一样的:为 AI 量身定制开发工具链。 AI 友好的工具,最好简单到自动触发、即时反馈,像第一部分那个 trace 工具。
如果你守着一个历史悠久的大项目,历史债堆成山,技术栈又换不掉,挑战会大很多——但方向不变。反过来也别以为通用技术栈就能省掉这一层:前言里那个平台就是通用栈,一样得建这些东西。
(三)会话之外必须有一层
工具之外还有一层:会话之外的 check 流水线——规则式的质量门禁,语义式的 checklist。
为什么必须有会话外这一层?因为 AI 的训练是应试式的,为达目标不择手段,它目前还理解不了价值本身。它干得出荒唐事:gpt5.6 被曝跳出沙盒直接抄测试答案;为了让测试通过,直接改测试本身。会话内的约束它有办法绕,会话外的流水线它绕不了。 这种兜底 check 比人类注意力便宜得多,多多益善。
所以人不是要看得更快,是要退到只做判断——把核对交给机器,把绕不过的规则交给流水线。
第五部分:把判据变成流程
本部分要点
- AI 的单次产出不可靠,人的单次注意也不可靠——用多重独立的嵌套环叠加出可靠性。
- 环的接口只许传事实:生产者不是写者,失败必须有名字,事实源唯一且可推导。
- 不信上一环的自报:平台用自己的容器独立重跑,把事实摆到审批页上,判断留给人。
- 资产不是文件,是活的生命周期;人不是环外的验收员,是环里的节点,平台的职责是降低人做判断的成本。
- 该不该自建:可替代的层本来就没自建;自建的是判据、资产模型、环的接线——凡是本体都不通用。
前四部分讲了怎么让 AI 顶得住、怎么量它干得好不好、人退到哪里。最后一部分讲:怎么把”机器提供事实,人做判断”从一句原则,变成每天都在跑的研发体系。
这就是前言那个平台的由来。先声明一句:平台还在建设中,这一节讲的不是建成报告,是建设思路。一个这么重的平台该不该由业务团队自建、会不会被中台轻易替代,是它最容易被质疑的地方——这个问题先挂着,答案放到最后一节。思路的结论可以先给:这个平台承载的不是功能,是环——人、agent、资产编织成的、转速各异的嵌套环。 整个推导从两个不可靠开始。
第一个不可靠:AI 的单次产出。 前言已经说过,出错的绝对数量不由单次准确率决定,由累积量决定。一次会话里写得再好,也不能指望它无瑕。
第二个不可靠:人的单次注意。 这条更不容易承认,但同样是事实。代码不是人写的,大量细节超出人的观测;一次长会话里,人的注意力会疲劳、会变形,判断质量跟着产出量一起往下走。让人在一个环里盯到底,和让 AI 在一个环里写到底,是同一种侥幸心理。
(一)环为什么要嵌套
两个不可靠合在一起,推出的不是”更认真地检查一次”,而是用多重独立检查叠加出可靠性。
我们的做法是把环分成大小两层。一次会话里的”开发加自测”是小环——AI 写,AI 自己跑,人在会话里看。会话结束,大环启动:agent 自动做代码评审,过各类 checklist 和静态检查,跑更全面的测试验证。不能指望在一个小环里让 AI 写出完美无瑕的代码,也不能指望会话里的人盯得住。
大环要成立,有两个设计点。
第一,每个环必须独立。 评审的触发是轮询提交,与会话无关;测试的验证用平台自己的容器重跑,与生产者的自报无关。触发源和被验对象之间没有因果关系,这个环说出来的才是新话,不是把上一环的结论复述一遍。
第二,大环不消灭注意力问题,它拆解注意力问题。 一次长会话要求的是持续的、发散的注意,那恰好人最不擅长;多个环把同一件事拆到多个场景、多个时机触发,每个场景主题单一,需要注意的点集中,注意力效率完全不同。而且同一个问题会被不同角度的环反复提醒——被反复暴露的问题,比被凝视一次的问题更容易被发现和修复。
环里还有一类不占注意力的角色:闸门——确定性的规则和 checklist。它们不消耗人的注意力,只把例外递到人面前,所以多多益善。唯一要守住的是第一部分那条标准:闸门的判据必须是事实。判据是事实,闸门做硬拦截;判据是代理指标,最多做提醒。闸门吃掉常规,人只看例外,环才转得动。
(二)环的接口:只许传事实
环多了,问题就转移到接口上:环和环之间传什么?我们的答案是一条铁律——环的接口只许传事实,不许传声明。 接口处传一次声明,下游所有环都在验一个假东西。
这条铁律落成三个机制。它们都是从测试用例这条环上长出来的——那是第一条转完整圈的环——就以它为例。
机制一:生产者不是写者。 生成侧只提交候选,写仓库的权利全部收归平台:平台是唯一写者,每个受治理的仓一条串行队列——AI 负责快,人负责对。
链路统一成:AI 生成候选 → 人工审查 → 串行入库 → 沉淀为资产。审批不只是通过或拒绝:人可以改内容、挪归属、整条丢弃,入库时平台会把不再被引用的公共代码一并裁掉,不让垃圾进仓。审批是要花时间的,这期间仓库可能已经被别的入库推进了,所以入库不是追加,是合并——能自动合的自动合,合不了的标出来交给人。人只在真冲突时介入,其余交给机器。
机制二:失败必须有名字。 环是跨天、跨容器、跨进程转的,卡住的时候,人要能一眼看到环卡在哪一节。
这条是被一次事故按着头教的。一批用例的入库长期卡在”正在合并”,前后四次恢复都失败。查下去发现三层原因叠在一起:底层仓库对象有”有没有实际改动”这个能力,但生产实现没有把它透传上来,内核探测失败后默认”有改动”,于是去提交一个空 commit;普通错误没有对应的失败状态,网关只记日志、不改状态;重启后的恢复逻辑只会重投”正在合并”的任务,同一个空 commit 循环自己养活自己。
最讽刺的是测试:替身实现直接把那个可选能力实现了,所以测试永远绿,测不到生产漏接线。
修法有三处:把那个能力变成必需方法,漏接就编译不过;没有实际差异就跳过提交直接收口;普通失败、超时、panic 一律进一个可重试的”合并失败”态,停机取消保留原状态,恢复逻辑不再自动重试失败态。状态机由此变成七态:待审、正在合并、已入库、已丢弃、冲突、推送失败、合并失败。最后一态是事故买来的。 一个没有名字的失败,对人来说等于没有事实;对一个环来说,等于这个环消失了——它还在转,只是永远转不到头。
机制三:事实源唯一,而且要能被推导。 环与环之间不能传两份事实,也不能传”它说是这样”。
AI 生成的测试和覆盖率事实怎么关联?我们的选择是按路径规范推导:用例文件路径 ucases/<被测文件路径>/<被测函数名>_unit.lua 就是被测函数归属的事实源。为什么不让 AI 在元数据里声明?因为声明会撒谎,路径不会。这句话要补一个诚实的注脚:现网有两千多条单测的归属字段本来就是空的,我们其实没得选;而代码里至今保留了一条”库里已有值就先用它”的分支,也就是说 AI 真填了,还是会被信。这条路只走了一半,另一半是明确的技术债。
覆盖率事实还有两条判断,都是被自己打脸打出来的:
- 多用例合并按行取并集单测逐条单独跑,每条快照只反映自己覆盖的分支,取任何一条都会低估。
- “没跑过”和”跑了没覆盖”严格区分分母只含实际跑到的函数,两种情况都不显示 0%。
第二条就立在平台曾经把”采集失败”显示成”没有数据”的废墟上。上一篇里我们说错误的覆盖率信号比没有更危险,第三部分那两次是评测系统自己有 bug,这里是同一个教训的第四次——而这次撒谎的是”事实”本身。
(三)环的入口:不信上一环的自报
接口管”传什么”,入口管”信不信”。生成侧的 AI 自己跑过测试,也上传了覆盖率报告。这份结果不能信——它可能声称成功而覆盖率为空。
所以平台用自己的容器池,对待审批的用例独立重跑一轮,逐条执行结果直接摆到审批页上,当审批材料。结果仅供参考、不阻塞审批——机器提供事实,人做判断。
执行容器池的设计也顺着环的思路:池成员不是平台建的,是管理员把自己的开发容器贡献进池子。每个容器若干执行槽位,槽位内严格串行、槽位间并行;执行记录只追加不修改;失败换槽位重试。手动触发的跑在触发者的私有池,平台自动验证跑公共池,互不挤占。
(四)环上的货币:资产要被治理,而且是活的
环和环之间真正在流动的东西,是资产。需求、用例、知识——它们进仓前的形态一律是”候选”,审批是货币的发行纪律:人做过判断才入库,入库才成为资产。不治理的情形我们见过:AI 生成的用例散落各处,没人知道哪些能跑、哪些已经过时;评审意见发完企微就沉了,同样的误报下周再来一遍;知识沉淀不设关卡,错误经验写进正式知识,比没有知识更危险。
人的判断本身也是数据。用例为什么被丢弃、评审为什么被标无价值、知识候选为什么被拒,这些原因和审批结果一起结构化落库——它们是回流给生产端的信号:哪类候选总被丢弃,上游的提示词和 skill 就该改哪里。但回灌要克制:先把人的判断攒成可信的数据,再谈自动回灌。 拿一批没经过校准的标签去改提示词,等于用脏数据训练上游——那就是第三部分那个错误评分器的翻版,只是这次污染的是生产者。
资产不是文件,是活的生命周期——这是”为什么必须是平台而不是仓库”最硬的回答。结合版本周期,一个需求的上下文至少要支撑两个版本:一个上线版本,一个在研版本。需求上线后出 bug 要 patch,而 patch 期的修改模式和开发期完全不同:特别在意最小改动、兼容性、数据修复和可观测。这几条恰好全是前言说的那种”价值观型”约束——AI 交付功能的能力强于交付纪律的能力,所以它们不能写进提示词指望自觉,要做成挂在需求状态机上的判据。仓库存得下文件,存不下一个要活过两个版本、两期纪律完全不同的上下文。资产的活法,就是环的转法。
(五)人是环里的节点,不是环外的验收员
平台奠基时立过一条边界:只做外环,不做云端编码环境,警惕膨胀成 Codespaces。后来我们自己推翻了它,做了 AgentUI——浏览器里的会话工作台,直连容器内的 agent CLI,权限审批、提问、干预都在页面上完成。
推翻的理由比原来的边界更本质:人不是环外面的验收员,人是环里面的节点。 节点要干活——批权限、定稿、审批、随时和任意 agent 对话——平台就得把交互做进来。这也是第二部分那个需求在平台尺度上的重演:workflow-engine 给不了的掌控感,环系统必须给。
人在环里,紧跟着的问题就是:人怎么跟得上环的转速。这件事的本质不是让人读得更多,是降低人做判断的成本——用一切手段,让人能高效、轻松地完成判断。手段没有尽头,目前做的是三个例子。
第一,产物形态要从 Markdown 走向 HTML。 一百行以上的 Markdown 我自己都读不完,更别说让别人读。HTML 能承载表格、SVG 图示、折叠、导航,还能一键分享。Anthropic 的 Claude Code 团队专门写过一篇讲这件事,他们给出的理由和我一样:用 HTML 之后,人重新回到了循环里(feel much more in the loop)。而且他们强调了一个我认为最关键的做法——自定义的 HTML 界面必须以”导出”结尾:copy as JSON、copy as prompt,你在界面上做的判断能一键变回给 AI 的输入。回路因此更紧。我们目前做到的只有一部分:测试报告、覆盖率报告和 trace 语义视图已经是自包含 HTML,人不必读 JSON;需求文档、知识条目、评审意见还都是 Markdown。
第二,万物可标注,标注回灌 AI。 这条平台里还没有,但在 /dev 流程的插件里已经跑通了一个小回路:人在方案上划词批注,批注当场变成结构化输入回给模型。方向是清楚的——人的判断是这套系统唯一的外部输入,机制的职责是让它不流失、可复用。
第三,trace 报告要可视化,而且数字不许模型碰。 trace 语义视图的设计原则值得单独说:LLM 只负责命名、归组、写文字解释;所有数字由确定性脚本从原始证据重算;守恒校验不过,HTML 就退回原始全量视图。把”机器提供事实、模型只做表达”用在可视化上——可视化不是把 JSON 涂漂亮,是在不牺牲真实性的前提下让人一眼吸收。
(六)环要铺满研发周期
到目前为止讲的环,大多围着测试和评审转。但环的目标从来不是某一条链路,是整个研发周期:需求、开发、评审、测试、上线、patch,每个环节都值得有环。环的骨架只有一副——触发、生产、闸门、人审、入库、复用——往哪个环节铺,都是这一副骨架的实例。离我们最近的三个,挂在研发周期最前段:
- 需求预分析:需求单分配后 agent 立刻开工——大致方案、策划案没写明的细节、影响面与工作量评估。这条环最克制的地方是产出形态:
待澄清清单是核心价值,方案只是 AI 的一种自洽——不标注清楚,看的人会把它当推荐。
- bug 预分析:是不是 bug,客户端、DS 还是后台的问题,原因是什么,怎么修。它有一个别的环没有的奢侈品:这个 bug 最终真的会被修,真值免费回流,判对率可以统计、可以校准。
- 评审预分析:人工 review 之前,agent 先自动过一遍,排查常见形态的 bug,把人的注意力留给真正需要判断的地方。
(七)业务团队该不该自建这么重的平台
环铺得越满,那个绕不开的问题就越大:这么重的平台,业务团队有没有必要自建?会不会被中台轻易替代,投入打水漂? 我们的答案分两层。
第一层:可替代的层,本来就没自建。 容器用 AnyDev,CLI 用现成的,需求单从 TAPD 同步。真正自建的是三层中台给不了的东西:判据(变异算子来自我们的评审 checklist,patch 纪律来自我们的版本制度)、资产模型(路径规范、知识模块、需求文档体系)、环的接线(谁在什么时机收到哪个例外)。中台能给壳——算力、会话、通用 CI;给不了本体。这和第三部分是同一个结论的一体两面:题集是本体,判据是本体,资产模型是本体——凡是本体都不通用,凡是通用的都不是本体。 哪天通用平台真把壳做得更好,换掉就是;自建的部分恰好是按”不可替代”筛过的,这不是巧合,是建设原则。
第二层:投入沉在哪,决定打不打水漂。 平台最值钱的产出不是代码,是判据、题集、资产模型、环的设计,和一路攒下的失败史——它们跟着团队走,不跟着平台代码走。前言里删掉的 spec 文档和这里是同一个道理:载体可弃,想清楚的东西省不掉。就算明天整个平台被替代,这些本体一件都不会浪费。
这个平台是研发体系的运行时:人、agent、资产编织成转速各异的嵌套环,人从产出线退到环的节点上只做判断,机器在环的接口处提供可验证的事实,资产在环间流动并增值。
结语
五个部分讲的是五件事,但回头看,每一件里的动作都一样:把某个判断从”听它说”改成”去验它”。不信状态符号,改看超时有没有消息;不信活体成员还在,改认磁盘上的产物;不信 AI 自己跑出来的那份报告,改让平台用自己的容器重跑一遍;不信元数据里的归属声明,改从文件路径推导;不信我自己设计的那套失效原型分类,改看变异体到底被杀死了多少。
一句话:不信声明,信事实;不信自觉,信机制。 它和开头那句”机器提供事实,人做判断”是一体两面——后者说的是分工,前者说的是这个分工凭什么立得住:机器那一半交出来的必须是可验证的事实,不是听起来可信的说法。上一篇说”硬门禁比软约束可信”,这一篇把它贯彻到了底。
还有一条更硬的教训,这一年里它出现了四次:上一篇说错误的覆盖率信号比没有更危险;第三部分那套重平台的判分器因为格式解析出错,把同一份产物判出两个差 98 分的分数;紧接着自研评分器把”放错文件”记成”没找到”;第五部分发现平台把”采集失败”显示成”没有数据”。同一个判断,我们在四个地方各犯了一次。 所以”机器提供事实”不是一句口号,它的难点从来不在”人做判断”这一半,而在”机器给出的那个事实,本身是不是真的”。提供事实的机器,自己也要被验证。
最后回到开头那个平台。它是纯 AI 交付的:三个月,七个服务,三十多万行代码,人一行没写;它也会出事。这两件事不矛盾——AI 的产出能力已经够用,而正因为它产得又多又快,兜底比以前更必要,不是更不必要。 不管什么技术栈,都是这样。
模型每季度都在变强,提示词、编排、评测数字都会随之作废——它们是流。约束型尤其如此:补偿模型当下缺陷的那些 SOP,模型一换代就该删。但另一些东西,模型再换代依然值钱:把问题想清楚的那个过程(连它产出的 spec 文档都能删,它本身省不掉)、从上百次失败会话里提炼的运维纪律、对 runtime 主权的判断、框架层那些不占模型注意力的能力、验收的杠杆、评测题集、治理链上的每一条判据——它们是常。做 AI 工程,就是在湍急的流里,找到并守住那些常的东西。
观千帆而识其流,历万变而守其常,承其重而赴其远。
以上是这一个季度摸出来的东西,不少还在半路上,也还在改。与诸君共勉——有疑问、有不同看法,欢迎评论区随时交流。
本文作者:黄迅, PUBGMobile后台,来源@腾讯技术工程。
原文链接:https://mp.weixin.qq.com/s/tSmMdSSzLgTsAX1JUaZI9Q

