别指望一份 CLAUDE.md 管住 agent
看到 agent 干了件你不想要的事,大多数人的第一反应是往 CLAUDE.md 里加一句话。Agent import 了不该 import 的模块,就写"领域层永远不许 import adapter"。Agent 跳过了测试,就写"宣布做完之前永远先跑测试"。
感觉像在解决问题。多数时候并不是,而且它同时错在两个方向上,这正是这一篇要讲的,给 agent 盖一个能维护的仓库 系列的第三篇。散文绑不住 agent,绑法跟你以为的不一样;而往里加更多散文,这个动作本身会让 agent 别的事都干得更差。两条都反直觉,我一个一个说。
指令是上下文,不是强制
不舒服的事实,而且直接从做这些工具的人那儿来:一份指令文件是上下文。Anthropic 自己的文档很小心地把 CLAUDE.md 框成"会被加载进模型上下文的记忆",而不是 runtime 会去强制执行的规则。AGENTS.md 一样,Cursor 的 rules 一样。它们是模型会读、会被影响的文本,不是栅栏。系统里没有任何一部分,会物理上拦住 agent 去做那件文件叫它别做的事。
这区别点头容易,内化难,因为文件看着很权威。它用祈使句写,写着"必须""永远不"。但"领域层永远不许 import adapter"这句话躺在一个 markdown 文件里,强制力恰好等于贴在服务器机架上那张"请勿重启"的便利贴。它一直有效,直到某个人、或者某个 agent,在任务进行到一半、上下文压力下、重构已经走了三个工具调用深的时候,有了个无论如何都要那么干的理由。模型是个概率系统,在一堆软偏好里照顾着其中一个软偏好。它同时在抛接的东西越多,软的那些就越稳地被丢掉。
一个 README 写着"core 不许 import adapter",而 CI 里没有任何东西会在 core 真的 import 了 adapter 时失败,那它不是规则,是一个语法很好的愿望。Agent 早晚会让你尝到"把愿望当规则"的后果:它干了那件被禁的事,散文没拦住,你在 review 里才发现,或者更糟,你根本没发现。
修法是把承重的规则变成可执行的。任何真的必须成立的东西,需要一个在它被违反时会失败的检查,而不是一句求你配合的话:
- 一条边界规则,比如"领域层保持纯净,不 import 框架、不碰 IO",变成一条 lint 规则(
no-restricted-imports、no-restricted-globals),或者一个架构测试(ArchUnit、dependency-cruiser,或者一个二十行的脚本,grep 那些被禁的模式,命中就以非零退出)。 - "没有文件超过 300 行"变成 CI 里的巨文件检查,配一个必须写明 owner 和过期时间的 allowlist。
- "别手改生成的代码"变成一个 generated-clean 检查:重新生成,
git diff --quiet,动了就失败。 - "宣布做完之前先跑验证"变成一条 CI 一模一样跑的命令,于是"做完"是一个 exit code,不是一种感觉。
这些都不信任模型,这就是它们的全部价值。一个检查,对散文有多有说服力、agent 有多自信,完全无所谓。它是一堵墙,agent 爱撞几次撞几次,没有任何坏东西能到 main。
这点我是在自己的配置上学会的,不是纸上推的。我全局配置里本来有一句话,明明白白告诉 agent 别再让我 review 它的产出,长文我不审,那是它自己的活。这话没起作用。Agent 一直在回合结尾来一句"要我带你过一遍方案吗",因为"对用户客气"是个很强的先验,而配置文件里一句话是个很弱的。真正解决问题的是一个 hook:一个 Stop hook,检查这一回合,发现 review 讨好的句式就直接拦下来。那句散文在那儿挂了好几周,啥也没干。Hook 第一天就管用了。同一条规则,两种完全不同的强制力,差别全在于有没有一段确定性的代码,卡在 agent 和结果之间。
这就是那个心智模型:散文负责提议,hook 负责裁决。指令文件可以描述意图描述一整天。规则要是真重要,得有模型之外的某个东西,来当那个让它成真的玩意儿。
指令越多,agent 越差
第二个方向,是很多人接受了第一点之后还会漏掉的。
假设你认真对待了"要强制执行"这点,但散文还是留着,想着"顺手也写下来,又没坏处"。有坏处。指令文件每个会话都被加载进上下文,而上下文不是免费的。它是一份固定的预算,模型拿它去注意你给的所有东西。你往一个常驻文件里加的每一句话,都是一句在跟真正的任务、跟文件里别的每一条规则抢注意力的话。
这个失败模式真实存在,各家实验室都记录过,叫上下文污染:过了某个体积,更多指令降低可靠性,而不是提高。一个塞满了每一个历史决策、每一个边角情况、每一句"还有那次记得……"的五千行根指令文件,不会让 agent 更小心。它让 agent 更可能在一片用不上的规则汪洋里,丢掉对这次改动真正重要的那三条。规则攒多了还会互相打架,模型得猜哪条赢,而这正是你本来想消灭的那种猜。
所以那个反射,agent 干错事就加条规则,错得双倍。新规则绑不住(它是散文),还稀释了它周围的规则(它是更多散文)。你花了上下文,买了一份你并没买到的强制力。
解法是把常驻指令文件当成它本来的样子,一条热路径,然后分层。不同的东西该待在离 agent 注意力不同的距离上:
- 全局规则:一小段通用工作习惯。验证纪律、你喜欢怎么 commit。不是仓库架构。
- 仓库
AGENTS.md:架构地图、命令、边界、做完的定义。一张路由图,不是知识倾倒。 - 模块本地文件:本地的例外,跟它管的代码放一起,只在 agent 真的进了那个子目录时才加载。
- Skills:那些长流程、多步 runbook,按需拉进来,而不是永久占着上下文。这就是渐进式披露,是让常驻文本能保持短的那个泄压阀。
- 任务 prompt:当前的目标、范围、验收标准。
顶层文件的那个指导问题,不是"关于这个仓库有什么是真的",而是"每一次、对每个任务,agent 都需要进上下文的是什么"。这个集合,比"真的事情"那个集合小得多。剩下的全往外挪:挪进一个有作用域的文件、一个 skill,或者最好,挪进一个不需要在上下文里、反正都会跑的检查。
少写,多强制
把两半拼起来,你得到一套跟大多数人直觉相反的纪律。
Agent 干错了事,该做的动作几乎从来不是"加一段话",而是两件事之一。要么这条规则是承重的,那你就写一个检查:一个 hook、一条 lint 规则、一个架构测试,然后你反而可以把散文写得更短,因为墙在干那些字一直没干成的活。要么这条规则确实只是建议,那你就把它放进最小、作用域最窄的那个会看到它的地方,全局文件保持精瘦。
两个动作指向同一边:markdown 越来越短,可执行的那一面越来越硬。你不再试着用措辞客气的建议去管一个又快、又死板照字面、又失忆的工人,你开始给它修那些它物理上离不开的轨道。散文回归它真正擅长的事:定位、意图、去哪儿找,不再假装自己是一道它从来当不成的栅栏。
这也是为什么 上一篇 和 下一篇 里那条验证命令这么要紧:它是把所有这些强制力收进一道闸门的地方,agent 必须过,而且没得跟它争。指令文件可以是错的、过期的、被无视的。闸门照跑。你的 AGENTS.md 该告诉 agent 这道闸门存在、怎么跑它,然后让开,因为让仓库保持诚实的,是那道闸门,不是那个文件。
这是 agent 可维护仓库系列四篇里的第三篇。上一篇:默契这东西,agent 是真不懂。下一篇:别让 agent 自己说"我搞定了"。相关:光有聪明的大脑还不够,这里的 hook 和闸门,正是那篇讲的 harness,对准了你自己的仓库。