一份 CLAUDE.md 管不住 agent
agent 干了件我不想让它干的事,我以前的第一反应就是往 CLAUDE.md 里加一句话。import 了不该 import 的模块,就加一句「领域层不许 import adapter」;跳过测试直接说做完了,就加一句「宣布做完之前必须先跑测试」。加完感觉问题解决了,还挺踏实的。
只能说,这个动作基本没用,而且它同时错在两个方向上。第一,散文根本绑不住 agent;第二,你往里加的每一句散文,都在让 agent 把别的事干得更差。两条都挺反直觉的,我一条一条讲。这也是给 agent 盖一个能维护的仓库这个系列的第三篇。
指令文件是 context,不是强制
先讲第一条。指令文件本质上就是一段 context。这不是我的解读,Anthropic 自己的文档写得挺清楚的:CLAUDE.md 是「会被加载进模型 context 的记忆」,不是 runtime 会去强制执行的规则。AGENTS.md 一样,Cursor 的 rules 也一样。它们就是一段模型会读、会被影响的文本,仅此而已。整个系统里没有任何一个环节,会物理上拦住 agent 去干那件文件叫它别干的事。
这个区别,点头容易,真内化挺难的,因为指令文件看着很权威。它用祈使句写,满篇「必须」「永远不」。但你冷静想一下,「领域层永远不许 import adapter」这句话躺在一个 md 文件里,谁来执行它?没人执行。它一直「有效」,直到某次任务做到一半、context 已经堆得很满、重构走到三个 tool call 深,agent 有了一个无论如何都要那么干的理由——它就那么干了。模型是个概率系统,你的规则只是它同时照顾的一堆软偏好之一。它手上要抛接的东西越多,软的那些越先掉。
所以一个仓库的 README 里写着「core 不许 import adapter」,CI 里却没有任何东西会在 core 真的 import 了 adapter 的时候挂掉,那这条就不算规则,只是写下来的愿望。这个后果 agent 早晚会让你尝到:它干了被禁的事,散文没拦住,你 review 的时候才发现,或者更糟,你根本没发现。
修法其实很直接:承重的规则,全部变成可执行的检查。任何真的必须成立的东西,都需要一个在它被违反时会失败的东西,而不是一句求 agent 配合的话。具体大概是这几类:
- 边界规则,比如「领域层保持纯净、不 import 框架、不碰 IO」,变成 lint 规则(
no-restricted-imports、no-restricted-globals),或者架构测试(ArchUnit、dependency-cruiser,或者干脆一个二十行的脚本,grep 被禁的 pattern,命中就非零退出)。 - 「单文件不超过 300 行」,变成 CI 里的巨文件检查,配一个 allowlist,allowlist 里每条要写 owner 和过期时间。
- 「别手改生成的代码」,变成 generated-clean 检查:重新生成一遍,
git diff --quiet,有 diff 就挂。 - 「宣布做完之前先跑验证」,变成一条 CI 一模一样跑的命令。做完没做完看 exit code,不看 agent 自己的感觉。
这些检查有个共同点,就是它们不信任模型。不信任模型这一点,恰恰就是它们的价值。agent 有多自信、散文写得有多有说服力,对一个检查来说完全无所谓,它只看这一次违没违反。违反了就挂,坏东西进不了 main,就这么简单。
散文规则拦不住 agent,检查才是墙
这一点我不是从纸上推出来的,是在自己的配置上吃过亏才明白的。我全局配置里本来有一句话,明明白白告诉 agent:别再让我 review 你的产出,长文我不审,那是你自己的活。这句话就是没用。agent 还是一直在回合结尾来一句「要不要我带你过一遍方案」,因为「对用户客气」在训练里是个很强的先验,配置文件里的一句话是个很弱的信号,弱的干不过强的。真正解决问题的是一个 stop hook:检查这一回合的输出,发现 review 讨好句式就直接拦下来,不让这个回合结束。那句散文在配置里挂了好几周,一点用没有;hook 上线第一天就管用了,真的就是第一天。同一条规则,两种完全不同的效果,差别就在有没有一段确定性的代码,卡在 agent 和结果之间。
所以我现在的心智模型很简单:散文只负责把意图讲清楚,真正拍板的是 hook、lint、CI 这类确定性的检查。一条规则要是真重要,就得有一个模型之外的东西来兜底,不然它只是被写下来了而已。
指令堆多了,agent 反而变笨
第二个方向更隐蔽,接受了第一条之后还是很容易栽在这。
就算你把该强制的都做成了检查,散文多半还留着,想着反正写都写了,留着也没坏处。有坏处。指令文件是每个 session 都会被加载进 context 的,而 context 不是免费的——它是一份固定的注意力预算,模型拿这份预算去处理你给它的所有东西。你往常驻文件里加的每一句话,都在跟真正的任务抢注意力,也在跟文件里别的每一条规则抢注意力。
这个现象各家 lab 都记录过,叫 context pollution:过了某个体积,指令越多,可靠性反而越低。一个塞满历史决策、边角 case、「还有上次那个记得别再犯」的五千行指令文件,不会让 agent 更小心,只会让它更容易在一堆这次用不上的规则里,把对这次改动真正要紧的那三条给丢了。规则攒多了还会互相打架,模型得自己猜哪条优先——而你当初写规则,就是不想让它猜。
所以 agent 一干错事就加条规则,这个反射其实两头都亏。新加的那条还是散文,照样绑不住它;而且它又多占了一块 context,把文件里其他的规则也稀释掉了。context 花出去了,该拦的还是没拦住。
解法就是分层。常驻指令文件是一条热路径,反正原则就一条:热路径上只放每次都用得上的东西,其他的按离 agent 注意力的距离往外放。
- 全局规则:一小段通用工作习惯,验证纪律、commit 偏好这类。仓库架构不放这。
- 仓库的
AGENTS.md:架构地图、常用命令、边界、做完的定义。它是一张路由图,不是知识倾倒场。 - 模块本地的文件:局部的例外跟它管的代码放一起,agent 真进了那个子目录才会加载。
- skill:长流程、多步的 runbook,按需拉进来,不常驻。这个就是 progressive disclosure(按需披露),是让常驻文本能一直保持短的那个泄压阀。
- 任务 prompt:这一次的目标、范围、验收标准。
顶层文件该问的问题,不是「关于这个仓库有哪些事是真的」,而是「哪些东西是 agent 每次干活都需要的」。后面这个集合小得多,真的小得多。剩下的全往外挪:挪进有作用域的文件、挪进 skill,或者最好,挪成一个根本不占 context、反正每次都会跑的检查。
规则按离 agent 注意力的距离分层:热路径最小,检查在 context 之外
所以正确的动作是什么
这两条拼起来,得到的做法跟直觉正好反着。
agent 干错了事,正确动作几乎从来不是加一段话,而是二选一。这条规则要是承重的,就写一个检查——hook、lint 规则、架构测试都行——写完之后散文反而可以删短,因为检查在干那些字一直没干成的活。这条规则要是只是个偏好,就把它放进作用域最窄、真正会用到它的那个地方,全局文件保持精瘦。
两个动作其实是一个方向:常驻的 md 越改越短,真正的强制力慢慢都挪到 hook、lint、CI 这些检查里去。agent 这个工人的特点很明确,快、死板、照字面执行、没有跨 session 的记忆。对这种工人,措辞再客气的建议,都不如一条它绕不过去的检查。散文的活就是把意图讲清楚、把东西在哪讲清楚;强制这个活它本来就干不了,也不用硬让它干。
这也是为什么上一篇和下一篇都在反复讲那条验证命令:它是所有强制力最后收口的地方,agent 必须过,而且跟它没得商量。指令文件可以写错、可以过期、可以被无视,验证命令照跑不误。你的 AGENTS.md 只需要告诉 agent 这条命令存在、怎么跑,剩下的交给命令本身。
这是 agent 可维护仓库系列四篇里的第三篇。上一篇:agent 看不见没写下来的约定。下一篇:agent 自己说做完了,不算数。相关:光有聪明的大脑还不够——这里说的 hook 和验证命令,就是那篇讲的 harness 对准你自己仓库的样子。
