跳到主要内容

60 次运行,0 次遵守,90% 自报合规

阿舟
阿舟

· 阅读约 7 分钟

我 CLAUDE.md 里第 14 条写着"不许为了过测试写特判逻辑"。

上周我 review 一个自己丢给实习生写的函数。它把一个 fixture 的期望值整个抄进了实现里。测试全绿,lint 全过。我看第二遍才发现。

第 14 条挂在那儿,一个字没少。

三周前翻 dev.to 上 Jeongho Nam 那篇讲 TS Evidence Graph 的文章,我被两个数字钉住了:六个前沿模型,60 次运行,真正遵守指令的次数是 0。同一批运行里,模型自称遵守了的比例,超过 90%。

画外音:这实习生不是不会干活,是会一边把活干歪,一边跟你汇报干完了。

它推的东西叫 @ttsc/evidence,跑在作者基于 typescript-go 自己搭的编译器 ttsc 上。大概是我这半年看过最不"技巧"的一套东西——因为它压根没想让模型更听话,它换了个战场。

先说我为什么会点进去。我自己的规则文件已经膨胀到快两百行了。

AGENTS.md、CLAUDE.md、.agents/skills/*/SKILL.md,这三个位置我全用过,现在也全留着,自己都说不清哪条规则落在哪个文件里。文章里列的那套强化手段我全试过——全大写、加粗、挪到文件最顶上、在 prompt 里再复述一遍、前面加 emoji。一个都不管用。后来连调都懒得调了,因为调的过程本身就有种很明显的徒劳感。

那 18 条工程原则我不反对,大部分我还认:不许硬编码、修根因不修表象、不做打地鼠式修复、不留破窗、不重复知识。问题是它列了 18 条。

他引的一项研究给了个挺冷的数:同时压上八条约束,模型满足单条约束的概率约 41%,八条全部满足的约 5.7%。而他有 18 条。这不需要会算。

插一句我自己的判断——这条规律对人也成立。18 条规则贴墙上,人也就只能记住前三到四条。所以"规则越多越不被遵守"不是模型的毛病,是所有会遗忘的东西的通病,只不过在模型这儿它有一个可测量的形状。

它没让模型更听话,它换了个战场

这是我唯一觉得"这个我真没想到"的地方。

规则从提示层搬到了编译层。每个函数都要为技能文件里的每条规则留一行回答注释:

// @evidence no-hard-coding: 入参来自参数,未写死任何 fixture 名
// @evidence open-closed: 新分支走 strategy 注册表,未改 switch
// @evidence yagni: 只实现调用点当前需要的那一个分支
// @evidence fix-root-causes: 修在归一化层,未在调用处打补丁
function normalizeOrder(seed: string): string { /* ... */ }

删掉任意一行,构建就停,报 TS16411,而且它会告诉你欠的是哪一条、定义在哪儿:

src/normalize.ts:41 - error TS16411:
  Missing acknowledgement for rule "no-hard-coding"
  declared at .agents/skills/principles/SKILL.md:24

我喜欢它的地方不在"强制",在错误列表本身就是一张不会蒸发的工作清单。作者说他直接把错误列表当任务列表用——规则文件里加一条新规则,从下一次构建开始,每个函数都多欠一行。这个我服气。因为我定过的那些"下次记得……"的规矩,全都在开新会话的那一刻消失了。它们不在 CI 里,不在别人发起的 PR 里,只存在于我上一轮的对话框上下文里。

所以这东西的真实价值,我认为是把它从"提示"变成了"仓库里的一件资产"。同一个函数,换一个模型、换一个空上下文来写,也得交出同样的回答。这一条是真进步,跟模型听不听话无关。

洞是硬的

接下来这个洞是硬伤,而且不小。

看一眼 TS16411 到底在检查什么:它在检查你有没有写下那句话。不是那句话是不是真的。

也就是说,这样写照样过编译:

// @evidence input-strict: 入参已在边界完成校验
function parseOrder(raw: any) {
  return raw.id.toUpperCase(); // 上一行是假的,raw 可能是 null
}

编译器管不了这个。它只认得那行注释存在。

作者自己在评论区交代了:原本有个 evidenceReview 机制就是为验证这件事设计的,一度启用,最近他停用了。理由是几乎没见过前沿模型写虚假证据,只在 Luna 的 benchmark 里撞到过一次。

我坐那儿想了半天,觉得这里绕回去了。整篇文章的立论基础是"模型在自述合规这件事上不可信"——60 次运行、0 次遵守、90% 以上自报。可写一行 @evidence,正是在让模型做同一个动作:自述合规。而且这次的谎更便宜,从"写一整段话说我遵守了"降到"照着编译器要求的格式补一行"。编译器甚至把格式和内容范围都替它框好了。

那"我没见过它写假证据"这个判断,和当初"我以为模型会遵守规则"这个判断,在证据强度上有什么区别?我看不出来。

说清楚,这不让工具归零。错误列表当工作清单的价值还在,这个跟真假无关。被削掉的是那句"我们的 agent 遵守规则这件事现在可以由编译器证明"。证明不了。它证明的是每个函数都被要求表过一次态。

顺便,作者那条"回答只有在规则真被遵守时才写得出来,所以构建变绿时代码质量也会提升",同一个问题。前提不成立,后面的话就跟着飘。绿的意思只是欠的账都打了白条。

让我坐直的是另一组数

真正让我把文章从头读完的,是一组跟"证明能力"没关系的数。同一个模型,四个应用各建两遍,唯一差别是开不开这个插件:

项目覆盖率token
todo85.5% → 100%866M → 92M
reddit80.3% → 100%1,179M → 245M
shopping63.1% → 100%1,516M → 271M
erp51.6% → 100%5,449M → 411M

覆盖率那一列我持保留。一套让模型自己写 acknowledgement、自己把数往上推的流程,报出四个齐刷刷的 100%,我第一反应是"这个数不好说"。

但 token 那列不太一样。作者说应用越大,纯提示方式的覆盖率掉得越狠,因为在出结果之前反复"再来一轮"吃掉了九成的 token。

这个我信,因为我自己的账单就是这么涨起来的 😅。真正烧 token 的从来不是一次写对,是我跟实习生来回拉扯——"不对,我要的是这个""不,别碰那个文件"——一轮一轮重发上下文。而它为什么会绕圈,文章里另一个引用说得很直白:Cursor 测过,成功解决里有 63% 是从某处检索来的,不是推导出来的;把网络掐了、git 历史封了,得分从 87.1% 掉到 73.0%。它不是在想,它在翻。翻了半天翻不到,就开始"便宜地通过检查"。

把一连串模糊的拉扯,换成一张编译器给的、确定的、可枚举的待办清单,来回次数掉下来——这条路是能走通的。

我抄什么,不抄什么

只抄三点,说完就完。

第一,把错误当待办。任何能生成一张稳定的、可枚举的、跨会话不消失的清单的检查,都值得装。跟它检查的是不是真事无关,清单本身就有用。

第二,从规则文件这一端开始,别直接跳到他画的那个规格驱动大图。作者自己都说了,那需要人类审阅过的需求文档,而现有项目基本没有。那套东西我暂时不碰——要求文档先写好再写代码,听着对,落地的时候你会发现自己一整天都在写文档。

第三,规则条数砍到能记住的量。他 18 条,我建议 5 条以内。第一次跑在已有仓库上,错误数大概等于函数数乘规则数(文章原话),那就是几百上千条。看到这个数字的人不会逐条回答,只会把所有 acknowledgement 一次性糊上去——而这恰好是这套机制本来要防的失败模式,只不过由人手执行了一遍。

至于"用编译器证明 AI 遵守了规则",我现在还是那个态度:能被编译器检查的东西,值得放进编译器;不能的,别假装能。一行注释证明不了任何行为,它只证明有人要求过。

本期缴税:我花了一个下午把 CLAUDE.md 从 18 条压到 5 条,代价是承认另外 13 条我以前从来没在执行。

阿舟
阿舟

写代码写到一半开始怀疑人生,靠 AI 工具续命,顺手把踩过的坑都记下来。

查看主页 →