AGENTS.md 实战:用声明式配置解决 AI 编程无状态难题
1. 从每次重新解释需求说起无状态模型到底卡在哪如果你最近半年深度用过任何一款 AI 编程助手大概率经历过这种循环早上开了一个新会话把项目结构、命名规范、技术栈、禁止使用的库、测试命令一股脑讲了一遍AI 干得不错下午换个功能再开一个会话又得从头讲一遍。讲到第三天你自己都烦了——明明这些信息是项目的常识为什么每次都要重新喂一遍这个问题的根源在于绝大多数大模型在编程场景下是无状态的。所谓无状态不是说模型没有记忆能力而是说每一次请求对它而言都是独立的它不知道你昨天定了什么规范不知道这个仓库里哪些目录是自动生成的、不能手改也不知道你们团队约定所有接口必须返回统一的错误码结构。它只能看到你这一次塞进上下文窗口里的东西。于是大家自然演化出两种应对方式。第一种是人肉记忆把常用提示词存到笔记软件里每次粘贴。第二种是堆上下文把 README、规范文档、几个关键源文件全部拖进对话。这两种方式在项目小的时候还能凑合一旦项目超过几十个文件、多人协作、规范条目超过二十条就会迅速崩溃——上下文窗口是有限的你塞进去的规范越多留给真正代码的注意力就越少模型反而更容易忘记关键约束。我在一个中型前端项目上做过粗略统计一个功能迭代平均要开 6 到 8 个会话每个会话开头平均花 3 到 5 分钟重新交代背景一天下来光复述项目常识就消耗掉将近半小时。这还只是时间成本更隐蔽的损失是一致性——你每次复述的措辞略有不同AI 的理解就会漂移今天生成的代码用camelCase明天可能就变成snake_case。所以真正要解决的问题不是怎么让 AI 更聪明而是怎么把项目的稳定知识从一次性对话里抽出来变成模型每次都能读到的固定输入。这就是所谓范式转换的起点从每次对话临时喂料转向项目级声明式配置。而承载这个配置的载体目前社区里逐渐收敛到一个很朴素的文件名——AGENTS.md。这篇文章会围绕这条主线展开为什么无状态模型必须靠外部声明来补AGENTS.md这类文件到底该写什么、不该写什么它和传统的 README、代码注释、lint 规则之间是什么关系以及我在实际落地时踩过的那些坑。适合已经用 AI 写过一段时间代码、但总觉得每次都要重新教它的开发者也适合想把 AI 编程规范沉淀到团队里的技术负责人。2. AGENTS.md 不是 README 的翻版它解决的是机器可执行约束很多人第一次听说AGENTS.md第一反应是这不就是给 AI 看的 README 吗。我一开始也这么想结果第一版写出来直接照搬了 README 的结构——项目简介、安装步骤、目录说明、贡献指南。用了两周发现效果很差AI 该犯的错还是犯。后来才想明白README 是写给人看的它的目标是让新人快速理解项目而AGENTS.md是写给模型看的它的目标是让模型在生成代码时不违反约束。两者的读者不同写法就完全不同。2.1 人类文档重理解机器文档重约束README 里写本项目采用函数式风格尽量避免副作用人读了能领会精神写代码时会自觉往这个方向靠。但模型读到这句话它不知道什么叫尽量也不知道边界在哪。它可能在一个明显需要副作用的场景比如写日志里也硬憋成纯函数反而写出别扭的代码。AGENTS.md的正确写法是把这类模糊表述翻译成可判定的规则。比如不要写尽量使用函数式风格要写业务逻辑层禁止直接修改传入参数需要变更时返回新对象。不要写注意代码风格要写所有导出函数必须有 JSDoc 注释参数和返回值都要标注类型。不要写遵循项目约定要写新增 API 路由必须放在src/routes/下文件名用 kebab-case且必须在src/routes/index.ts中注册。区别在于前者是价值观后者是检查项。模型对检查项的执行力远高于价值观。这跟带新人的逻辑是一样的——你跟新人说代码写规范点他一脸茫然你给他一张 checklist他照着做就不会出大错。2.2 一份能落地的 AGENTS.md 应该包含哪几块我前后迭代了五六个版本最后稳定下来的结构大概是这几块。注意这不是模板而是我根据模型最容易在哪犯错倒推出来的第一块是项目定位与技术栈的硬约束。不要写这是一个 React 项目这种废话要写清楚版本和关键依赖。比如使用 React 18 TypeScript 5状态管理统一用 Zustand禁止引入 Redux 或 MobX。为什么要写禁止因为模型在训练数据里见过大量 Redux 代码你不明确禁止它很可能顺手就给你写一个createStore出来。第二块是目录结构与文件归属。明确告诉模型哪写哪。比如组件放src/components/页面放src/pages/工具函数放src/utils/所有网络请求封装在src/api/下禁止在组件里直接调用 fetch。这一块能极大减少文件放错位置的低级错误。第三块是命名与代码风格。变量、函数、文件、CSS 类名分别用什么命名法缩进几个空格是否用分号。这些看似琐碎但恰恰是模型最容易漂移的地方。第四块是禁止事项清单。这是我认为最有价值的一块。把你被 AI 坑过的场景全部列进去禁止使用any类型、禁止在循环里做网络请求、禁止提交console.log、禁止修改generated/目录下的文件。每一条背后都是一次真实的踩坑。第五块是验证方式。告诉模型改完代码后怎么自检跑哪个测试命令、跑哪个 lint、类型检查命令是什么。这样模型在生成代码后可以自己对答案而不是等你发现错误再返工。2.3 为什么是 Markdown 而不是 JSON 或 YAML有人会问既然是给机器读的为什么不干脆用结构化格式比如 JSON schema我的实测结论是Markdown 在约束表达这件事上反而更合适。原因是模型的训练语料里 Markdown 占比极高它对标题层级、列表、代码块的理解非常自然而 JSON 虽然结构清晰但模型在长 JSON 里容易丢字段而且写起来啰嗦维护成本高。更重要的是Markdown 允许你在约束旁边写理由。比如禁止在组件里直接调用 fetch因为我们需要统一处理鉴权、重试和错误提示。模型读到理由后在边缘场景下的判断会更准——它理解了意图而不只是死记规则。这一点是纯结构化配置做不到的。3. 声明式配置的底层逻辑把记忆变成检索理解了AGENTS.md写什么还得理解它为什么有效。这背后其实是一个从模型记忆到外部检索的范式转换值得单独拆开讲。3.1 上下文窗口不是记忆是工作台很多人把上下文窗口理解成模型的记忆这是个误解。更准确的类比是工作台模型每次干活时你把需要的材料摊在台面上它看着台面干活干完这一单台面清空下一单重新摊。台面大小是固定的摊的东西越多每样东西分到的注意力就越少。这就解释了一个反直觉的现象不是塞给模型的上下文越多越好。我做过对比测试同样一个功能给模型塞 8000 字背景和塞 2000 字精炼约束后者的代码质量反而更高。因为 8000 字里大量是无关信息稀释了关键约束的权重。所以AGENTS.md的写法要追求高信息密度每一条都必须是模型真正需要遵守的废话一句都不要。3.2 声明式 vs 命令式为什么说清楚是什么比教它怎么做更稳传统提示词工程偏向命令式你一步步告诉模型先做 A再做 B然后做 C。这种方式在单次任务里有效但很难复用因为任务一变步骤就全废了。声明式配置的思路完全不同你不描述过程只描述状态和约束。这个项目的代码应该长什么样哪些事情绝对不能做改完之后必须满足什么条件。至于怎么达到这个状态交给模型自己规划。这跟数据库领域从存储过程转向声明式查询、前端从命令式 DOM 操作转向声明式 UI是同一种演进逻辑——把怎么做的自由度留给执行方把做成什么样的约束牢牢握在配置方手里。实测下来声明式配置的复用性明显更强。同一份AGENTS.md无论是让它加一个组件、改一个接口、还是修一个 bug都能生效不需要为每种任务单独写提示词。3.3 分层配置项目级、目录级、任务级当项目变大一份根目录的AGENTS.md会变得很长。这时候可以引入分层。我的做法是项目级放在根目录写全局约束技术栈、命名规范、禁止事项、验证命令。目录级放在子目录写局部约束比如src/api/AGENTS.md里写接口层的特殊规范src/components/AGENTS.md里写组件的 props 约定。任务级不落盘在具体对话里临时补充比如这次只改样式不要动逻辑。分层的好处是按需加载。模型处理src/api/下的文件时只需要读项目级加目录级两份配置不用把整个项目的所有约束都塞进上下文。这直接对应了前面说的工作台逻辑——只摊当前需要的材料。提示分层配置的加载顺序要明确通常是项目级在前、目录级在后后者可以覆盖前者。这个顺序最好在项目级配置里写清楚避免模型自己猜。4. 落地实操从零搭一套可复用的声明式配置前面讲的是为什么这一节讲怎么做。我会按我实际搭建的顺序把每一步的操作、参数和判断依据都写清楚你可以直接照着改。4.1 第一步先别写配置先收集踩坑清单新手最容易犯的错是上来就写一份漂亮的配置结果全是空话。正确的顺序是先积累素材。具体做法接下来一周每次 AI 生成的代码让你不满意就记一条——它做错了什么、你希望它怎么做。一周下来你会得到一份二三十条的清单这才是配置的原料。我当时的清单里前几条是这样的原始踩坑记录转化后的约束条目AI 用了any类型禁止使用any未知类型用unknown并做类型收窄AI 把工具函数写进了组件文件工具函数统一放src/utils/组件文件只放组件AI 提交了调试日志提交前必须移除所有console.log用统一 loggerAI 改了自动生成的类型文件src/types/generated/为自动生成禁止手动修改AI 每次错误处理写法都不一样所有异步操作必须用try/catch错误统一走handleError这张表就是配置的雏形。注意每一条都是具体的、可判定的没有一条是注意代码质量这种废话。4.2 第二步把清单组织成有层次的文档有了素材接下来组织成文档。我的结构是总—分—禁—验四段式# 项目 AI 协作规范 ## 技术栈约束 - React 18 TypeScript 5禁止引入其他 UI 框架 - 状态管理用 Zustand禁止 Redux/MobX - 样式用 CSS Modules禁止内联样式和 styled-components ## 目录与文件归属 - 组件src/components/文件名 PascalCase - 页面src/pages/文件名 kebab-case - 工具src/utils/纯函数无副作用 - 接口src/api/所有网络请求必须封装在此 ## 命名与风格 - 变量/函数camelCase - 常量UPPER_SNAKE_CASE - 类型/接口PascalCase接口不加 I 前缀 - 缩进 2 空格必须用分号 ## 禁止事项 - 禁止 any 类型 - 禁止在组件内直接 fetch - 禁止修改 src/types/generated/ - 禁止提交 console.log ## 验证方式 - 类型检查npm run typecheck - 代码检查npm run lint - 单元测试npm run test这份文档不到 60 行但覆盖了我 90% 的踩坑场景。关键在于每条约束都能被机械判定——模型读完就知道自己有没有违反。4.3 第三步接入工作流让配置真正被读到写好文件只是第一步关键是让它每次都被模型读到。不同工具的接入方式不一样但思路一致把AGENTS.md的内容作为系统提示或首条上下文注入。如果你用的是支持自定义系统提示的工具直接把文件内容粘进去。如果工具支持读取项目文件就在每次会话开头让它先读AGENTS.md。我自己的习惯是在会话第一句固定写先读根目录 AGENTS.md 和当前目录的 AGENTS.md再开始任务。这句话我存成了快捷短语一键发送。这里有个细节值得注意不要让模型记住配置而是让它每次读配置。因为模型的记忆是不可靠的而文件是可靠的。每次读一遍成本很低几十行文本但能保证约束始终生效。4.4 第四步用真实任务验证配置是否有效配置写完不能就放着得验证。我的验证方法是设计一组陷阱任务专门测试模型会不会违反约束。比如让它加一个用户列表组件看它会不会把组件放错目录、会不会用any。让它调一个用户信息接口看它会不会在组件里直接 fetch。让它改一下类型定义看它会不会误改generated/目录。如果模型在这些陷阱任务里都守住了约束说明配置有效如果某条约束反复被违反说明这条写得不够明确需要回去改措辞。我第一版的禁止内联样式就被违反了三次后来改成所有样式必须写在同名的.module.css文件里组件内只能通过styles.xxx引用才彻底解决。5. 那些配置里不会写、但一定会踩的坑前面讲的都是正确做法但实际落地时真正消耗时间的往往是那些文档里不会提的坑。这一节我把踩过的几个典型问题完整复盘一遍包括排查过程你可以对照自己的情况避雷。5.1 配置太长导致模型选择性失明我一度把AGENTS.md写到了 200 多行觉得越全越好。结果发现模型开始漏读后面的条目——尤其是禁止事项这种放在文档末尾的部分违反率明显上升。排查过程很简单我把同一份配置拆成两半前半段和后半段分别单独测试发现后半段的遵守率只有前半段的一半左右。原因还是工作台逻辑文档越长靠后的内容在上下文里的相对权重越低。解决办法是把最重要的约束前置并且控制总长度。我现在的做法是项目级配置不超过 80 行超出的部分拆到目录级。另外禁止事项这种高频约束我会在文档开头用一句话先概括再在末尾展开形成首尾呼应。5.2 约束之间互相打架有一次我同时写了两条约束所有函数必须有显式返回类型和简单箭头函数可以省略类型标注。结果模型在遇到简单函数时反复横跳一会儿加类型一会儿不加。这不是模型的错是配置自相矛盾。排查这种问题的方法是做冲突检查把配置里所有涉及同一对象的条目列出来看有没有互斥的。比如命名规范、类型标注、错误处理这几类最容易冲突。发现冲突后要么合并成一条要么明确优先级——默认必须显式标注仅当函数体为单表达式且返回类型可推断时可省略。5.3 模型假装读了配置这是最隐蔽的坑。有时候模型会回复我已阅读 AGENTS.md 并遵守相关规范但生成的代码依然违反约束。这说明它只是声称读了实际没把约束纳入生成过程。应对办法是在任务指令里显式引用配置条目。比如不说帮我加个组件而说帮我加个组件注意遵守 AGENTS.md 里的目录归属和命名规范。显式引用会强制模型去检索对应内容而不是泛泛地知道有这么个文件。实测下来显式引用的遵守率比不引用高出不少。5.4 配置更新后旧会话不生效配置是活的会随项目演进不断更新。但已经开着的会话不会自动重新读取配置导致你在旧会话里改了配置模型还在用旧规则。这个坑我踩过好几次排查时一度以为是模型问题后来才意识到是会话缓存。解决办法很朴素改完配置就开新会话。如果任务必须延续就在当前会话里手动把更新后的配置重新贴一遍。我现在养成的习惯是只要动了AGENTS.md当前会话立刻作废重新开一个。5.5 把个人偏好和项目约束混在一起配置里有些条目其实是个人偏好比如我喜欢用早返回而不是嵌套 if。这类偏好写进项目级配置会污染团队协作——别人不一定认同。我的做法是把配置分成两层项目约束团队共识必须遵守和个人偏好只影响自己的会话。个人偏好我单独存一份只在个人会话里追加不写进仓库里的AGENTS.md。6. 从单文件到工程化Memory 工程的下一步把AGENTS.md用顺之后你会发现它只是Memory 工程的起点。所谓 Memory 工程本质是系统性地管理模型在编程时需要的外部知识——哪些知识该持久化、放在哪、怎么加载、怎么更新。AGENTS.md解决的是项目级静态约束但真实项目里还有更多类型的记忆需要管理。6.1 静态约束之外还有动态上下文AGENTS.md管的是不变的东西技术栈、命名、禁止事项。但项目里还有大量会变的上下文当前正在做的功能、最近的改动、已知的 bug、待办事项。这些信息不适合写进静态配置因为天天在变。我的处理方式是在项目里维护一个CONTEXT.md记录当前迭代目标已知问题最近改动摘要。每次开新会话时除了读AGENTS.md再读一遍CONTEXT.md。这样模型既知道长期约束也知道当前在干什么。两份文件分工明确一份管永远如此一份管此时此刻。6.2 让配置可演进版本化与评审AGENTS.md既然是项目资产就应该像代码一样管理进版本控制、走评审、有变更记录。我们团队的做法是任何人想加一条约束都要在提交信息里写清楚这条约束是为了解决哪个具体问题。没有具体问题支撑的约束一律不加避免配置膨胀成愿望清单。另外配置需要定期清理。有些约束随着依赖升级已经过时了比如某个库已经废弃对应的禁止使用就没必要留着。我一般每个季度过一遍配置删掉失效条目合并重复条目。6.3 从一个文件到一套知识库当项目足够大AGENTS.md会自然演化成一套知识库项目级配置、目录级配置、动态上下文、常见问题库、代码片段库。这时候就需要考虑检索策略——模型该在什么时候读哪一份。我的经验是遵循最小必要原则只加载当前任务真正需要的知识其余的不加载。加载太多反而稀释注意力这一点在前面已经反复验证过。具体实现上可以在任务指令里明确指定要读哪些文件。比如改接口层时指令写读 AGENTS.md、src/api/AGENTS.md、CONTEXT.md 后开始。这种显式指定比让模型自己判断要可靠得多。6.4 一个容易被忽略的点配置的可读性也是给模型看的最后说一个反直觉的经验AGENTS.md的可读性不只影响人也影响模型。一份结构清晰、标题明确、条目简短的配置模型的遵守率明显高于一份密密麻麻的长文。所以写配置时该用列表就用列表该分节就分节该加粗关键约束就加粗。这不是为了好看是为了让模型更容易抓住重点。我在实际使用中最大的体会是AI 编程的质量瓶颈往往不在模型能力而在你给它的输入质量。同一款模型配一份好的声明式配置和每次临时口述需求产出质量能差出一个档次。把项目的稳定知识沉淀下来让模型每次都能读到这件事的投入产出比高得惊人。与其抱怨模型又忘了不如花一个下午把AGENTS.md认真写一遍——这可能是你今年在 AI 编程上最划算的一笔投入。