这两年我明显感觉到,写 AI 应用的方式正在经历一次非常深刻的转变。以前大家讨论的是怎么调 API、怎么写 Prompt、怎么把大模型的能力塞进自己的业务里;现在更多的讨论开始转向“这个 AI 到底记住了什么”“它怎么知道我们项目的规矩”“为什么换一个队友来维护,AI 的表现就完全不一样”。
说到底,这背后是一个从“无状态模型”到“有状态智能体”的范式转移,而 Memory 工程(记忆工程)和 AGENTS.md 这类声明式配置,正是这次转移的核心抓手。这篇文章我想结合自己实际折腾过的项目,把这个话题拆开聊透:什么是无状态模型的痛点、AGENTS.md 到底在解决什么问题、Memory 工程有哪些落地路径,以及怎么把它们组合起来用。
1. 无状态模型的“健忘症”与大模型协作的断层
先从一个最朴素的问题聊起:为什么 GPT 这类模型明明很聪明,但在真实项目里用起来总有种“不靠谱”的感觉?答案很大程度上就落在“无状态”这三个字上。
1.1 API 调用背后的状态缺失
模型本身确实是无状态的。每一次你调用接口,模型都是“重新开始”的,它不记得上一轮和你说了什么,更不知道你昨天让它分析过哪份文档。所谓“多轮对话”,本质上是你在前端把历史消息一股脑塞回请求里,让模型“假装记得”。一旦上下文窗口被撑满、或者消息在传输中做了裁剪,它就立刻“失忆”。
这种设计在聊天场景下勉强够用,但在复杂项目协作里就会断层。
举个例子。我在某个模拟项目里做过一个代码评审助手,最初的设计非常简单:把每次 diff 传给模型,让它输出审查意见。第一次测试效果很不错,因为那次提交的上下文很干净。但等我们接入真实仓库,问题就来了——模型根本不知道这个项目的命名规范、不知道哪些模块是核心链路、也不知道团队约定俗成的设计原则。明明是同样的代码,它今天的判断和下周的判断完全不一致,甚至会提出和项目架构相悖的重构建议。
我当时的第一个念头是“是不是 Prompt 写得不够好”,于是拼命往系统提示词里塞背景信息。结果上下文越来越长,模型反而被无关信息干扰,关键信息被稀释,回答质量进一步下降。这就是真正的痛点:你需要的不是一个更聪明的模型,而是一个能让模型“按需记忆”“长期稳定”的机制。
1.2 单次推理的确定性需求与长期协作需求之间的冲突
这里隐藏着两层需求,很容易被混为一谈。
第一层是单次推理的确定性。你希望模型在拿到同样输入时,输出至少是稳定可靠的,而不是随温度变化或者因为上下文里多了几句无关的话而跑偏。
第二层是长期协作的连续性。你希望模型像团队里的资深工程师一样,知道项目背景、知道决策脉络、知道约定俗成的做法,并且这些知识能跨会话保留。
这两层需求放在无状态模型框架下几乎是对立的:你想让它稳定,就得固定它能看到的信息;你想让它协作,就得给它足够的背景。过去大家靠“把所有规则写进系统提示词”强行调和,但实际效果就像让一个新人入职时背诵一整本操作手册——它能复述,但并不知道什么时候该用哪条。
因此,真正的解法不是继续堆 Prompt,而是把“背景知识”和“工作记忆”从模型的脑袋里搬到外部,变成一种可以结构化存储、按需读取、随时更新的工程资产。这正是 Memory 工程要回答的问题,也是 AGENTS.md 这类声明式配置能发挥价值的地方。
2. AGENTS.md:给 AI 队友的一份“团队入职手册”
第一次接触 AGENTS.md 的时候,我脑子里冒出来的类比是“给 AI 队友写的 README”。但很快我发现,它比 README 承载的东西要具体得多,也克制得多。
2.1 AGENTS.md 与前代配置方案的分野
早前大家会怎么做?通常是在项目根部放一个 CLAUDE.md 或者自定义的 AI_CONTEXT.md,希望 AI 助手每次开工前先读一遍。思路没问题,但落地时很容易失控:文件越写越长,从技术栈写到部署流程又写到团队考勤,最后模型记住了一堆杂讯,真正关键的命令和约束反而被淹没。
AGENTS.md 的设计哲学是“声明式的、面向任务的最小上下文”。它不打算塞下所有知识,它的作用是告诉 AI:你是谁、这个项目怎么跑起来、有哪些绝对不能碰的约束、遇到问题优先去哪里查。它更像是一张地图,而不是一本百科全书。
我在实际使用中体会到几个很重要、但文档里很少强调的原则:
- 面向新人(包括 AI 新人):它的首要读者是“刚进仓库的智能体”,所以信息必须像入职手册一样清晰,不能默认读者已经具备项目背景。
- 声明式而非命令式:尽量避免“你应该怎么怎么做”的过程描述,多写“本项目使用什么、禁止什么、哪里能找到什么”的状态描述。
- 常驻但不臃肿:放在仓库根目录,让 AI 工具能自动拾取,但整体控制在几百行以内,保证每次读取的成本足够低。
2.2 一份高可用 AGENTS.md 的骨架示例
因为我前后在不同项目里迭代过好几版,这里直接给你一份我调整后觉得比较能打的骨架。它不是模板,而是一个“出发点”,建议你根据自己团队的真实情况裁剪。
md
Project: 某内部知识库问答服务
项目概述
这是一个面向内部员工的知识检索与问答服务,核心链路为: 用户查询 -> 召回候选文档 -> 大模型生成答案 -> 引用来源返回。 本仓库包含前端 Web、后端 API、离线索引构建三部分。
技术栈速览
- 后端:Python、FastAPI、PostgreSQL、pgvector
- 前端:Vue 3、Vite
- 索引:离线 Python 脚本,基于某开源嵌入模型
- 部署:Docker Compose,单机部署
常用命令
- 本地启动后端:
uvicorn app.main:app --reload - 重建向量索引:
python scripts/build_index.py --env local - 运行全部测试:
pytest tests/ -q - 前端开发服务:
npm run dev
关键约定与红线
- 数据库表结构调整必须同时更新
migrations/下的迁移文件。 - 所有涉及用户隐私信息的字段,响应中一律脱敏,禁止把原始邮箱输出到日志。
- 修改召回策略时,不能破坏“必须有引用来源”的前置条件,答案缺少来源即视为失败。
代码导航
app/api/:HTTP 路由层,只做参数校验与响应组装。app/services/:业务逻辑层,问答、召回、过滤等核心逻辑都在这里。app/models/:SQLAlchemy 数据模型。scripts/:离线任务,包括索引构建、数据清洗。
仓库外的参考资源
- 接口约定文档:仓库
docs/api.md - 部署手册:团队内部 Wiki 的“知识库服务部署”页面
- 询问更有经验的同事:直接问 @backend 维护组的同学
你看,这份文件没有一句废话,但它足够让一个 AI 助手在进入仓库后快速形成正确的行为预期。它知道了这个项目是做什么的、怎么启动、哪里有雷区、代码大概怎么组织,这就比“我什么都不知道、靠猜”要强太多。 ### 2.3 为什么“声明式”是更适配 AI 协作的描述方式 这里我想特别展开说一下“声明式”这个关键词。很多人以为它只是“把规则写下来”的另一种说法,其实没那么简单。 在传统编程里,我们习惯用命令式描述过程:先做 A,再做 B,如果满足 C 就执行 D。这种描述对确定性的程序很有效,但对大模型来说并不是最优的输入形态。大模型的推理方式更像“在约束下选择最合理的下一步”,如果你把过程写得太细,它反而容易机械执行,遇到边界情况就不知所措。 声明式配置的核心是定义“状态、约束、偏好、可用资源”,而不是定义“步骤序列”。AGENTS.md 里写的是“数据库表结构变更必须携带迁移文件”“答案必须有来源引用”,而不是“你每次改表前,请先执行迁移命令 X 生成脚本再检查一遍”。前者的抽象层次更高,模型理解之后可以自行规划步骤,灵活性更强,也更能适配不同版本的代码仓库。 我在几个项目里的实测感受是:命令式的文档适合写给人看,声明式的文档更适合写给 AI 看。当你把项目里那些“不成文的规矩”用声明式语言沉淀进容器后,AI 的表现会明显更稳定,因为它不再靠猜测来推断团队的偏好。 ## 3. Memory 工程的四条主流路径:不只有向量数据库这一条路 聊完 AGENTS.md,很多人会自然想到:那这不就是“长期记忆”了吗?它和 Memory 工程里的记忆有什么区别?这里我得说,AGENTS.md 只是记忆工程里“静态声明层”的一部分,真正的 Memory 工程要复杂得多,至少可以分出四条路径。 ### 3.8 路径一:会话级记忆(Context Memory),最直接的短期记忆 最常见、也最容易理解的记忆层就是会话级记忆。它就是把多轮对话的历史(包括用户输入、模型输出、工具调用结果)重新组织后放回上下文窗口。 表面上看这是个“很笨”的方案,但工程上有很多细节。比如: - 怎么对历史消息做摘要压缩?当对话超过窗口长度时,你是直接截断,还是先让模型生成摘要? - 怎么处理工具调用的中间结果?是把完整 JSON 全部保留,还是只留主要字段? - 怎么控制消息的“新鲜度”?太旧的记忆是否应该降权或删除? 我在各种实验里常用的策略是分块管理:最近的若干轮对话完整保留,更早的按摘要压缩,再早的只保留决策结论。这样做的好处是既保证了近期信息的精确性,又不会让窗口被历史细节占满。缺点是摘要本身有信息损失,如果摘要做得不好,模型会遗忘关键约束。 ### 3.9 路径二:检索增强记忆(Retrieval Memory),适合海量知识的承载 检索增强(也就是常说的 RAG)是另一条路。它的核心是把知识文档、过去的问答记录切分成块,用向量化手段做索引,在需要的时候把最相关的内容检索出来放到上下文里。 这条路径非常适合“海量知识但每次只用一小部分”的场景。比如团队内部的知识库、公告、历史决策记录,都可以做成检索记忆。AGENTS.md 解决的是“高频、稳定、必须遵守”的信息,而检索记忆解决的是“低频、量大、按需提取”的信息,两者是互补关系。 不过检索记忆有两个隐藏坑: 第一,切片策略直接影响效果。按固定长度硬切往往会把语义切碎,更好的做法是根据文档结构(标题、段落、列表)做语义切片,必要时做重叠。 第二,检索到的信息“过于精准”会带来误导。如果检索结果和当前问题只是表面相关,实际上指代不同的东西,模型可能会被带着走。所以我在落地时通常会在检索结果里带上来源和置信度,并在 Prompt 里要求模型“如果上下文不足以回答,就明确说不知道”。 ### 3.10 路径三:结构化记忆(Structured Memory),把记忆变成可查询的数据 第三种思路是把记忆结构化,存成数据库里的记录、键值对、知识图谱的节点等等。比如用户的偏好设置、项目的状态标记、执行任务的历史清单,都可以用结构化方式存储。 结构化记忆的优势是**精确、可校验、可部分更新**。你不需要让模型在大段文本里“意会”某个偏好,而是直接给它一张表格:张三的偏好是简洁回复,李四的偏好是附上代码示例。这种记录方式在需要“事实级准确”的场景里非常重要。 但结构化记忆的门槛也很明显——你需要定义清楚数据模型,并且处理存储与更新逻辑。比如用户改了偏好,旧值怎么处理?要不要保留变更历史?并发写入怎么处理?这些都不是模型能自动解决的,需要工程化的设计。 ### 3.11 路径四:声明式记忆(Declarative Memory),让配置文件成为记忆的锚点 回到 AGENTS.md。它本质上就是一种声明式记忆:把团队约定、项目状态、技术选型这些高稳定性信息,用人类可读、机器可解析的方式固化下来。 我觉得它和记忆工程的关系可以从几个层面看: - 它是**元记忆**,告诉 AI 在哪里可以找到更详细的其他记忆(比如代码导航、文档链接)。 - 它是**长期记忆**里更新频率最低、但优先级最高的一层。 - 它还是**记忆的索引**,AI 读完之后才知道要不要去检索、去哪里检索。 在实际项目中,我会把 AGENTS.md 当作“第一屏信息”,让 AI 每次进入项目时优先读取;然后根据它的指引再去加载其他记忆层。这样做的好处是避免一次性把所有记忆都塞进上下文,真正实现“按需加载”。 ## 4. 一次完整的落地实践:从零搭一个带记忆的代码问答助手 理论聊得再多,不如看一次实际落地。这里我以一个内部代码助手为例,讲我怎么把无状态模型改造成一个“知道项目规则的 AI 队友”,以及在这个过程中踩过的坑。 ### 4.1 目标设定与总体架构 我当时的场景是:团队有一个比较庞大的后端仓库,新人经常问“这个接口在哪定义”“这个业务逻辑在哪实现”,而旧人又没时间逐一回答。我的目标是做一个代码问答助手,它必须满足三个要求: - 能回答“某功能在哪个文件、哪个函数里实现”这类问题。 - 回答前要遵守仓库的规范,比如推荐使用已有的工具函数,而不是建议重复造轮子。 - 对不确定的内容,必须坦诚说不知道,不能一本正经地编造代码位置。 整体的架构是经典的四层:`AGENTS.md 声明层` -> `代码索引检索层` -> `会话管理层` -> `生成层`。AGENTS.md 提供全局规则和导航;代码检索层负责把问题映射到具体代码片段;会话管理层负责维护多轮上下文;生成层负责组装答案和引用。 ### 4.2 配置 AGENTS.md 时做了什么关键取舍 写 AGENTS.md 时我第一版写了 400 多行,从环境搭建、依赖列表、常见 bug 到花名册全塞进去了。结果测试下来模型的表现反而变差了——它在回答代码问题时频繁引用 AGENTS.md 里无关的部署信息,甚至会把花名册里某人的名字当成技术名词来解释。 后来我做了三件事: 1. 把 AGENTS.md 砍到 120 行左右,只保留项目概述、技术栈、常用命令、代码导航、红线规则。 2. 把所有详细文档的信息从 AGENTS.md 里移出,改成“去 docs/ 找”“去 Wiki 找”这样的指引。 3. 把红线规则用“否定句”写出来(“禁止…”“不能…”“必须…”),因为模型对明确禁令的遵从度明显高于对正面建议的遵从度。 事实证明,这个取舍非常关键。AGENTS.md 里信息少了,但信噪比高了,模型的行为反而稳定了。这也让我理解了一个反直觉的事实:记忆不是越多越好,而是越精确越好。 ### 4.3 代码检索层的实现细节与召回率优化 代码检索层我一开始直接用了最粗糙的“文件名匹配 + 全文关键词搜索”,上线后效果惨不忍睹。比如用户问“用户登录的逻辑在哪里”,文件名匹配根本找不到,因为登录逻辑分散在 auth 服务、中间件、数据库模型等多个文件里。 后来我改成了“双路召回”: - 第一路是向量检索,对代码文件做切片(按函数、类、注释块切片),生成嵌入向量。 - 第二路是符号检索,用正则和 AST 抽取函数名、类名、路由路径,做精确匹配。 - 最后用简单规则做融合:符号命中的结果优先,向量命中作为补充。 这个方案看起来简单,但效果提升非常明显,尤其对“找定义”“找路由”这类问题,准确率大概从 50% 提到了 80% 以上。我没有上重型的代码知识图谱,因为成本太高,而对当前场景来说双路召回已经完全够用。 ### 4.4 会话管理:记忆更新的量与时机 这一层我吃过不少亏。最初我把每轮问答的完整历史都保存下来,然后全部发给模型。结果对话超过十轮之后,上下文开始打架——模型会纠结于之前的错误回答,而不是根据最新的准确检索结果来修正。 后来我采用了一种“结构化会话记忆”的方案: - 保存用户的核心意图和每轮的最终结论,而不是全部原始对话。 - 每次检索结果都会“刷新”记忆中的相关信息,取代旧的、可能过时的上下文。 - 在生成回答前,先判断本轮是“延续话题”还是“新话题”。新话题会清空大部分会话记忆,只保留用户画像类信息。 这背后的逻辑是:**记忆更新不是简单追加,而是有优先级的替换**。如果旧信息和最新信息冲突,需要让最新信息获胜。这一点尤其在代码场景里重要,因为代码是持续演进的,旧的位置和旧的文件名经常会过时。 ### 4.5 实测效果对比与关键收益 在完成了上述改造后,我用团队里真实的高频问题做了评测。对比对象有两个:一是裸模型(只有系统 Prompt、没有外部记忆),二是“全身都塞”的配置(把所有文档全文塞进上下文)。 结果大致是: | 场景 | 裸模型 | 全量上下文 | AGENTS.md + 检索 + 会话管理 | | --- | --- | --- | --- | | 找路由定义 | 约 20% 准确 | 约 55% 准确 | 约 85% 准确 | | 解释业务逻辑 | 约 30% 准确 | 约 45% 准确 | 约 75% 准确 | | 遵守代码规范 | 经常违反 | 时好时坏 | 大部分时间遵守 | | 响应速度 | 快 | 慢(上下文臃肿) | 快 | | 成本 | 低 | 高 | 中 | 这个结果其实一点也不意外:上下文里塞的东西越多,模型被无关信息干扰的概率就越大;而采用“分层记忆 + 按需加载”的方式,既能保证关键信息在场,又能控制上下文长度,可以说是“质量、速度、成本”三者同时获得改善的最好方案。 ## 5. 踩坑实录:记忆工程里那些“想当然”的陷阱 如果说前面聊的是怎么做,那这一章我想认真聊聊“千万别怎么做”。以下每个坑都是我在真实项目里踩过的,有些坑甚至踩了两次以上。 ### 5.1 把 AGENTS.md 当成 Prompt 垃圾桶 很多人(包括我早期)对 AGENTS.md 的定位是“把我想告诉模型的东西全写进去”。结果就是文件越来越长,读一遍的成本越来越高,而模型的注意力是有限的,它不会因为你在文件里写了“要重视这一条”,就真的给它更高的权重。 我的经验是:**AGENTS.md 只放“违反就会出大事”的规则**。像“代码风格推荐”“命名偏好”这种低风险偏好,放到检索记忆里就够了,不要让它们占用 AGENTS.md 的注意力额度。 ### 5.2 记忆里的过时信息比没有记忆更危险 记忆工程里我踩过最大的一个坑,是“记忆库里的旧信息污染了新的推理”。 举个例子。某次我把一个旧的接口签名存进了结构化记忆,后来后端改了路由,代码库已经更新,但记忆库没同步。于是模型在回答时信心满满地给出了旧接口地址,而且因为它“记得”,所以语气比没有记忆时更确定。这种错误比单纯“不知道”要糟糕得多,因为用户会被误导。 怎么解决?目前我采用了两道防线: 1. 给所有记忆条目打时间戳,检索时靠时间衰减做降权。 2. 在 Prompt 里明确告诉模型“如果记忆信息与代码检索结果冲突,以检索结果为准,并说明差异”。 这个方法虽然不是完美解法,但能大幅减少“自信地使用过期信息”的情况。 ### 5.3 检索记忆的“结果偏差”与重复信息压垮上下文 检索增强的另一个问题是“找到了一堆看似相关但实际重复的信息”。比如问了“这个项目怎么启动”,检索结果可能返回了 README 里的一段、AGENTS.md 里的一段、以及某次对话里的同一段,三份内容高度重复。模型等于用三倍篇幅读同一份信息,浪费上下文不说,还可能在细节不一致时“选择困难”。 我在工程里加的缓解手段是**检索结果去重 + 来源覆盖检查**:先按相似度聚类,每组只保留代表性片段;再统计来源的覆盖情况,避免所有结果都来自同一个文档。这一步听起来简单,但实际效果提升非常明显,上下文的利用效率高了很多。 ### 5.4 忽略“记忆的权限边界”,引入了安全风险 这一点是最近才认真对待的。记忆不是越多越好,也不是所有信息都该被模型记住。比如用户的隐私信息、未公开的业务策略、内部人员的个人信息,这些一旦进入记忆库,就可能在某次检索时被意外带出来,造成信息泄漏。 我的做法是:在记忆入库前做敏感信息过滤,在检索时做权限过滤,并且把“某些信息不能出现在上下文里”也作为一种反向规则写进配置。当然这只是一种基础防范,真要在生产环境里做得更严格,还需要专门的权限设计和审计机制。 > 提示:如果你在做对话类的产品,记忆的“遗忘机制”一定要在规划阶段就想清楚。默认记住一切是容易的,难的是做到“该忘的忘、该记的记得准、该被删的能删干净”。 ## 6. 范式转移的下一步:从“给模型塞上下文”到“构建可演进的记忆生态” 写到这里,我想把视角再拉高一点。AGENTS.md 和 Memory 工程表面上是在解决“模型记不住”的问题,实际上背后是一整套关于“人机协作范式”的转变。 ### 6.1 旧范式:以模型为中心,所有人都围着改 Prompt 过去很长一段时间,我们默认“模型的认知边界 = 你在 Prompt 里写了什么”。所以每次遇到模型表现不好,第一反应是调 Prompt;遇到新需求,第一反应是把更多背景塞进 Prompt。这个范式的终点是“破窗效应”——Prompt 越改越长,结构越改越乱,协作体验越来越差,模型的表现却不一定有多少提升。 ### 6.2 新范式:以知识为中心,模型只是读取记忆的执行者 而 AGENTS.md 和 Memory 工程代表的,是另一个完全不同的思路:**模型的角色从一个“全知者”降级为一个“按需读取知识的执行者”**。我们不再试图让模型“知道所有事”,而是给它一套精准的知识入口,让它知道在什么情况下该读哪份资料。 这带来的好处是: - **知识可以独立演进**。项目规则变了,你只改 AGENTS.md 或更新记忆库,不用改任何应用代码。 - **模型可以无缝替换**。今天用模型 A,明天换模型 B,只要记忆层的接口不变,整体的表现不会崩。 - **协作可以并行**。人的经验、代码的状态、项目的约定,都可以沉淀到记忆生态里,而不是只存在于某个人的脑子里。 ### 6.3 我眼中的“记忆生态”分层模型 经历了这些项目后,我逐渐形成了自己的一套分层认知,供你参考: 第一层是身份记忆,告诉 AI“你是谁、项目是什么”,对应零号文档,高度凝练。 第二层是规则记忆,包括红线、偏好、约定,稳定性强,必须严格遵守。 第三层是知识记忆,包括代码、文档、历史问答,体量大,按需检索。 第四层是工作记忆,也就是当前会话的上下文与临时状态,需要持续更新和裁剪。 这四层不是独立运行的,而是相互配合。没有一个标准答案说哪一层最重,它的比例完全取决于你的业务场景。但有一点我感受特别深:**凡是能让记忆“显式化、可维护、可删除”的工程,最终都会比那些“全塞进上下文”的野路子活得久**。 ## 写在项目之后的一些体会 最后说点个人体会吧。这套东西并不是某个天才发明的银弹,而是一个不断试错、不断修订的自然演化结果。我最初只是想把 AI 助手做得更靠谱一点,结果一路从调 Prompt 走到搭记忆分层、写 AGENTS.md、设计检索融合、管理会话刷新,回过头才发现自己其实已经把一套“人机协作基础设施”慢慢搭起来了。 如果你现在也在做类似的事情,我的建议是从最小的闭环开始:先写一份 100 行左右的 AGENTS.md 放进仓库,再把你的文档做成可检索的索引,最后才考虑要不要做复杂的结构化记忆。别一上来就想着搞一个完美的记忆系统,因为记忆系统本身就是需要演进的产品——你先让它跑起来,再让它记住该记住的,忘掉该忘掉的,最后它才会真正像一位靠谱的队友。