原文:Addy Osmani 在 X 上发表的长文。下面先按原文结构整理要点(用我自己的话转述,不是翻译),再写我读时的批注,最后是我和 Claude 对照自己的 Skill、hook 和 AGENTS.md 讨论后得出的判断。这些判断当天就落成了改动,过程见《一次 Skill 迭代的全过程》。

原文在说什么

问题不在明显的错误

作者先声明他很喜欢 agent 工具,写这篇不是劝人少用。他担心的是另一件事:以前,大部分思考和判断发生在”提出问题”和”拿到答案”之间那段时间里,现在这段时间缩短到了几秒。模型越来越好,明显的错误越来越少,大部分输出流畅、基本正确,也就不会让人觉得有必要再看一遍。你可以同时开很多会话,但审视输出的只有你一双眼睛。默认情况下,没有什么会逼你思考。

卸载、顺从、放弃

他把人和 AI 的关系画成一条连续谱:最上端是认知卸载,任务交给 AI,但你仍对结果负责;中间是顺从,AI 的输出成了你的新输入,你不再把它当作待验证的假设;最下端是认知放弃(这个词来自沃顿商学院的 Steven Shaw 和 Gideon Nave),AI 的输出悄悄变成了你自己的想法。

人会沿着这条谱慢慢往下滑,每一步都很小、很合理:上一个回答细读了,下一个就略读,再下一个只看框架,直到有人问起某一行,你解释不清。这就是理解债务,平时完全不可见,代码库看起来没问题,测试也全过,直到要改系统时才发现没人懂它。

作者也承认,对大型代码库一直保持完整的心智模型是不可能的。他引用 Claude Code 团队的做法:需要理解哪一块,就让 Claude 现场解释哪一块。不必时刻掌握全貌,但要清楚自己缺了哪块理解。出错代价低的事尽管交出去;后果要由别人承担的事,推理和判断要留在自己手里。

问自己的问题

作者给了几个他现在会问自己的问题:我是在让它帮我思考,还是在把思考交给它?这个结论在什么条件下会是错的,能不能用测试去查?我听过反方的意见吗(他会让不止一个模型为反方辩护)?当着要维护它的人,我敢为它打包票吗?我不再练习哪些技能了,这是不是有意识的选择?

把验证变成习惯

瓶颈已经从写代码转到了确认结果是对的。给 agent 验证自己工作的手段(端到端测试、属性测试、直接操作应用),它就能在你看之前自己改、自己跑、自己重试。

但验证也会被放弃:”有没有证据支持结论”变成了”检查是不是绿的”。agent 让写测试几乎免费,于是到处是绿色对勾;可如果测试和功能犯了同样的错,测试照样通过。一个绿色对勾只说明一件事,所以要问它证明了什么,再问它没证明什么。他的建议包括:事先定义什么叫”已验证”;完成的标准要是看得见的东西(测试、截图、日志、trace);收到完成通知时先看它跑了什么命令、输出是什么,再看总结;用全新上下文的子 agent 自查,并且固化到每次循环里;最后再问一句”我有没有遗漏什么”。

真正看清生成的内容

一个 600 行的 PR,结构看着合理、变量名合理、测试全过,你批了,可中间某处事务边界里的执行顺序变了,或者某个边缘情况的默认值反了。审查到一定程度就会变成走过场。作者建议像代码不是 AI 写的那样读 diff,再跳出 diff 看 agent 替你做了哪些决定:默认值、什么不在范围内、两个需求冲突时改了哪一个。让它列出自己的决定和假设,再随机挑几行考自己。

他还提出了”编排税”:agent 生成的东西和你愿意署名的东西之间有差距。完成的工作堆积得比你审得快时,再开一个会话也没用,要么加强检查,要么放慢。

输入的质量

人的作用移到了开头和结尾:开头写清楚任务,结尾审输出。以前需求说不清会有人当面追问,agent 往往不会,它会补上你漏掉的部分,交回一份看起来完成的东西,背后的想法依然模糊,这就是作者说的”意图债”。所以动手前要先用自己的话写一份需求简述:给谁用、什么不在范围内、怎样算完成。也要留意输入链:每个 agent 都把上一个的摘要当输入,到第三个时,可能已经没人读过原始材料。没过关的东西,把原因记进共享配置、hook 或测试,别再犯第二次。

“模型以后会比我强”

也许很多问题以后模型都能发现,但总得有人为交付的东西负责。Anthropic 对自家工程师的研究把这叫作”监督悖论”:监督 Claude 需要的编程能力,恰恰可能因为过度使用 AI 而退化。你可以把工作和大部分审核交出去,但责任交不出去。

日常练习

每周脱离 agent 亲手做一点事;每月写下自己已经不再做的事,决定哪些可以接受;遇到不懂的东西去真正弄懂;把核查机器的工作当成正式的工作排进日程。文章结尾的清单里,我最记得的是最后一条:提问前写下你认为会发生什么;agent 做完后,写下它没检查的一件事。

我读时的批注

我在 Notion 里给原文写了五条批注,后来都成了讨论的起点:

  • 在”验证会被放弃”那段旁边:”测试绿了不代表软件功能正确,尤其是 Codex 还会改验收测试,即使功能没完成,它也会改造测试来作弊,说自己完成了任务。”
  • 在”一个绿色对勾只说明一件事”旁边:”当前测试绿了,要找到它没覆盖到的地方。”
  • 在”让 agent 针对不理解的内容向你提问”旁边:”grilling”,这正是我那个 grilling Skill 在做的事。
  • 在”监督悖论”旁边:”很重要,古法编程不能被丢弃。”
  • 在”去真正弄懂”旁边:”做东西的过程中肯定会遇到很多没见过的东西,不能黑盒地拿来就用,得先去了解,不断学习。”

我们的判断

文章说对了、我们原来缺并吸纳了的

文章的说法 我们原来的问题 现在的做法
绿色对勾只说明一件事 测试绿了就报 PASS;改一点就跑一遍全量,把全量通过当成完成的证据 完成报告分三行:Works(改动生效的证据)、No regression(没有回归的证据)、Risk / undo(还可能出什么问题、怎么回滚)。全量通过只能写在 No regression 那一行
事先定义好什么叫”已验证” PASS 只要求”有证据”,没说证据关于什么 PASS 正向定义:动手前写下的验收条件,被关于本次改动、与改动规模相称的证据证明了。”No tests run, because …” 是合规的 PASS 写法
全新上下文的子 agent 审查,固化到每次循环 11 月 3 日为了避免”盲审挑刺”,把结构审查改成实现者收尾自查,结果变成自己批改自己的作业 恢复干净上下文,但只给目标、非目标和验收条件,不给实现理由;每条 FAIL 都要附一个能走到被标代码的输入。Stop hook 每轮自动跑 scanner
说不清检查会漏掉什么,就要真正的审查 审查报告里没有”我没查什么” 报告的 LIMITATIONS 写没覆盖的范围;没复现的问题写 “may”,不写成事实
把没过关的原因记进配置、hook 或测试 已有”闭环”规则,但只能写回 AGENTS.md 同类问题再出现时,先找能拦住它的检查(测试、lint、脚本、hook);机器判定不了才写文字规则;区域级的审查要点写进 REVIEW.md
提问前写下你认为会发生什么 我会写”期望这个功能正确实现”,其实是许愿式编程 见下面的”预测和验收条件的区别”
产出超过审阅能力时,加强检查或放慢 所有改动都是同一个审查力度 审查深度由改动本身决定,不看作者:文档、文案、配置值不跑审查 Skill,diff 就是证据;普通代码按路由表;碰到敏感路径、验收面、schema、迁移或公共接口时两个审查都跑,再由我看证据批准

文章没写到、我们做了的

  1. 谁来看住测试本身? 文章说”事先定义已验证”,可 agent 能改测试,就能连定义一起改掉。我们把测试分成两类:开发测试 agent 可以随意改;验收测试在实现前从需求写出、由我确认,实现期间冻结。哪些路径算验收面,写在仓库的 REVIEW.md 里;Stop hook 发现它们被改就阻断,要求改回,或说明是我要求或批准的。REVIEW.md 本身也冻结,否则删掉一条 glob 就能绕过。
  2. 审查者拿什么上下文? 文章只说”全新上下文”。我们两头都踩过坑:继承了实现者理由的审查者会去证实那些理由;什么都不给的审查者会脱离意图挑刺。所以只给契约(目标、非目标、验收条件),不给理由。
  3. “每次都运行”怎么保证? 靠 hook,不靠 Skill:Skill 里的脚本只有模型想起来才会跑。我们用真实的 Codex 跑了一次,发现未被信任的 hook 在 codex exec 里会被静默跳过,连警告都没有。所以任何检查装好以后,都要用一次真实运行确认它确实跑了。
  4. 从哪里开始比? scanner 原来只比工作区和 HEAD,agent 一 commit,改动就从视野里消失了。现在 SessionStart 时记下会话起点,Stop 时从起点开始比。
  5. 验证本身的成本。 文章没谈全量测试。AI 改一点就跑全量,是因为训练里漏掉回归代价很大、多跑几乎没有惩罚,跑全量还能省掉影响分析。规则改成:跑测试前先说这次要证明什么;迭代时只跑覆盖改动的测试;全量放在合入点(有 CI 就交给 CI),或改到很多测试依赖的共享面时再跑。
  6. 打转信号。 原来的 hook 按编辑次数判断”在打转”,任何测试通过就清零,等于奖励跑测试、惩罚正常的多次编辑。现在换成:同一个测试命令连续失败 3 次,且中间改过代码。
  7. 状态词像强化学习里的奖励。 规则里原来只有惩罚(”不许把未完成说成 PASS“),没写怎样才能得到 PASS,模型就会拿跑全量来买保险。现在每个状态词先写怎样才能得到它;诚实的 INCOMPLETE 加明确的下一步,算好结果。
  8. 审查轮次。 Anthropic 的代码审查文章里,一个 PR 一夜被审了 50 多轮(笔记见《Claude Code 团队怎么做代码审查》)。我们的规则是:一次审查,修完最多一次定向复审;同一条不变式被新反例再次打破时,不再开新一轮,而是写出不变式、系统地搜索。

我们还缺、可以继续吸纳的

  • 校准练习和”不再做的事”清单。 每周脱离 agent 亲手写一点代码,每月写下不再做的事。这是个人习惯,写不进 Skill,得排进自己的日程。
  • 合并前的测验。 让 agent 在变更报告末尾出题,全部答对才合并。还没做,适合高风险改动。
  • 让多个模型为反方辩护。 决策场景已经有 grilling,但还没用在”我有没有遗漏什么”这类收尾检查上。
  • 输入链。 多 agent 流程里,后面的 agent 有没有被要求去读原始材料,我还没检查过自己的 a2a 设计。
  • 不懂的东西先弄懂。 reality-first-engineering 只处理会改变方向的未知,学习本身还得靠自己。

预测和验收条件的区别

文末那句”提问前写下你认为会发生什么”,最容易和需求简述混在一起。两者写给不同的人:

  • 验收条件写给 agent:做完时怎样算对。它是契约,做完要逐条对照。
  • 预测写给自己:动手前我认为会发生什么。它是假设,价值在于事后的偏差。

“期望这个功能正确实现”两样都不是。它是愿望,永远无法被证伪,所以校准不了任何东西。预测要具体到事后能对答案,三行就够:

  1. 会改哪里:例如”只改 export.py,不动数据库 schema”。
  2. 最容易出错的地方:例如”难点在大文件内存,它可能一次性全读进来”。
  3. 做完会看到什么:例如”导出 10 万行在 5 秒内完成,中文不乱码”。

如果它实际改了 7 个文件,还动了 schema,要么我对系统的认识过时了,要么 agent 越界了,两种情况都值得知道。偏差出现的地方,就是该补理解的地方;预测越来越常落空,说明心智模型在退化,这比感觉更准。