AGENTS.md 不是 README:我给 AI 写的项目宪法

前段时间我在项目里加了一张常量表,起名 opcode_table。晚些时候换了个新会话再打开文件,它已经变成了 kOpcodeRegistry。
我把它改了回来,特意加了行注释说明这个名字的来历,结果过了一阵再看,好家伙,它又变成 OPCODE_TABLE 了!🤯
同一个变量,同一个模型,两个完全不同的改名方向,AI 每次都改得理直气壮。
约定写过,注释加过,全当没看见。这事儿逼得我直接把给 AI 写的那份项目规范整个重写了一遍。
这个系列的第 1 篇讲过:把一款 2003 年上线、早就停运的韩国网游客户端,一个人复刻一遍。
复刻是 AI 帮着干的,所以怎么管它,跟怎么用它,是同一个项目里面的两面。
改名这件事儿,就是「管」这条线上最具有代表性的坑之一。
AI 缺的不是知识
kPascalCase 和 UPPER_SNAKE_CASE 都不是模型瞎编的。前缀 k 加驼峰也好,全大写加下划线也好,都是正经命名习惯,很多代码库里这就是标准。
而我的项目使用的是 snake_case 这种命名方式,主要参考的是 C++ Core Guidelines。
模型当然知道 snake_case 这种代码风格,问题的根本在于它不知道在这个项目里哪个算对。
每个项目几乎都会有一套自己的编码规范:命名用哪种、错误怎么传、哪些底线不能破。
这些东西不是通用知识,是这个项目独有的。模型进来的时候对它一无所知,手里唯一的参照系就是「大多数项目怎么做」。
本质上 AI 是没有智能的,它只能预测下一个最有可能的 token。这也意味着它给出的代码大概率是 copy & paste 别人的代码,再做一个微调。
哪个看起来更通用,或者当时在做的事情的参考代码看起来逻辑更自洽,AI 就会选择它参考代码的命名风格。
下次来个新会话,换个新角度,它又自洽地推出另一个结论。结果就是我们看到的:同一个变量被翻来覆去地改名,方向每次都不一样。
这正说明一件事:模型缺的从来不是知识,而是这个项目的成功标准。 它默认按「大多数项目的最佳实践」办事,而最佳实践,未必符合我们的项目。
关键就一句:听谁的?
光在规范里写「我们使用 snake_case 方式」是不够的,这句话只表达了一种偏好。
使用各种 Agent 的时候,它们可能还会自带规则,比如 ECC(Everything Claude Code)里面就有一条相反的全局规则(使用 kPascalCase 命名)。
两条都摆在台面上,AI 就没有了一致性,那就每次会话自己看心情挑一条执行好了,反正用户也「没说清楚」。
所以我在规范里强制增加了这么一句:
当本文件的约定与工具自带的全局规则冲突时,以本文件为准。
看似简简单单一句,但是规则打架的时候,它就知道该听谁的了。
但光这句还不够。「用 snake_case」在它眼里仍然可以是一种倾向,而不是排他的禁令。所以规范紧接着明写:不要用 kPascalCase,不要用 UPPER_SNAKE_CASE,同时给出例子:叫 opcode_table,不叫 kOpcodeRegistry,也不叫 OPCODE_TABLE。
这样边界才最终锁死:不然 AI 可能会说,没说不让用,那「也可以用别的」。现在就是明确告诉 AI:「别的也不行!」
光写「要做什么」,模型会把你的例外当成疏漏,顺手帮你「修正」回去,它真心以为自己在帮你改错别字(还挺敬业的)。
每立一条新规矩,我们都要说清楚对 AI 的约束,明确告知什么是对的,什么是错的,该做什么不该做什么,约定得越详细,结果越可靠。
AGENTS.md 不是 README
规范越写越长,就绕不开一个问题:这份东西跟 README 有什么区别?
答案很简单:区别是读者。
README 讲的是「这个项目干什么的」,是写给人看的。
规范讲的是「在这个项目里,怎么样才算正确」,是写给那个真正动手写代码的 Agent 看的。
两份文档,两类读者,两种写法,并不完全互通。
很多人用 AI 写代码的第一反应,是往 README 里塞一段「开发指南」,觉得这样规矩交代清楚了。
但介绍和约束是两码事,模型没法从一段介绍性文字里推断出哪些事不能做。README 写得再完整,它也推不出「常量必须用 snake_case,别的不行」。
因为我同时使用很多不同的 Agents,所以我现在有 AGENTS.md 和 CLAUDE.md 两种规范文档。不得不吐槽万恶的 A 社,真的是……(此处省略一万字)
AGENTS.md 是跨工具的通用约定,不止一家编码工具会读它;而子目录里的 CLAUDE.md 只有一行,作用就是指向同级的 AGENTS.md。别问为什么这么恶心,要问你就去问 A 社。
实际的收益就是:换工具不用两眼抓瞎,从头再来,所有的规矩都清清楚楚,明明白白。
今天用 Claude,明天用 Codex,规矩同一份,谁来都一样,换个 Agent 立马干活。谁家好,用谁家,项目不应该被一个 AI 模型或一家 AI 厂商所绑架。
AGENTS 里面真正管用的是哪几类
反向规则比正向规则有效得多。 告诉模型「不许做什么」,比告诉它「应该做什么」管用。最典型的一条:绝不许写死任何用户可见的文字(《永远不要让 AI 写 fallback》讲的那个「职业限制」事件),AI 顺手给查表调用编了一批中文默认值,错得谁都看不出来,在屏幕上活了整整一个月。再比如:重构时不许删那些解释「为什么」的注释,只有真正过时的、或者纯复述代码的才能动。
能查表的,别让它推理。 文件头的版权声明有一份模板,需要填对应的模块名。规范里配了一张「文件路径 → 模块名」的对照表,拿到文件一查表就知道该填什么。推理会错,查表不会——机械的事交给机械的检查。
版本号只在一个地方声明。 各平台的清单都从那个唯一的事实源生成,规范里明写了原因:跟二进制对不上的清单,在有人报错之前是看不见的。 不是很难发现,是你根本不知道它存在。
流程也要写进规范。 多阶段任务允许它自动进入下一阶段,不要每做完一步就停下来问「继续吗」——除非碰到要人拍板的分叉口。模型默认做完一步就汇报、等指令,简单任务没事,多阶段任务里节奏会被拆得稀碎。什么时候能自己往下走,什么时候必须停下来等人,写清楚,两边都省心。
还有一类更隐蔽的墙:模型不记事。新会话一开,它会把仓库里早就存在的功能当成新点子,兴致勃勃地再提议一遍;上周拍板的事,这周见面又是第一天上班。所以规范里多了一类「记忆条款」:进度要落盘,决策要落盘,上次聊到哪儿也要落盘——文件不会失忆,让它替模型记着。
交接提示词(handoff prompt)就是这么长出来的一门手艺。现在每次开新会话,开头固定是这么几样:当前分支和提交的哈希、测试基线(218/218 全绿)、这次只许动什么、不许动什么。模型看不到上一次会话,但看得到这张「交接单」——上工第一分钟就知道自己在哪、什么算完、什么算越界。
规范写了,它也不读
以上是规范里管用的几类。但最近的一次翻车提醒我,前面所有规矩都共享一个前提:AI 真的去看了规范。
有一个场景是混合集群联调:原版客户端连上来,卡在选人界面进不去。正在排查,AI 忽然来了灵感:它怀疑世界服务器也连上了新数据库,于是自作主张改了连接端口。问题是,原版客户端只能连 2000 端口,文档里写得明明白白,它压根没看。
还有一回,还是登录卡住了,AI 又开始在回复里抱怨上下文不够用。事后规矩里又加了一条:「别抱怨上下文,去看日志,跟黄金抓包对比。」
这两次之后规范里多了一类新规矩:动手前先查什么。查文档、查实录、查抓包,全要写明。不写,它就擅作主张,天马行空。宪法写得再完备,也只在被翻开的时候才有效;而「翻开它」这个动作本身,也得写进宪法里。😮💨
总之,**把模型容易自作主张的地方,提前堵死。**它不知道的,告诉它;它容易猜错的,给对照表;它会摇摆的,给优先级。
这套东西是怎么来的?
每一条都不是想出来的,都是掉坑里掉出来的。改名的墙撞了,才有「声明优先级」;「职业限制」藏了一个月,才有「绝不许写死用户可见文字」。规范里每一条反向规则,背后都有一次翻车。
所以这份文件注定写不完,只要项目还在不断进行,新的坑就会不断冒出来,AGENTS.md 也只会越来越长。
前面三篇,一篇一个字:
这一篇是「管」:读完、判完,还得立规矩,毕竟不管它,它连变量名都要替你做主。🐶
带走这几条
- 规范里先写一句「听谁的」。 你的约定跟工具自带的全局规则冲突时以谁为准,明写。不写这句,模型每次会话都会重新猜,方向还不固定。
- 每条规矩配上「不要做什么」。 「用
snake_case」是偏好,「不要用kPascalCase、UPPER_SNAKE_CASE」才是规矩,再给正反例。只写前半句,模型会当成疏漏,帮你「修正」回去。 - 列出项目特有的规矩。 比如第 2 篇那个「错误必须显眼」。普通项目里 fallback 是好习惯,还原度项目里它是灾难。模型缺的不是通识,是知道你这里的例外长什么样。
- 能查表的别推理。 从路径到模块名、到版本号和平台清单,凡是能列成对照表的,通通列出来让 AI 查,别留给它自由发挥的空间。
- README 与规范。 给 AI 的那份规范从「在这个项目里,什么算对」写起,文件名用
AGENTS.md,各家工具都认(除了某 A 字公司)。