AI编程中的“失忆”难题:用AGENTS.md为无状态模型构建项目记忆
调试了一个下午最终发现问题出在“它忘了”三个字上。这是最近一次AI辅助重构项目里最让我抓狂的时刻模型在第二轮对话里完整、准确地复述了我第一轮给出的全部需求却在执行到第五步时突然绕过了最开始明确要求锁定的公共接口文件擅自改了签名。项目没有崩溃但所有依赖它的模块全部编译失败。这不是偶发问题。从我把AI辅助编程纳入日常开发流程以来这类“失忆”现象反复出现。背后的根子不在模型的智商而在“无状态”这个底层范式。如今越来越多开发者转向 AGENTS.md 这种声明式配置本质上是在做 Memory 工程——把模型每次都要重新“认识”的项目状态提前沉淀成仓库里的持久声明。这篇文章我就想拆一拆这个范式转换的来龙去脉讲清楚无状态模型为什么有天花板、AGENTS.md 为什么能补上这块短板以及我实际落地时踩过的坑和验证过的方法。适合谁看如果你正在用AI写代码、做多步骤重构或维护多个项目经常觉得“它明明能写怎么就老忘事”这篇应该对你有用。1. 无状态模型的“失忆”困境到底遗忘在哪一步1.1 无状态不是“傻”是每一次对话都像初见这里说的“无状态”指当前主流AI编程工具在每次发起新对话或新任务时模型并不具备对之前项目的持久记忆所有有效信息都靠上下文窗口临时携带。换句话说模型每轮任务都像你招来的一个新同事不认路、不认识老代码也不了解仓库里的隐性约定。它能不能干好活完全取决于你这一轮塞给它的“背景说明书”写得多详细。我一开始以为这只是提示词写得不够好后来才发现更深层的问题模型对上下文内容的利用是极度不均匀的。即便窗口足够大、信息都在里面注意力机制也更偏向开头和结尾部分中间段的约束常常被稀释甚至忽略。这就解释了一个诡异现象我明明在一大段说明文字的中部写了“切勿修改XXX”模型在长任务执行中依然会踩线。它不是没看到而是“看到了但没排上优先级”。所以无状态模型的天花板不在于“能不能记住”而在于“每一轮都从零开始”。只要任务链一长、约束一多中间某个环节必然出现遗忘、改写或自作主张。这个瓶颈靠提示词技巧只能缓解不能根治。1.2 一次跨模块重构里的典型翻车过程为了把问题说清楚我举一个最近的实例。某内部系统改造涉及三个模块我定的方案是第一步调整数据层第二步迁移接口层第三步修改调用方。首轮对话里我把这个顺序强调了三遍模型也准确复述了出来。可当任务执行到第二步时它忽然自作主张把第三步的调用方改动一并做了还顺手往公共DTO里加了一个字段。这个字段只有调用方需要数据层根本不认。结果接口层签名一变调用方代码提前被改整个链路当场卡死。我回翻聊天记录发现第二步里模型还明确答应过“保持DTO不变”第五轮却毫无征兆地违反了承诺。这类问题的共性是约束只存在于对话中的某一两句话里没有被解构成结构化规则也没有被后续任务反复确认。模型在每个时刻会基于当前局部上下文重新推理前面的承诺在后面的推理中权重极低。它不是坏是“忘了自己答应过什么”。1.3 窗口越大为什么问题反而越明显有人会说现在很多工具都支持超长上下文了把整个仓库都塞进去不就行了我做过一个简单的对比测试同一批任务跑两套配置一套只给简短需求描述另一套额外附加完整仓库摘要。结果很有意思——初期带完整摘要的那套确实更稳定但任务链越往后模型开始“选择性遗忘”位于中间段的约束。问题出在约束密度。窗口越大里面装的指令越多指令之间的相对权重就越分散。以前5个约束它都能守住现在放进50个约束它反而不知道该优先听哪个。也就是说问题不再是“装不装得下”而是“如何让重要信息在噪声中保持高优先级”。这让我彻底换了思路与其无限加宽窗口不如改变信息的存储位置和组织方式。让状态待在仓库里、待在一个稳定可复用的声明文件里而不是每次寄希望于模型从长对话中把旧指令捞回来。2. Memory 工程的核心逻辑把“临时交代”变成“仓库声明”2.1 定义 Memory 工程记忆不该存在对话里应该存在仓库里Memory 工程是我在实践里总结出来的一个词意思是围绕模型有限记忆的特性系统地设计“项目记忆的载体”——哪些信息需要被长期记住、用什么格式记录、放在哪个位置、什么时候更新、由谁来维护。它本质上是在给项目打一套持久化记忆补丁。核心原则只有一条信息越稳定越应该往仓库根部靠信息越临时越应该留在会话里。稳定的信息包括项目结构、代码风格、任务边界、禁止事项、常用命令、验证方式。临时的信息包括本轮需求、当前进度、待办清单、临时代码片段。以前我常常把稳定信息当成临时信息在每一轮对话里重复输入Memory 工程要求反过来把稳定信息固化成仓库里的文件一次写入、长期生效。这样做的直接收益是模型的工作方式从“每次盲猜”变成“按谱施工”而且这份谱子可以被 review、被版本管理、被继承不会随着某次会话关闭就消失。2.2 范式转换从“约束在指令里”到“约束在配置里”我理解的范式转换核心是状态所有权的转移。无状态模型时代的协作方式是用户承担全部记忆负担负责把项目背景、路径、规范、需求在每一轮对话里完整复述。模型的“短期记忆”里有什么完全取决于你这一轮喂了什么。一旦上下文被截断或覆盖约束就跟着丢了。声明式配置的范式则完全不同模型在开始工作前主动读取仓库里的一份长期生效文件。这份文件不是“这一轮对话的产物”而是“项目的一部分”它跟代码一起被管理、被评审、被演进。模型的行为从“靠对话理解项目”变成“靠文件了解项目”。打个比方以前你带新人每次都得把公司流程口头讲一遍新人听没听进去不好说现在你把《新人手册》放在工位上他任何时刻都能自己翻。AGENTS.md 就是这个手册。它把“指令”从易失的对话流里剥离出来固化成一个持续有效的“项目状态”。2.3 为什么偏偏是 AGENTS.md 成为了载体市面上提出过不少类似的方案比如项目规范文档、README 约定、全局配置文件。但 AGENTS.md 能在众多方案中跑出来我观察主要有三个原因。一是位置约定足够强。放在仓库根目录、固定文件名任何工具、任何模型只要想了解项目就能按约定找到它不需要额外解释。二是格式对人和模型都友好。Markdown 本身就是半结构化文本人类读起来清晰模型解析时也不需要特殊语法。三是生态推动。越来越多的AI编程工具默认把 AGENTS.md 作为项目上下文的入口它已经事实上成为“项目级标准”。所以说到底AGENTS.md 不是技术上的重大发明更像是“命名约定”的胜利。而真正让它有价值的是围绕它建立起来的 Memory 工程方法论。光有一个文件名解决不了问题你得知道里面该写什么、不该写什么、怎么维护它才能真正成为项目的记忆中枢。3. AGENTS.md 完整实操从空文件到能保住项目底线3.1 写什么不写什么一份 AGENTS.md 的四层结构我目前使用的 AGENTS.md 结构分四层你可以直接抄回去改。第一层是项目定义。用三到五句话讲清楚项目做什么、技术栈是什么、目录结构怎么分布。这个部分是为了让模型快速建立全局认知避免它在错误的目录里找代码。第二层是任务规则。明确列出“必须做”“可以做”“绝不能做”三类清单。注意优先写“绝不能做”因为禁止类规则对行为边界的约束力最强。第三层是工作流与验证。给出标准动作序列先读什么、再改什么、最后怎么验证。第四层是会话与交接策略。告诉模型如何开启新任务如何记录已完成工作避免数据散落在对话里无人回收。举几条实际规则给你看“新增功能时必须同步补充 tests/ 目录下的单元测试”这是必须类“如确需修改公共接口先停手并输出变更说明等待确认”这是禁止类“改完代码先跑测试再跑 lint全部通过后输出 diff 摘要”这是工作流类。3.2 一份可直接抄作业的最小 AGENTS.md 示例这里给一个最小可用版本。真正的生产项目可以在这个基础上扩展但请务必保持“总行数不超过一屏”的原则。# 项目定位 这是一个基于 Python 的后台服务项目技术栈为 FastAPI PostgreSQL 队列任务使用 Redis 存储。项目入口在 app/main.py核心业务在 app/services。 ## 目录地图 - app/services核心业务逻辑改动前必须阅读 - app/models数据模型定义结构变更需同步迁移脚本 - tests/单元测试与集成测试 ## 任务规则优先级由高到低 1. 禁止修改 app/services 下公共函数的签名 2. 新增或修改业务逻辑时需同步补充 tests/ 对应用例 3. 所有数据库结构变更必须同时提供迁移脚本 ## 工作流 1. 先阅读相关模块源码理解现有实现 2. 列出改动计划和影响范围 3. 修改代码并补充测试 4. 运行 pytest 和 ruff check . 5. 输出 diff 摘要与测试结果 ## 会话交接 任务完成后请将最终结论与未完成事项写入 docs/task-log.md我特意把规则按优先级从高到低排列。原因是模型在执行时如果遇到多条规则冲突大概率会优先遵循更靠前的指令。这条经验在实操里非常有用。3.3 版本管理与迭代节奏AGENTS.md 不是写完就算完AGENTS.md 的维护我按“两个触发点”来做。任务开始前如果预判本次工作会引入新约束先把它补进配置任务结束后把对话中发现的隐性规则沉淀进去。举个例子某个任务里模型自己发现这个项目统一用返回值对象而不是抛异常这个观察本身就是一条值得写进 AGENTS.md 的规则。不写进去下一个新会话又得重新摸索一遍。版本控制上我给 AGENTS.md 单独安排提交不用夹杂在功能 commit 里。每次变更的 commit message 以AGENTS.md:开头比如AGENTS.md: 增加公共接口不可修改规则。这样后续追溯行为变化时能快速查清是哪条规则发生了变化。我把这个机制叫“规则变更留痕”成本极低收益却很大。4. 从 AGENTS.md 到一整套 Memory 体系4.1 分层记忆不是所有信息都写进根目录文件AGENTS.md 是项目级长期记忆但它不该是唯一的记忆载体。我把项目记忆分成三层对应不同的信息颗粒度和更新频率层级载体适用信息更新频率L0 会话级当前对话上下文本轮需求、进度、临时踩坑点每轮L1 项目级AGENTS.md项目结构、规则边界、工作流每次规则变化L2 文档级模块README、docs/decisions/模块细节、关键决策、复杂业务说明随任务迭代分层的好处是顶层文件保持精简细粒度知识放在对应位置模型需要时按路径主动去读而不是一股脑塞进顶层文件。很多团队失败在把 AGENTS.md 写成了百科全书后果是模型吸收不了文件反而失效。4.2 持久化策略让“决策”像代码一样可追溯我在项目里建了一个docs/decisions/目录每个关键决策用一个独立文件记录命名带日期比如2024-11-15-input-validation-strategy.md。文件内容固定为四段背景、结论、影响范围、关联的 AGENTS.md 规则编号。每次新任务开始时我用一个简单的 shell 脚本扫描这个目录把最近五条决策摘要追加到 AGENTS.md 的“最近决策”区块。这个做法的好处是让模型在新任务里能引用“决策ID”而不是凭空发明方案。举个例子如果 AGENTS.md 里写了“日期校验统一走 utils/validate.py”模型下次遇到格式校验相关需求时会主动去读这个工具模块而不是自己重写一套。决策像代码一样可追溯模型的方案选择就更有依据。4.3 团队协作中的 Memory 治理一个人用AIAGENTS.md 怎么折腾都行一旦团队里多个人都在用AI辅助这份文件就变成了一份需要评审的“行为准则”。最常见的三个问题规则互相矛盾、文件膨胀失去焦点、有人往里塞私货。我的建议是给 AGENTS.md 建立“规则指纹”机制。每次变动PR 描述里必须解释两点改这条规则的动机是什么影响哪些任务类型。如果两个 PR 分别引入了相互冲突的规则review 阶段就能发现而不是等到模型行为异常才回头查。这个机制不复杂但能把 AGENTS.md 从个人笔记升级成团队资产。5. 实测里的意外与教训AGENTS.md 也有边界5.1 文件越长遵循率越低我踩过最大的坑是 AGENTS.md 写得过于完备。有一版我把自己半年内所有的经验教训全部写了进去大概两百多行。结果模型连续两次无视文件后段一条“全局禁止使用某库”的规则直到我把这条规则移到最顶部问题才解决。后续测试显示顶层文件超过一屏约80到120行后模型对后段规则的遵循率明显下降。这不是玄学而是长文档里越靠后的内容注意力权重越低。解决方法是顶层只放项目最核心的约束其余细节全部链接到附属文档需要时由模型主动读取。5.2 指令冲突时的优先级要明确实际使用中模型同时接收系统提示、全局配置、项目 AGENTS.md、用户本轮指令四类信息。当它们冲突时模型的行为很难预测。我的项目里显式约定了一套优先级写在 AGENTS.md 最开头优先级指令来源说明1用户本轮明确的指令最高临时覆盖其他约定2AGENTS.md 中的“禁止”类规则保命规则优先于一般配置3项目内其他文档README、docs 下的规范4全局配置默认值兜底适用所有项目设置这套优先级之后模型遇到冲突时的行为稳定了不少。特别是“禁止”类规则优先级仅次于用户直接指令保证底线不被突破。5.3 如何验证 AGENTS.md 是否真的生效写 AGENTS.md 只是第一步验证它是否真的被模型遵守才是关键。我常用两个办法。第一个是“保命规则验收测试”如果 AGENTS.md 里写了“禁止修改公共接口”就在 CI 钩子里加一个检查一旦 diff 中出现目标目录的公共接口文件改动就直接拦截。用工程手段兜底而不是依赖模型自觉。第二个是“对话复述测试”新开一个会话不问任何背景直接把 AGENTS.md 路径丢给模型让它用自己的话复述规则。如果复述漏了某条说明这条规则很可能在真实任务里也会失效需要调整位置或表述。5.4 工具链变化时的重新验证换模型或换工具链时AGENTS.md 的读取权重可能会发生很大变化。前一代严格遵守的规则下一代可能完全无视。所以每次更换核心工具链我都会重跑一遍“复述测试”和“保命任务”测试。这个动作类似回归测试花不了多少时间却能避免“看起来一切正常、实际全线失守”的尴尬。如果你也想把这套思路用起来我不建议刚开始就做完整的 Memory 体系。先把 AGENTS.md 从空文件写到能让模型守住保命的三条规则再用几次真实任务验证它是否真的被遵守最后再考虑加决策记录和分层。等模型的行为稳定下来你会明显感觉自己从“每次重新教一个新人”变成“带一个越来越靠谱的老兵”。我现在的固定习惯是任何项目新开分支之前先扫一眼 AGENTS.md 有没有该补的地方这短短半分钟省掉的是后面一整轮重讲。这也是我坚持把 AGENTS.md 当“第一块砖”来用的最大动力。