记忆层:跨会话该记住什么,何时读取,何时淘汰
| 难度 | 阅读时间 | 最后验证 | 作者 |
|---|---|---|---|
| 进阶 | 14 分钟 | 2026-07-11 | LearnPrompt 编辑部 |
你上个会话花十分钟给 Agent 讲清了这个项目的构建命令、目录约定和那个反直觉的坑。今天开一个新会话,它像什么都没发生过:又不知道要先跑 setup、又把同一个文件改错、你又得从头讲一遍。
多数人的下一步是把这些约定复制粘贴进每一次对话,或者干脆把上次的聊天记录整段塞回去。但上下文越长,遵循度越低,而且每次都在重付 token。真正要解决的问题是:哪些信息值得跨会话留下来,用什么结构留,什么时候读回来,什么时候把它扔掉。这就是记忆层。它和把所有历史塞进上下文是两回事——记忆是挑出来的、压缩过的、能被审计和淘汰的持久事实。
读完你能做什么
标题为“读完你能做什么”的章节你会学会把任意一条信息放进正确的桶,并给跨会话记忆定一条纪律:
- 按生命周期分桶:当前上下文、任务状态、持久项目指令、自动记忆、原始证据。
- 判断一条信息该不该进跨会话记忆,以及为什么。
- 用相关性门控召回,而不是把整个记忆库倒进上下文。
- 按过期、污染、隐私三条边界淘汰记忆。
- 说清 Claude Code 的 auto memory 与 CLAUDE.md、Codex 的 AGENTS.md 与实验特性 memories 各是什么,不把它们混成一套术语。
你还会运行一个最小仓库,亲眼看到没有记忆时两个独立进程重复同一个错,写入一条可审计教训后,一个全新进程如何读取并避开它。
图注:左边决定什么该进跨会话记忆,右边决定它何时被读取、何时被淘汰;召回一路的绿框特意标出「不召回则零作用」,因为记忆不等于模型自动记住。
先看一个失败:换个进程,同一个坑又踩一遍
标题为“先看一个失败:换个进程,同一个坑又踩一遍”的章节假设有一个小任务:跑一条校验命令 node src/build-token.mjs。它要读一个 data/fixture.json,而这个文件是 setup 阶段生成的产物,写进了 .gitignore、不入库,全新克隆或全新进程里默认不存在。
一个没有任何记忆的进程直接跑校验,会撞上这个报错:
$ node scripts/run-session.mjs --mode no-memory --today 2026-07-11FAIL build-token:缺少 data/fixture.json —— 该文件由 setup 生成、不入库(.gitignore),全新进程默认不存在;必须先运行 node src/setup.mjsresult: exit=1它自己摸索一阵,也许最后跑通了。但只要它没把这条教训留下来,下一个独立进程就会从零开始,撞上一模一样的错——这正是 Showcase 里第二个 no-memory 进程发生的事:同样的输入,同样的 exit 1。这不是模型笨,而是每个会话都从一个全新上下文窗口开始,没人替它把上次学到的东西递过来。跨会话的重复犯错和重复盘点,根因是缺一层能持久携带教训的记忆,而不是提示词不够诚恳。
记忆层做的是跨会话复用,不是囤积上下文
标题为“记忆层做的是跨会话复用,不是囤积上下文”的章节记忆层回答的问题只有一个:跨会话、跨进程,要记住什么。它和当前上下文的分界很清楚——上下文是这一轮对话的工作区,会话一结束或一压缩就没了;记忆是从中挑出、值得下次还用的持久事实。
Claude Code 官方文档把这层机制说得很直接:每个会话从一个全新上下文窗口开始,靠两套互补机制跨会话携带知识。一套是你写的 CLAUDE.md,放指令和规则;一套是模型自己写的 auto memory,放它发现的教训和模式。两套都在每次会话开始时载入,但都被当作上下文,而不是强制配置。也就是说,写进记忆不保证被严格遵守;要真正拦住一个动作,得用 PreToolUse 钩子,那是约束层的事。记忆层负责的是「下次还能用得上」,不是「这次必须照做」。
这条边界决定了记忆层的纪律:不是把聊天记录囤起来,而是挑出跨会话可复用的少数事实,压缩成索引,需要时再召回。
按生命周期分五个桶
标题为“按生命周期分五个桶”的章节要判断一条信息该放哪里,最好用的尺子是生命周期:它能活多久,谁写、谁在什么时候读回来。按这把尺子,日常打交道的信息可以分成五个桶。
| 桶 | 生命周期 | 谁写 / 何时读 | 典型内容 | 归属层 |
|---|---|---|---|---|
| 当前上下文 | 本轮会话 | 双方 / 当下 | 这次对话的往返、临时推理 | 上下文,非记忆 |
| 任务状态 | 一个任务 | 编排 / 跨轮但任务内 | 目标、进度、上次失败证据 | 状态层 |
| 持久项目指令 | 跨会话 | 人 / 每次全量载入 | CLAUDE.md、AGENTS.md 的规则 | 指令层(与记忆交叠) |
| 自动记忆 | 跨会话 | 模型 / 索引载入、细节按需 | build 命令、调试洞见、偏好 | 记忆层核心 |
| 原始证据 | 跨会话(外置) | 系统 / 用指针引用 | 日志、diff、测试报告 | 记忆指向,不内联 |
记忆层管的是下面三个桶:持久项目指令、自动记忆、原始证据——都是跨会话才有意义的东西,其中自动记忆是它的核心。上面两个桶不归它:当前上下文属于这一轮的工作区,任务状态属于状态层。
最常见的错误就是错层。把「这一轮的临时结论」当成永久记忆写下去,下次它就成了误导;反过来,把「这个项目永远要先跑 setup」这种跨会话事实不写进记忆、每轮靠上下文重讲一遍,就是在为缺记忆持续付费。分桶不是学术分类,而是决定一条信息值不值得跨过会话边界。
保存什么、为什么保存
标题为“保存什么、为什么保存”的章节跨会话记忆要保存的,是那些「否则你就得反复重讲」的东西。Claude Code 官方给了一份很实在的「何时该写进记忆」清单,几乎就是在定义为什么要保存:
- 模型第二次犯了同样的错。
- code review 抓到一件它本该知道的项目惯例。
- 你又把上个会话敲过的同一句纠正敲了一遍。
- 一个新同事要上手,需要同样的背景。
这四条的共同点是「重复」。auto memory 也是同一个判据:模型不是每次都存,而是判断这条信息在未来会话里还用得上,才把它记下来——它存的通常是构建命令、调试时踩出来的洞见、架构笔记、代码风格偏好这类会反复用到的事实。
一条值得保存的记忆,不该只是一句结论,而应该带上能被复用和审计的字段。Showcase 里那条教训是这样写的:
---name: fixture-before-verifyscope: this-repotrigger: [build-token, verify, fixture]verified_at: "2026-07-11"expires: "never"kind: durable-lesson---事实:跑 build-token 之前必须先跑 setup。为什么:data/fixture.json 是生成物、不入库,全新进程默认不存在。怎么用:任何一次校验前先生成 fixture,再校验。证据指针:showcase/memory-recall/result.txt 阶段 1。事实、为什么、怎么用、验证日期、作用域、证据指针——这几项让记忆既能被下次的进程直接执行,也能被人回头审计。特别是证据指针:记忆是压缩过的事实索引,不该把整段日志抄进去,而是指向原始证据的所在。这既省上下文,又保住了可追溯性。压缩不是丢信息,而是把细节留在原处、只在记忆里留一个能找回它的入口。
何时读取:召回是门控,不是全量倒入
标题为“何时读取:召回是门控,不是全量倒入”的章节保存只是一半,读取是另一半,而且更容易被忽略。一个自然但错误的想法是:既然记下来了,就每次全量塞回上下文。这样记忆库一大,上下文就被它占满,遵循度反而下降。
官方 auto memory 的读取被压成两层,正好示范了正确做法。记忆目录里有一个 MEMORY.md 当索引,每次会话开始只载入它的前 200 行或前 25KB(先到先算),超出的部分不在启动时载入;而像 debugging.md 这样的细节 topic 文件,启动时根本不加载,模型需要时才用文件工具去读。换句话说,进上下文的是一份精简目录,细节按需再取。这就是相关性门控加索引压缩:先看索引,命中当前任务才深入。
Showcase 用一个最小的召回闸门把这条纪律做实。它按 trigger 判断一条记忆是否跟当前任务相关,只把命中的取出来:
$ node scripts/recall.mjs build-token --today 2026-07-11RECALL fixture-before-verify [durable-lesson]RECALL mirror-slow-on-0711 [one-time-state]summary: 2 recalled, 0 evicted召回之后,一个全新进程先照教训跑 setup,再校验,一次就过:
$ node scripts/run-session.mjs --mode with-memory --today 2026-07-11RECALL fixture-before-verify [durable-lesson]setup:已生成 data/fixture.jsonPASS build-token:fixture.json 就位,token=ready-2026result: exit=0从 exit 1 到 exit 0,靠的不是这个进程更聪明,而是它读到了上一次留下的教训。
何时淘汰:过期、污染、隐私
标题为“何时淘汰:过期、污染、隐私”的章节记忆会腐坏,所以必须有淘汰。三种情况要主动清理。
过期,是最容易被忽略的一种。一次性的环境状态——比如「今天内网镜像抽风、setup 偶发慢」——不是长期项目事实,过了那天就不再成立。把它当永久记忆留着,下一次进程会据此做出已经不对的判断。给这类条目标一个 expires,召回时超期就淘汰。Showcase 把时间推到过期之后,那条一次性状态就被挡在门外,而永久有效的那条照常召回:
$ node scripts/recall.mjs build-token --today 2026-07-13RECALL fixture-before-verify [durable-lesson]EVICT mirror-slow-on-0711 — 已过期(expires 2026-07-12 < 2026-07-13),不注入summary: 1 recalled, 1 evicted污染,是把不该长期存在的东西写成了永久记忆。任务状态、一次性结论、临时假设写进记忆库,都会在日后误导别的进程。官方也把 CLAUDE.md 定位成「每个会话都该持有的事实」,而多步流程或只在局部生效的规则应该下沉到 skill 或路径门控的 rule,别塞进入口记忆;两条记忆互相矛盾时,模型可能任选其一,这本身就是污染的代价。
隐私,是一条硬边界。密钥、session id、绝对路径、用户私密历史,绝不写进记忆。auto memory 是机器本地的明文 markdown,可以用 /memory 审计、也能直接删——正因为它是人能读到的文件,更不能把敏感信息留在里面。真正拦住写入是约束层的职责,记忆层这边的纪律是:不主动留存任何敏感数据。本文的 Showcase 也守着同一条线,记忆条目里没有任何密钥、session id 或绝对路径。
别把不同产品的记忆机制混成一套
标题为“别把不同产品的记忆机制混成一套”的章节记忆层最容易踩的坑,是把不同产品的功能当成同一件事。它们的名字、写入方、载入方式都不同,混着说会写出既不生效又有安全错觉的配置。核验日期 2026-07-11。
Claude Code(本地 2.1.206)有两套并列的机制:
- CLAUDE.md 是你写的持久指令,按 managed → user → project → local 的层级、从根到工作目录拼接,祖先目录的全量载入,官方建议单文件控制在 200 行内,可用
@path导入(最多四跳)。它属指令层,只是因为跨会话持久,才和记忆层交叠。 - auto memory 是模型自己写的记忆,存在
~/.claude/projects/<项目>/memory/,按 git 仓库归目录、worktree 共享、机器本地;它需要 v2.1.59 以上,默认开启,可用/memory审计或关闭。载入规则前面讲过:MEMORY.md索引进上下文,topic 文件按需读。
一个容易混的点:Claude Code 只读 CLAUDE.md,不读 AGENTS.md;想让两者共用,得在 CLAUDE.md 里 @AGENTS.md 导入或做软链。另外,压缩时项目根的 CLAUDE.md 会在 /compact 后从磁盘重读并重新注入,而子目录里嵌套的 CLAUDE.md 不会自动重注入——这决定了哪些指令能挺过一次压缩。
Codex(本地 codex-cli 0.142.2)是另一套东西。用 codex features list 可以看到,它把 memories 标为实验特性(experimental)、把 auto_compaction 标为稳定特性;会话可以 resume、fork、archive。而它文档化的跨会话「指导」机制是 AGENTS.md——那是指令,多份文件按根到叶合并、project_doc_max_bytes 默认 32 KiB。也就是说,Codex 的 memories 目前是一个实验开关,本文只陈述它的存在与状态,不臆测它的召回或淘汰语义;要用请以官方为准,别把它当成 Claude Code 的 auto memory 的同义词。
一句话记住分界:Claude Code 的 auto memory 与 Codex 的 memories 是不同产品的不同实现;CLAUDE.md 与 AGENTS.md 是两套不互读的指令机制。配置时按各自的官方文档写,不要交叉套用。
记忆层和其他四层的分工
标题为“记忆层和其他四层的分工”的章节记忆层很有用,但它只负责「跨会话记住什么」,不负责本轮工作、即时信号、判决和调度。分不清边界,就会把本该别处解决的问题硬塞进记忆。
- 指令层回答「应该怎样工作」。CLAUDE.md 是你写的持久指令,和记忆交叠,但它是人写的规则,不是模型学来的教训。
- 状态层回答「这一轮要记住什么」。目标、进度、上次失败证据属于任务状态,任务一结束就该弃;记忆是跨会话才留的东西。别把任务状态写成永久记忆,也别让跨会话事实每轮靠状态重述。
- 反馈层回答「这一轮从环境刚拿到什么信号」。反馈是回路内即时、每次行动都刷新的东西;它可以沉淀成一条记忆,但它本身不是记忆。
- 编排层回答「谁读记忆、何时召回、何时停」。召回发生在哪一步、失败后要不要重开一个干净上下文,都是编排的控制流;记忆只提供被读取的内容。
一个简单的判断法:如果一条信息换个会话还用得上、且值得被审计和淘汰,那是记忆层的活;如果它只在这一轮或这一个任务里有意义,就交给上下文或状态层。
Showcase:没有记忆就重复犯错,写入一条教训后独立进程能读取并避开
标题为“Showcase:没有记忆就重复犯错,写入一条教训后独立进程能读取并避开”的章节为了让「记忆决定能不能避开同类错误」不停留在口号,本文附了一个无网络依赖的最小仓库,保留了从「无记忆重复犯错」到「召回后避开」的完整可复现过程。完整目录、命令与脱敏输出见 研究与 Showcase。
任务对象是一个校验脚本,它依赖一个不入库、需先生成的 fixture 产物。五个阶段串起整条链路:两个 no-memory 的独立进程各自撞上同一个报错(重复犯错);写入一条带事实、为什么、怎么用、验证日期的教训后,召回闸门能取出它;一个全新进程先召回再行动,校验通过;把时间推到过期之后,一次性状态被淘汰、永久教训存活;最后,一个「记忆在盘上却不召回」的进程,照样重复犯错:
$ node scripts/run-session.mjs --mode skip-recall --today 2026-07-11FAIL build-token:缺少 data/fixture.json —— …必须先运行 node src/setup.mjsresult: exit=1这个 Showcase 证明的是:跨会话能不能避开同类错误,取决于教训有没有被结构化地写下来、并在下一次被召回注入。它没有证明的是:recall.mjs 只是「相关性门控加过期淘汰」两条读取纪律的最小可复现模型,不是任何产品本身;真实产品的召回与淘汰语义以官方为准。它也不构成任何模型排名——全程无网络、未运行任何在线大模型,跑的是确定性脚本。最关键的一条限制写在最后一个阶段里:记忆在盘上却不去召回,教训就零作用。记忆不等于模型自动记住,它是「写入磁盘加召回注入」的检索,缺了召回这一步,写得再好也不会自己回来。
什么时候不该用记忆层解决
标题为“什么时候不该用记忆层解决”的章节记忆层不是万能钥匙。遇到下面这些情况,先别急着往记忆里写:
- 失败来自工具缺失或权限不足。这属于能力层或约束层,记再多教训也变不出一个不存在的命令。
- 你需要的是硬拦截而非提醒。「绝对不能删生产库」要靠 deny 规则、沙箱或 PreToolUse 钩子在动作发生前拦住,写进记忆只是软提醒。
- 信息只在这一轮或这一个任务里有意义。临时结论交给上下文,任务进度交给状态层,别让它跨过会话边界变成污染。
- 同一个问题已经被失败尝试反复污染。这时正确动作是重开一个干净上下文、用更好的提示重来,而不是把混乱的历史当记忆继续背下去。
一个判断法:如果把一条信息写下来,下次会话能少走一段弯路、且这条信息值得被审计和过期,那是记忆层的活;如果它改变不了环境、工具或强制边界,或者根本活不过这个任务,就换层解决。
练习:给一条信息找它的正确桶
标题为“练习:给一条信息找它的正确桶”的章节找一件最近让你「换个会话又重讲一遍」的事,或者一个反复踩的坑,填这张表:
这条信息: 一句话写下它是什么生命周期: 活到本轮 / 本任务 / 跨会话正确的桶: 当前上下文 / 任务状态 / 持久项目指令 / 自动记忆 / 原始证据为什么保存: 否则会重复重讲还是重复犯错何时淘汰: 永久有效,还是该标一个 expires隐私检查: 有没有密钥 / session id / 绝对路径要剔除如果它只活在这一轮,就别写进记忆;如果它跨会话可复用,就把它写成一条带事实、为什么、怎么用、验证日期的教训,并给它一个作用域和过期策略。然后做一次关键验证:换一个全新进程或新会话,只靠这条记忆的召回,看它能不能避开原来的弯路。
完成标准:这条信息能明确落到五个桶里的一个;跨会话的那条能被独立进程召回并复用一次;任何敏感字段都已剔除。如果你发现「写下来了但下次它没自己回来」,那说明缺的不是记忆本身,而是召回这一步——记忆不等于自动记住。
来源与延伸阅读
标题为“来源与延伸阅读”的章节- Claude Code 官方文档:How Claude remembers your project(CLAUDE.md 与 auto memory)
- Claude Code 官方文档:Hooks(PreToolUse,硬拦截)
- OpenAI Codex 官方文档:Custom instructions with AGENTS.md
- Anthropic:Effective harnesses for long-running agents
- Harness Engineering 橙皮书
- 本篇研究包与可运行 Showcase
前四条为官方一手资料,支撑本文所有当前产品行为,并用本地 Claude Code 2.1.206、codex-cli 0.142.2 核验,日期 2026-07-11;其中 auto memory 的「MEMORY.md 载入前 200 行或 25KB」「需 v2.1.59 以上」为当前实现,Codex memories 为实验特性,均可能随版本变化。橙皮书作为中文主题地图(二手来源)帮助确认记忆层在 harness 叙事中的位置,其仓库为教育性分享并要求署名,本文在此保留链接与署名,未复制其任何图片或成段文字。五桶分类与写入/召回/淘汰三段纪律是 LearnPrompt 对官方机制的操作化综合,不是行业统一标准;文中 Showcase 已重新组织并复核。
