你删除的代码对你的编程 Agent 是不可见的
你周二下午花时间删除了一个已经废弃的工具模块。你清理了导入,运行了类型检查器,看着 CI 变绿,然后合并了 PR。周三上午,一个新的 Agent 会话查看同样的代码,认定代码库“缺失”了一个小型助手函数,于是又把那个废弃的模块写了回来——名字相同,形状相同,只是风格略有不同。昨天批准删除的评审者现在不得不回想为什么要删掉它,找到当时证明其合理性的对话,并再次解释一遍。Agent 并没有出现故障。它只是完全按照其上下文的要求在行事。
这是编码 Agent 的结构性可靠性问题,没有人通过提示词工程(Prompt Engineering)来解决:Agent 的上下文起始于代码库的当前状态,而不是该状态为何如此的历史。你移除的文件没有留下 Agent 可见的痕迹。你迁移掉的依赖项只是 npm 上的另一个包。你刻意删除的不稳定测试(Flaky test)是一个等待被“修复”的覆盖率缺口。缺席——即你做出的决策留下的负空间——是不可见的。
Git diff 并不是理由,况且 Agent 也不会去读它
第一直觉是诉诸 git log。当然,Agent 可以运行 git log -- path/to/dead-module.ts 并找到删除的提交(commit)。但在实践中,这在两个层面上都会失败。
较浅层的失败:大多数 Agent 会话不会主动查阅 Git 历史。它们的默认行为是读取文件的当前状态,并根据该快照编写更改。要求 Agent 在添加工具助手之前扫描过去六个月的历史记录,并不是工具本身会产生的流程——它必须通过提示词来诱导产生。等你记起来要提示它时,助手函数已经写好了。
较深层的失败:即使 Agent 确实查看了历史,Git 也只能告诉它改变了什么,而不能告诉它为什么改变。一条写着“移除未使用的支付幂等层”的提交信息只是关于代码的一个事实,而不是决策的记录。你真正需要知道的信息——该层之所以存在是因为两年前的一次“黑五”事故,当时重复扣费导致 4,200 名客户的生产环境崩溃——曾在 Slack 中讨论过,被记录在事后分析文档中,并在 PR 线程中被提及,但从未进入任何下一个 Agent 会话可以 grep 到的地方。Git 历史是一连串的增量(deltas)。理由是这些增量周围的负空间,而没有人把这些写在 Agent 能找到的地方。
这就是为什么“只需添加一个包含规范的 CLAUDE.md”并不能解决问题。规范文件描述的是正向规则——做什么,遵循哪些模式。它们很少列举那些被考虑过、尝试过、拒绝过或通过手术式手段移除的事项。你那半数以“不”字呈现的工程知识,在代码库中没有规范的归宿。
被重新提议的内容分类学
如果你观察一个团队使用编码 Agent 几周时间,就会发现相同的失败模式在不断重复。它们可以清晰地分为几类,而这些类别之所以重要,是因为每一类的解决方法都不同。
第一种是重新创建的工具函数。有人删除了 humanizeDuration 助手,因为它与你已经标准化的库逻辑重复。一周后,另一个 Agent 会话写了自己的 formatDuration,因为代码库在视觉上“没有这一个”。Agent 对缺口的认知没有错——错在它认为这个缺口是个问题。
第二种是重新添加的依赖项。你六个月前为了原生 Intl 加上 date-fns 而剔除了 moment.js。今天的 Agent 通过查找其训练数据中最流行的包(依然是 moment)来解决一个新的本地化格式化任务。迁移成本现在又回到了你的队列中。
第三种是恢复的测试。Agent 注意到一个没有测试的函数,写了一个测试,并重新引入了某人两个月前删除的完全相同的断言,因为那个断言在测试后来已经更改的实现细节,或者因为该测试的不稳定性掩盖了真实的回归。新测试“通过”了,这让你更难反驳保留它的理由。
第四种是重新合并的服务或模块。你故意分离了两段代码——不同的部署节奏、不同的扩展特性、不同的影响范围——而 Agent 将它们视为可以轻易共享基类的两个文件。重构 PR 减小了 diff 差异,在仪表板上看起来是一次纯粹的胜利。但它也撤销了一个经历过宕机才达成的决策。
第五种更微妙,是复活逻辑中的死分支。Agent 看到一个提前返回的 if 并将其“简化”,移除了一道防御卫语句(guard),而这道语句之所以被添加,是因为恰好有一个客户的数据形状会触发它。它所防止的 Bug 不存在于任何测试中,因为测试需要那个客户的数据。这个卫语句源自一个仅存在于 Linear 工单中的单行修复。
这五种情况都有一个共同的结构:Agent 对存在的内容推理正确,但对刻意移除的内容推理时却像个瞎子。
// (保留源代码中的空行或格式)
## 你真正需要的文件:负向决策记录
正确的思想模型是:代码库编码了正向决策,而你的沟通渠道编码了负向决策。智能体只能访问代码库。因此,如果你希望智能体尊重负向决策,代码库就必须开始承载它们。
架构决策记录(Architecture Decision Records,ADR)是现有的最接近 的惯例,它们很有用但并不完整。大多数 ADR 模板都会详细记录所选方案,而为了完整性,仅将替代方案视作简短的一段话。对于人类读者来说这没问题——人类记得那些否决了替代方案的会议。对于智能体,你需要反过来的权重:替代方案、它们被拒绝的原因,以及明确的“除非列出的约束条件发生变化,否则不要再次提议此项”。包含 `ACCEPTED`(已接受)、`REJECTED`(已否决)、`SUPERSEDED`(已取代)和 `BACKTRACKED`(已回溯)等值的状态字段能完成大部分工作,因为它让智能体能将“我们尝试过并决定反对什么”作为一等查询(first-class query)来检索,而不是从头到尾阅读每一份 ADR。
我所见过的有效的实践模式共享三个属性:
- 它们存在于仓库中,位于已知目录,格式可以在智能体的指令文件中用一行文字告知。`docs/adr/` 或 `docs/decisions/` 下的 Markdown 就足够了;模式(schema)的重要性次于位置的规范性。
- 它们包含结构化的“已否决”(rejected)部分。不是叙述性的散文,而是一个列表,每个被否决的替代方案占一个要点,包括原因以及到期条件(如果有的话,例如“如果我们以后在非 Node 运行时中需要亚秒级格式化,请重新审视”)。智能体对这种模式匹配的可靠性远高于对段落的匹配。
- 它们在智能体的指令文件中被引用。在 `AGENTS.md` 或 `CLAUDE.md` 中写下一行“在添加依赖项或删除守卫(guard)之前,请检查 `docs/decisions/` 中的记录”,就能将文档从被动的文字变成智能体真正会遵循的程序,因为它现在成了其常驻提示词(standing prompt)的一部分。
DECISIONS.md 风格——单一文件、仅限追加、按提交周期分段,并带有明确的 `REJECTED` 和 `BACKTRACKED` 状态——是一种低开销的变体,非常适合个人开发者和小型团队。智能体决策记录(Agent Decision Record,AgDR)扩展通过 Y 型陈述(Y-statement)摘要和智能体元数据将这一理念正式化:谁做的决定、哪个模型、何时决定以及考虑了哪些内容。选择适合你团队对流程仪式感接受度的方案即可。重点在于这种文件确实存在,与代码并存,并将被否决的路径视为内容而非脚注。
## 代码本身的“墓碑”
对于在行级反复出现的失败模式——复活的 `if` 守卫、不断重现的助手函数——仓库级的 ADR 就显得粒度太粗了。智能体不会在修改一个早期返回(early-return)之前扫描整个决策目录。
补充模式是本地“墓碑”(tombstones):在之前删除的代码位置写一段简短的注释,说明删除了什么以及原因,并带有一个可以告知智能体去遵守的稳定标记。像 `// HISTORY: removed humanizeDuration helper 2026-04-03 — use date-fns formatDistance` 这样的注释只需两行,就能永久防止同样的重复创建,因为智能体在原本会提议该助手函数的同一个缓冲区(buffer)中读到了它。
风险在于注释陈旧(comment rot),这是一个现实问题。墓碑应当简洁,如果存在规范的决策记录,则引用它,并在底层约束发生变化时将其删除。堆满墓碑的坟场比没有墓碑更糟,因为它会训练所有人——包括人类和智能体——直接跳过它们。
一个有用的纪律:每个删除非琐碎代码的 PR 都要在 PR 描述中回答这个问题:“什么能阻止别人下周重新添加这段代码?”如果答案是“没有”,那么你就交付了一个退化计时器(regression timer)。
## 将智能体的指令文件视为“不要提议”的清单
最后一种模式是大 多数团队使用不足的。智能体指令文件——`CLAUDE.md`、`AGENTS.md`、`.cursorrules`,无论你选择的工具如何命名——通常充满了正向引导:代码风格、框架惯例、首选库。它们发挥最大杠杆作用的地方在于反面:一份直接映射到上述失败模式的简短负向规则清单。
“不要添加日期库;我们使用 `Intl` 加 `date-fns`。”“不要为 `BillingService` 和 `TaxService` 引入共享基类。”“不要针对 `_internal/` 命名空间编写测试。”每一条都是一行文字,能将反复出现的重新提议转化为智能体在编写代码前就会回答的问题。当它们很具体时,维护成本几乎为零;当它们不再适用时,也能干脆地淘汰。
这种方法比仅使用 ADR 更有效的原因在于加载顺序。指令文件从每个会话开始就在智能体的上下文(context)中;而 ADR 目录只有在智能体想到去查看时才会被参考。在指令文件中设置负向规则,相当于在门口就说“不”,而不是等着智能体走进屋子后方去阅读指示牌。
## 负空间工程是一种习惯,而非工具
这不需要安装任何产品。 核心点在于,这种间隙之所以存在,是因为目前没有任何系统能跨会话传递信息;只要团队能够切实维护,任何模式——无论是 ADR、决策日志、墓碑,还是指令文件中的反向指令——都能奏效。该领域中所有解决方案的失败模式都是一样的:人们停止记录,而智能体又回到了只看得到当前状态的境地。
习惯的转变虽然微小,却是实实在在的。当你删除代码时,你只完成了一半的工作。另一半工作是留下能延续到智能体下次启动的痕迹:决策文件中的一行字、原位置的墓碑,或者指令文件里的 “不要提议” 指令,甚至是三者兼具。将代码的缺失视为你正在向仓库添加的东西,而不是从中移除的东西。智能体明天看到的代码库版本,取决于你今天在负空间中写入的内容。
- https://www.me2resh.com/blog/agent-decision-records
- https://github.com/me2resh/agent-decision-record
- https://github.com/anthropics/claude-code/issues/15222
- https://www.mahdiyusuf.com/why-your-coding-agent-keeps-undoing-your-architecture/
- https://www.augmentcode.com/guides/session-end-spec-update-ai-agents
- https://dev.to/pickuma/why-ai-agents-forget-memory-decay-and-context-contamination-explained-44kd
- https://rickpollick.com/blog/adr-comeback-anchoring-agentic-engineering-teams
- https://github.com/archgate/cli
- https://www.anthropic.com/engineering/claude-code-best-practices
