05 · 喂事实,不喂叮嘱¶
agent 出错时,多数团队的第一反应是往 prompt 里加话:"请注意 XXX""务必先确认 YYY"。dsh 的第一反应是另一个问题:它缺什么事实? 然后把这个事实做成结构化、可靠、防篡改的供给。这一章看两个样本。
样本一:一次真实事故的复盘(postmortem 0003)¶
docs/postmortem/ 下有 4 篇事故复盘,编号和格式与大厂 SRE 事故报告一致:摘要、影响、时间线、根因、护栏。(截至 09-04 仍是 4 篇,原因见第 09 章。)0003 讲的是一次 AI 自己犯的错:
事情经过:一个 Web agent 改了 GUI 源码,但它不知道自己的会话跑在哪个 URL、哪个进程上。于是它先把验收甩回给用户;然后自己起了个裸 Vite 服务,看到 HTTP 200 就宣布成功(实际是白屏——关键的启动清单只有完整宿主才会注入);最后又在另一个端口起了全新的服务器去"验证",而用户原来那个页面早就自动更新好了。用户连续指出三次错误它才反应过来。
他们怎么处理的,三点都反常规:
- 时间线精确到会话日志的事件序号("31865 号事件启动了裸 Vite"),不靠事后回忆重构意图——这依赖产品本身的 append-only 会话日志,一切模型可见的东西都有据可查
- 根因不怪模型笨,怪环境没给信息:当前 GUI 的 URL 和运行模式对模型不可见,它只能猜;HTTP 200 看起来像成功是因为"传输就绪"和"应用就绪"没有区分的信号
- 修复全是系统性的:把 URL 和模式注入 prompt 和环境变量、让裸 Vite 启动模式直接报错拒绝、加真实路径的回归测试。甚至发现第一版回归测试本身是假阳性(超时杀进程恰好满足非零退出断言),也写进了复盘。("模式"这一项其实在 08-11 就被移除了,只保留 URL 注入,本章初稿写作时它已不在源码里;复盘文档里的护栏描述滞后到 08-25 才随一次文档标准重写改掉。postmortem 不冻结历史,只描述现状,但也不是实时的。)
配套还有一份 defensive-patterns.md,33 行,开头一句定性:"每一条都是在这里真实发生过或差点发生的缺陷类别,写成防止复发的规则"。一个 bug 从发生到变成体系免疫有四层去处:postmortem 记单例 → defensive-patterns 归纳成类别 → review skill 要求对照 → 能机械化的沉到 lint。
样本二:change-scope——给 agent 造一个"事实仪表"¶
问题:agent 在 push 前和 review 时需要知道"这次到底改了哪些文件",据此选测试、定 review 重点。但 diff 需要比较基准(base),agent 自己算经常错——新分支还没 push 时 origin/分支名 不存在;堆叠 PR(分支 B 基于未合入的分支 A)时和 master 比会混进 A 的全部改动。基准错 → 范围错 → 漏测。
解法:一个仓库根命令 change-scope --base <ref>,输出一份版本化 JSON 事实报告:
- base、head、merge-base 各自解析成哪个 commit,白纸黑字
- 四份文件清单:已提交 / 已暂存 / 改了没暂存 / 未跟踪——普通 diff 只看第一类,agent 常忘了工作区还有脏改动
防污染的细节(每条都是防"事实不完整导致误判"):
- 禁用 rename 检测——改名显示成"删 a、加 b"两行,啰嗦但一个路径都不漏
- 禁外部 diff 驱动——本机 git 配置不能悄悄让某类改动从报告里消失
- 严格 UTF-8,非法值直接中止——宁可整个命令报错,也不悄悄把乱码替换成占位符(替换后两个不同文件名可能变得一样,报告就撒谎了)
最关键的一刀:命令拒绝自己猜 base。 可以做成"聪明"的(自动查 upstream、问 GitHub PR 目标),他们拒绝了——没有任何自动推断在所有分支拓扑下都对。于是分工明确:
判断 base 是谁 = 判断题,归 agent(skill 指导它先确认分支和 PR 状态); 给定 base 后改了什么 = 事实题,归工具(保证绝对准确,宁失败不失真)。
工具不越界做判断,agent 不亲手算事实。
这一章的通用原则¶
- agent 出错,先问它缺什么事实,再考虑改 prompt
- 事实供给要工具化:结构化输出、防污染、fail loud(宁可报错不输出看似正确但残缺的结果)
- 判断和事实分离:工具只陈述世界,判断留给 agent(并用 skill 指导判断)
- 让一切模型可见的东西可追溯(他们的会话日志把这条做成了产品级不变式:"model-visible ⟺ logged"),事故复盘才可能精确到事件而不是靠回忆
← 上一章 · 返回目录 · 下一章:测试哲学 →