1. 从"失忆"到"记忆":为什么AI编程需要范式转换
如果你最近半年深度用过任何一款AI编程助手,大概率经历过这种崩溃瞬间:昨天刚跟它讲清楚项目里所有API返回值的命名规范,今天新开一个会话,它又给你生成了一堆data、result、response这种毫无辨识度的变量名。你不得不把同样的上下文再喂一遍,喂完之后它记住了,但换个文件、换个模块,它又开始"失忆"。
这不是模型不够聪明,而是无状态调用这个底层机制决定的。每一次对话请求,对模型来说都是一次全新的开始,它不记得你上一轮说过什么,更不记得你三天前在另一个会话里定下的代码风格。我们过去一年习惯的"提示词工程",本质上是在跟这种失忆症做斗争——把规则、约束、示例全部塞进单次请求的上下文里,靠"一次性灌输"来换取当次输出的质量。
但项目是有记忆的。一个真实的中型项目,代码规范、目录结构、依赖版本、构建命令、测试策略、提交信息格式,这些东西是跨会话、跨文件、跨开发者稳定存在的。把它们每次都塞进提示词,既浪费上下文窗口,又容易遗漏,还无法版本化管理。于是就有了Memory工程这个概念——把项目的"长期记忆"从临时的对话上下文里剥离出来,沉淀成一份声明式的、可被AI持续读取的配置文件。
这份配置文件的典型载体,就是AGENTS.md。它不是什么官方标准,而是社区在实践中自发形成的一种约定:在项目根目录放一个Markdown文件,用自然语言声明这个项目的"行为准则",AI编程工具在每次任务开始前自动读取它,从而获得稳定的项目上下文。从"每次重新解释"到"一次声明、持续生效",这就是标题里说的范式转换——从无状态模型走向有状态协作。
这篇文章我会把这件事拆透:为什么提示词工程会撞墙、Memory工程到底解决什么问题、AGENTS.md该怎么写才能被AI真正"读懂"、以及我在实际项目里踩过的那些坑。适合已经在用AI写代码、但总觉得"它不够懂我项目"的开发者,也适合想把AI协作规范沉淀到团队里的技术负责人。
2. 无状态模型的本质困境与提示词工程的天花板
2.1 无状态调用到底意味着什么
要理解范式转换的必要性,得先把"无状态"这件事说清楚。大语言模型的推理接口,本质上是一个纯函数:输入一段token序列,输出另一段token序列。它没有跨请求的内存,没有会话级的变量,没有"上次我们聊到哪了"这种概念。你看到的"连续对话",其实是客户端在每次请求时把历史消息全部重新拼进上下文里,模型本身并不保存任何东西。
这个机制带来一个直接后果:上下文窗口就是AI的全部世界。它能看到的东西,仅限于这一次请求里塞进去的内容。窗口之外的一切,对它来说不存在。主流模型的上下文窗口从早期的4K、8K,涨到现在的128K甚至更长,看起来很大,但一个真实项目的代码库动辄几十万行,加上依赖、配置、文档,根本塞不下。就算塞得下,token成本也会高到离谱,而且长上下文里模型对中间部分的注意力会衰减,这就是业内常说的"lost in the middle"现象。
所以问题的核心不是"窗口够不够大",而是哪些信息应该常驻、哪些应该按需加载。无状态模型没有这个区分能力,它只能被动接受你喂给它的全部内容。提示词工程试图用"精心设计的单次输入"来弥补,但这注定是治标不治本。
2.2 提示词工程的三种典型失效场景
我在实际项目里总结出提示词工程最容易翻车的三类场景,几乎每个重度用户都遇到过。
第一类是规则漂移。你在会话开头写了"所有函数必须用JSDoc注释,参数类型必须标注",聊了二十轮之后,模型开始偷懒,注释越来越简略,最后干脆不写了。这不是模型故意违抗,而是长上下文里早期指令的权重被后续内容稀释了。你越聊越远,最初的约束就越弱。
第二类是跨会话失忆。这个最折磨人。今天调好的规范,明天新开会话全部归零。有人用"把提示词存成模板,每次粘贴"来应对,但模板会越来越长,粘贴成本越来越高,而且模板和项目实际状态容易脱节——你改了目录结构,忘了更新模板,AI就按旧结构生成代码。
第三类是团队不一致。A同学有一套提示词,B同学有另一套,两人用AI生成的代码风格南辕北辙,Code Review时吵得不可开交。提示词是个人资产,没法像代码一样进版本库、走评审、做diff。这是提示词工程最致命的短板:它不可协作、不可审计、不可追溯。
2.3 从"临时灌输"到"持久声明"的思维转变
把上面三个问题摆在一起看,会发现它们指向同一个根因:我们把本该持久化的项目知识,当成了临时的对话输入。这就像每次开会都重新口述一遍公司制度,而不是把制度写进员工手册。
Memory工程的核心思路,就是把这层"项目知识"从对话流里抽出来,变成一份独立于任何单次会话、可版本控制、可被工具自动加载的声明式文件。它不追求"这次对话说得多清楚",而追求"任何时候AI进来都能读到同一份真相"。
这个转变听起来简单,但它改变了AI协作的基本单位:从"一次对话"变成"一个项目"。对话是易失的,项目是持久的。声明式配置让AI的"记忆"有了物理载体,也就有了被管理、被优化、被传承的可能。
3. AGENTS.md 的定位与声明式配置的核心设计
3.1 AGENTS.md 到底是什么,不是什么
先把预期校准好。AGENTS.md不是某个厂商的官方规范,不是能被模型自动执行的代码,也不是什么魔法开关。它就是一个放在项目根目录的Markdown文件,内容是用自然语言写的项目约定。它的作用机制很朴素:AI编程工具在启动任务时,会去读这个文件,把里面的内容作为系统级上下文注入到请求里。
它的价值不在于"格式多标准",而在于约定俗成的可发现性。当社区里越来越多工具默认去根目录找这个文件时,它就成了一种事实标准。你不需要告诉AI"去读某某路径的文件",工具自己会找。这种"零配置发现"是它能流行起来的关键。
需要强调的是,AGENTS.md 是声明式的,不是命令式。你写的是"这个项目是什么样、应该遵守什么",而不是"第一步做什么、第二步做什么"。前者是稳定的项目属性,后者是易变的执行流程。把易变的东西写进声明文件,只会让它迅速过时。
3.2 声明式配置相比提示词模板的三个优势
为什么一定要用文件而不是提示词模板?我拿实际体验对比过,差距很明显。
第一,可版本控制。AGENTS.md 进Git仓库,谁改了什么、为什么改,一目了然。规范演进有了历史记录,新人入职看提交历史就能理解项目约定的来龙去脉。提示词模板做不到这点,它散落在各人的笔记软件里,改没改、改成什么样,没人知道。
第二,可分层加载。声明式文件支持目录层级。根目录放全局约定,子目录放模块特定约定,AI按当前任务涉及的路径逐层加载。这种"就近生效"的机制,让规范既有统一性又有灵活性。提示词模板只能一股脑全塞进去,做不到按需加载。
第三,可被工具消费。因为它是结构化的文本文件,工具可以解析它、校验它、甚至根据它生成代码骨架。它从"给人看的说明"升级成了"给机器读的配置"。这是提示词永远达不到的——提示词只能被人复制粘贴,没法被程序处理。
3.3 一份合格 AGENTS.md 的信息分层
写AGENTS.md最容易犯的错,是把它写成"项目说明书",事无巨细全往里塞。结果文件几千行,AI读起来抓不住重点,反而稀释了关键约束。我的经验是按稳定性分层,只把"跨会话稳定存在"的信息写进去。
最稳定的一层是项目身份:这是什么项目、用什么语言、什么框架、什么包管理器、目标运行环境。这些信息几个月都不会变,必须写。
中间层是代码约定:命名风格、目录组织、错误处理模式、日志规范、注释要求。这些会随项目演进微调,但整体稳定。
最不稳定的是当前任务上下文:正在重构哪个模块、临时禁用了哪条规则。这类信息不该写进AGENTS.md,应该放在对话里说。把易变信息写进声明文件,是导致文件迅速腐化的头号原因。
判断标准很简单:如果这条信息一周后大概率还成立,写进AGENTS.md;如果只对当前这次任务有效,留在对话里。
4. 实操:从零搭建一套可用的 Memory 工程
4.1 目录结构与文件放置策略
先讲放置。最基础的做法是在项目根目录放一个AGENTS.md。但如果项目有多个子模块,每个模块有自己的特殊约定,就该用分层结构。我常用的布局是这样:
project-root/ ├── AGENTS.md # 全局约定:语言、框架、通用规范 ├── src/ │ ├── AGENTS.md # 前端模块约定:组件命名、状态管理 │ └── api/ │ └── AGENTS.md # API层约定:请求封装、错误码 ├── tests/ │ └── AGENTS.md # 测试约定:用例组织、mock策略 └── docs/ └── AGENTS.md # 文档约定:格式、更新时机加载逻辑是从根到叶逐层叠加,越靠近当前文件的约定优先级越高。这样根目录可以写"所有代码用TypeScript严格模式",而src/api/AGENTS.md里可以写"本目录允许使用any处理第三方响应,但必须紧跟类型断言"。层级让规范有了作用域,避免了"一刀切"带来的僵化。
要注意的是,层级不宜过深。超过三层,AI加载时容易遗漏,维护成本也上去了。我的经验是最多三层,再深就该考虑拆仓库了。
4.2 内容编写的四个关键原则
写内容时,我总结了四条原则,每条都是踩坑换来的。
原则一:写约束,不写教程。AGENTS.md 不是新人培训文档。不要写"React是一个用于构建用户界面的库"这种背景知识,直接写"组件一律用函数式,禁止class组件"。AI不需要科普,它需要的是明确的边界。
原则二:给正例,也给反例。光说"用具名导出"不够,最好附上一行正例和一行反例。模型对示例的敏感度远高于抽象描述。比如:
## 导出规范 - 正确:`export function parseConfig() {}` - 错误:`export default function() {}` - 原因:具名导出便于重构和tree-shaking原则三:量化,别用模糊词。"函数不要太长"是废话,"单个函数不超过50行,超过则拆分"才是约束。"注释要清晰"没用,"每个导出函数必须有JSDoc,包含@param和@returns"才可执行。模糊词会让AI自由发挥,量化指标才能收敛输出。
原则四:标注优先级。当多条规则可能冲突时,明确谁优先。比如"性能优先于可读性"或"安全约束不可妥协"。没有优先级,AI遇到冲突时只能随机选,结果不可预测。
4.3 一个可直接抄的模板骨架
下面这份骨架是我在多个项目里迭代出来的,你可以直接拿去改。它覆盖了大多数项目需要的核心信息,又不至于臃肿。
# 项目约定 ## 技术栈 - 语言:TypeScript 5.x,严格模式 - 框架:React 18 + Vite - 包管理:pnpm,禁止使用npm/yarn - 测试:Vitest + Testing Library ## 代码规范 - 组件:函数式组件 + Hooks,禁止class - 命名:组件PascalCase,函数camelCase,常量UPPER_SNAKE - 导出:一律具名导出,禁止default export - 类型:禁止any,未知类型用unknown + 类型守卫 ## 目录约定 - src/components:纯展示组件,不含业务逻辑 - src/features:业务模块,按功能划分 - src/lib:与框架无关的工具函数 ## 提交规范 - 格式:type(scope): subject - type限:feat/fix/refactor/test/docs/chore - subject用中文,不超过50字 ## 禁止事项 - 禁止在组件内直接调用fetch,统一走src/lib/request - 禁止提交console.log,用logger替代 - 禁止修改package.json的engines字段这份骨架大概80行,覆盖了技术栈、规范、目录、提交、禁令五个维度。实测下来,AI读完之后生成的代码风格一致性提升非常明显,尤其是"禁止事项"这一节,能挡掉大量低级错误。
4.4 让配置真正生效的加载机制
写完文件只是第一步,关键是让它被真正加载。不同工具的加载时机不一样,但通用逻辑是:任务开始时读取,任务过程中保持。你需要确认你的工具是否支持自动发现根目录的AGENTS.md,如果不支持,就得在工具配置里显式指定路径。
有个容易忽略的点:加载顺序。如果工具同时支持全局配置和项目配置,要确认项目配置能覆盖全局配置。否则你在全局设了"用npm",项目里写了"用pnpm",结果AI还是按npm来,就白写了。
另外,文件编码统一用UTF-8,换行符统一LF。这些细节看起来琐碎,但跨平台协作时,编码不一致会导致AI读到乱码,规则直接失效。我见过一个团队因为Windows开发者用了CRLF,导致AGENTS.md在CI环境里解析异常,排查了半天。
5. 常见问题与排查技巧实录
5.1 AI不遵守约定怎么办
这是最高频的问题。你写了规则,AI该违反还是违反。排查思路按这个顺序走:
先确认文件是否被加载。最简单的验证方法是在AGENTS.md里写一条极其显眼的规则,比如"所有变量名必须包含前缀x_",然后让AI生成一段代码,看它有没有遵守。如果没遵守,说明文件根本没被读到,问题出在加载环节,不是内容环节。
如果确认加载了但规则被忽略,检查规则是否可执行。"代码要优雅"这种规则AI没法执行,因为它不知道什么叫优雅。改成"函数不超过50行、圈复杂度不超过10"就能落地。
再不行就是规则冲突。检查AGENTS.md内部有没有自相矛盾的条目,或者项目配置和全局配置有没有打架。冲突时AI会随机选一条,表现就是"时灵时不灵"。
5.2 配置文件越来越臃肿怎么治理
AGENTS.md 膨胀是必然趋势,因为大家都想往里加规则。治理的核心是定期做减法。我建议每个月过一遍,问三个问题:这条规则最近三个月触发过吗?这条规则还有效吗?这条规则能不能合并到别的条目里?
具体手段有三个。一是下沉,把模块特定的规则移到子目录的AGENTS.md里,根目录只留全局的。二是外链,把详细的规范文档放到docs/目录,AGENTS.md里只写一句"详细规范见docs/coding-style.md",让AI按需去读。三是归档,把历史规则移到docs/agents-archive.md,主文件保持精简。
我的经验是根目录的AGENTS.md控制在150行以内,超过就该考虑拆分了。太长不仅AI读起来吃力,人维护起来也痛苦。
5.3 多工具、多模型下的兼容性处理
现实情况是,一个团队里有人用工具A,有人用工具B,背后的模型也不一样。AGENTS.md 要尽量做到工具无关。具体做法是:不写任何工具特有的语法或指令,只用通用的Markdown;不假设AI有特定能力,比如不写"请调用你的代码搜索功能",而是写"参考src/lib/request.ts的实现"。
如果某些工具支持额外的配置文件(比如工具特有的.toolrc),可以把工具特定的配置放那里,AGENTS.md只保留跨工具通用的部分。这样换工具时,核心约定不用重写。
还有个技巧是用注释标注适用范围。比如某条规则只对特定模型有效,就写成<!-- 仅适用于长上下文模型 -->。虽然AI不一定读注释,但人维护时能看懂,避免误删。
5.4 排查速查表
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| 规则完全不生效 | 文件未被加载 | 加显眼测试规则验证 |
| 规则时灵时不灵 | 规则冲突或优先级不明 | 检查内部矛盾,标注优先级 |
| 早期规则后期失效 | 上下文稀释 | 精简文件,关键规则前置 |
| 换工具后失效 | 工具特有语法 | 改用通用Markdown |
| 中文乱码 | 编码不一致 | 统一UTF-8 + LF |
| 子目录规则不生效 | 层级过深或未加载 | 控制在三层内,确认加载逻辑 |
这张表是我从实际排查中攒出来的,覆盖了八成以上的问题。遇到新问题先对表,能省不少时间。
6. 我在实际项目里踩过的坑与经验
说几个具体的。第一个坑是把AGENTS.md当成了任务清单。早期我在里面写"本周要完成登录模块重构",结果两周后这条还挂着,AI每次读到都以为这是当前任务,生成代码时总往登录模块上靠。后来我定了个规矩:AGENTS.md里只写永恒为真的东西,任何带时间属性的内容一律不进。
第二个坑是规则写得太抽象。我写过"代码要有良好的错误处理",结果AI的理解是每个函数都包一层try-catch,把简单逻辑搞得无比臃肿。改成"网络请求必须捕获异常并返回Result类型,纯计算函数不强制捕获"之后,输出立刻合理了。抽象规则是AI自由发挥的温床,越具体越可控。
第三个坑是忽略了规则的维护成本。一开始我追求大而全,写了三百多行,结果每次改项目结构都要同步更新,两周后就懒得维护了,文件迅速腐化。现在我坚持"少即是多",根目录只留最核心的三十条,其余全部下沉或外链。维护得动的规范才是好规范。
最后一个体会是关于团队协作。AGENTS.md 最大的价值不是让AI更聪明,而是让团队对"什么是好代码"达成共识。以前这些共识藏在每个人脑子里,现在它变成了一份可以讨论、可以投票、可以迭代的文档。AI只是这份共识的第一个消费者,真正的受益者是团队本身。我建议把AGENTS.md的修改纳入Code Review流程,任何规则变更都要有人评审,这样它才能持续保持质量,而不是沦为一份没人看的摆设。