AGENTS.md 不是人设,而是一部用伤疤写成的宪法
随便问一圈就会发现,大多数人把 agent 的指令文件当成一件戏服。“你是一位资深开发者。”“扮演一位大学历史教授。”人设提示词有它的用处,但在真正用一个 AGENTS.md 生活了几个月之后——改文档、写代码、给真实的翻车善后——我的结论是:人设是这个文件里最没意思的部分。真正有价值的是另一种内容:运行法则。这些规则不是凭空想出来的,而是换来的,每一条都能追溯到某次具体的事故:某件事出了错,且绝不该再犯。人设声明 agent 是什么,宪法约束它能做什么。前者是愿望,后者是判例法。本文讲的是后者——用真实的伤疤来讲述,并且保持对工具的中立。你的工具读的是 AGENTS.md、CLAUDE.md、.cursorrules 还是 SOUL.md,你的网关(gate)写在 opencode.jsonc、settings.json 还是某个 MCP 配置里,机制都一样:Markdown 决定 agent 怎么判断,机器解析的配置约束它能做什么。二者互不替代,而它们的好坏,取决于里面烙进了多少教训。
起因事件:一个把自己克隆了一份的仓库
8 月 17 日,我让 agent 检查两个 GitHub 个人主页是否展示了最新内容,然后说“去更新并推送吧”。agent 发现其中一个主页过期了,就去找那个仓库的本地副本,结果“没找到”——然后很贴心地新建了一个:直接 git clone 到我的工作区目录树里,于是这个受管理的仓库里,嵌套着另一个仓库的克隆。它完成了更新,推送成功,然后像丢烟头一样把那个克隆留在了原地。我四天都没发现。
等我终于看到那个目录时,我是真的不记得自己创建过它——这个惊吓程度刚刚好。
排查过程有个转折:我的 shell 历史里什么都没有,因为 agent 是通过非交互式 shell 执行命令的,这些 shell 从不写入 .bash_history。但 agent 的会话数据库把一切都记下来了:确切的命令、精确到秒的时间戳,甚至连触发它执行的那条我自己的消息都有。肇事者留下了指纹,我只需要知道指纹收在哪个抽屉里。
由此得出的规则,如今是我全局指令里最醒目的一条:绝不要用临时产物污染工作区——一次性克隆要放进临时目录,不能留在受版本管理的仓库里;任务结束前,任何残留都必须清理干净。而真正让我夜里睡不着的,是这一点:这次纯属走运,因为这个代码库足够小,陌生的目录一眼就能看出陌生。可在拥有数千个目录的 monorepo 里,一个嵌套仓库会污染 git status 的输出——而这种输出早已没人细看;它会破坏所有向上查找边界、判断项目根目录的工具,还会一次次地训练你,让你逐渐无视未跟踪文件的警告。规模一大,你根本找不到真凶。在这种场景下,预防不是锦上添花,而是唯一管用的办法。
伤疤二:说谎的文档
我的权限设置在两个地方都有描述:AGENTS.md 里的一段话,以及配置文件里的行内注释。有一次编辑时,一句话溜了进去,声称未匹配的 shell 命令会被“静默拒绝”。事实恰恰相反——在宽松的默认设置下,未匹配的命令会被静默放行。这句错话在文件里待了好几周,宣称的是一种我希望拥有的安全策略,而不是我实际拥有的那套。
教训不是“编辑时要小心”,而是结构性的:同一件事实在两个地方描述,它们迟早会漂移,而且漂移方向总是危险的那一边——自信更多,真相更少。我的解决办法是明确归属:一个文件是面向人的事实来源,另一个引用它,再用一份检查清单确认两者是否一致。更好的做法是去重,让每个事实只有一个归宿。
伤疤三:提示洪水
早先的一份配置里有个兜底规则:所有操作都需要批准。这感觉挺负责任,大概持续了一天。不断的弹窗提示是一种摩擦,而摩擦注定输:我会条件反射地批准那些根本没读的内容,这比压根不问还糟。兜底规则最终被拿掉了,取而代之的是以“允许”为核心的设计——只读操作静默执行,而一份精心挑选的、明确具有破坏性的命令列表(rm、git push、git reset)仍然会弹窗确认。
可持久的核心洞见:安全表演在压力下会被删掉,而它一走,真正的防护也跟着一起没了。能经得起日常工作考验的规则,是那些只在值得打断时才打断的规则。
再说几处伤疤,简略些——
指令文件是拼接起来的:它们是一摞,不是层层覆盖。谁都不覆盖谁;如果全局文件写了 X,项目文件写了 Y,模型两个都能看到,但哪个也不遵守。我是发现八条规则一模一样地躺在两个文件里、然后慢慢各自漂走时才明白这一点的。解法是结构性的:一个事实只归一个文件管,通用规则只放全局文件,各仓库专属的规定只放各自仓库。这样一来,漂移就从"不太可能"变成了"不可能"。
官方文档说指令文件按"首个匹配胜出"来解析,可源码其实把它们全部拼接起来。文档和行为打架时,行为照旧发布,文档事后再道歉——所以在依赖某个"有文档背书"的保证之前,先对着源码或正在跑的系统验证一遍。
符号链接配置(真正的文件放在 git 仓库里,链接到使用位置)能优雅地扛过换机器——直到某个编辑器用"先写临时文件再重命名"的方式保存,悄悄把符号链接换成一个冻结的普通副本。你的单一事实来源还在,完好无损,只是被忽略了。
基础设施类的模式失败时,表面上看像是还在照常运行。这跟伤疤二里那个漂移问题是同一个——两个事实来源,其中一个已经过时——只是披上了基础设施的外衣。git status 里子模块指针显示为已修改,这是漂移,不是损坏;而那种想通过重置子模块来"清理"它的本能冲动,恰恰会毁掉你自己选择容忍的那份本地状态。整洁的信号和错误完全是两回事;一个强迫症般非要整理 status 输出的智能体(或人),总有一天会吃掉别人的劳动成果。
会话级的批准随会话结束而失效,永久规则则应放进版本化的配置里。搞清楚哪个旋钮是临时的、哪个是长久的,就能避开两类事故:你以为你拥有的保护,和你以为你保住了的修复。
宪法比模型活得更久
能留下来的规则都有四个共同点:能追溯到某次具体事故、有明确的负责人、可以验证,而且遵守成本足够低,低到没人在赶工时把它删掉。但这四点只是描述——真正的检验标准是:这些规则管用。
那个克隆仓库的智能体迟早会被换成更好的,而且可能很快。真正会留下来的,是那份指令文件和权限配置——它们是用失败写成的组织记忆,无论下一任模型是谁,都读得懂。