Claude Code 与 Codex Memory 机制详解
最近在疯狂学习 Agent 开发,把一些学习笔记整理成文章与大家分享。
这次聊聊 Claude Code 与 Codex 的 Memory。主要看几个问题:记忆如何组织以及记忆如何读写。
Claude Code 基于年初泄露源码,以及本文采用的 2.1.263 Memory 系统提示词。后台提取与 Dream 的实现只能作为旧版本参考。Codex 基于rust-v0.153.4源码。
两套设计都有一个很直观的思路:Markdown 组织记忆,渐进式披露这样每次对话都能找到过去的经验,也不用把全部历史重新塞进上下文,实现跨 session 记忆。
文章最后,我会介绍结合这两套思路,为DeepSeek Harness开发的dsh-memory插件。它用 Markdown 作为记忆载体,实现了简单的渐进式披露,并在LoCoMo上完成了 eval。欢迎大家使用并 Star:https://github.com/hr98w/dsh-memory。
Codex
感谢 Codex 是开源的,个人认为比 Claude Code 更有学习价值,我就把它放到了前面。
#系统提示词
Codex 的系统提示词中和 Memory 直接相关的是两部分:
•记忆读取规则什么时候要查记忆,先查哪里,什么时候需要验证旧信息,以及用了记忆以后如何标注来源
•memory_summary.md则提供用户偏好、常用经验和记忆导航。源码会把注入内容裁到约 2,500 Token,完整的MEMORY.md和历史摘要不会一起自动加载。(后面会详细讲讲记忆的组织与渐进式披露)
#记忆文件与渐进式披露
Codex 的记忆根目录是$CODEX_HOME/memories/,一般就是~/.codex/memories/:
memories/
├── memory_summary.md
├── MEMORY.md
├── raw_memories.md
├── rollout_summaries/
│ └── <会话摘要>.md
├── skills/
│ └── <流程名称>/SKILL.md
└── extensions/ad_hoc/notes/
└── <临时修改笔记>.md
这些名字看起来有点接近,可以按职责理解:
整体是遵循渐进式披露:一开始系统提示词里只有memory_summary.md,随后是按需进行加载,获取更详细的记忆文件。
#记忆的两阶段写入
先看整条路径:
新 turn 触发后台尝试
筛选符合条件的历史 thread
Phase 1:提取,写入 SQLite
Phase 2:同步中间文件,计算变化
整合 Agent 更新长期记忆
新 turn 只是触发一次尝试,还要检查开关、会话类型、数据库和额度等条件。后台异步执行,主对话不用等它完成。
Phase 1:提取会话记忆
默认会考虑最近 10 天内更新、且至少空闲 6 小时的符合条件的历史会话,排除触发后台任务的当前 thread。
对选中的 thread,程序读取磁盘 rollout,也就是持久化的会话记录/session,过滤部分注入内容和非目标记录,并在模型请求前后做敏感信息脱敏。
随后发起一次无工具的结构化提取请求,返回三个字段:
{
"raw_memory": "值得复用的知识、偏好和失败教训",
"rollout_summary": "这个会话做了什么、结果如何、有哪些证据",
"rollout_slug": "简短的会话标识"
}
成功后,结果先进入 SQLite 的一阶段结果中stage1_outputs。
Phase 2:整理多段会话记忆
这个阶段首先会把 1 阶段生成的记忆从 SQLite 读取出来,保存到文件中。
接着,Codex 用记忆目录里的 Git 记录计算记忆变化:哪些材料新增了,哪些更新了,哪些来源已经消失。需要整合时,把 diff 和文件位置交给内部 Agent。
这个 Agent 再按需读取材料,更新MEMORY.md、memory_summary.md,必要时把重复出现的操作经验整理成 skill。
第二阶段不会重新阅读全文原始会话,它只会读取 1 阶段的产物,以及访问原本的记忆系统,进行整理沉淀,处理多个 session 之间的关联记忆。
第二阶段成功后默认冷却 6 小时。这表示一段时间内不重复整合,不表示有一个每 6 小时准时运行的定时器。
用户主动修改记忆
主 Agent 还有一条明确的修改入口:临时笔记,也就是 ad-hoc notes。
例如用户说:“更新一下,以后这个项目使用 pnpm。”主 Agent 会把修改要求追加到:
extensions/ad_hoc/notes/<时间戳>-<简短名称>.md
二阶段发现这些文件变化,再把修改要求整合进长期记忆。
#记忆的读取
写入之后,主 Agent 依旧使用很普通的文件检索方式。
假设下一次进入项目,用户问:“上次数据库迁移是怎么处理的?”读取路径大致是:
已注入的 memory_summary.md
搜索 MEMORY.md 里的项目名与迁移关键词
打开相关 rollout summary 或 skill
必要时沿 rollout_path 核查原始会话
这里读原始会话的是主任务 Agent,直接进行Agentic Search
系统提示词还给了一个轻量检索预算:先做大约 4—6 步快速查找,通常只打开 1—2 个相关文件,没有命中就停止。它不要求每次先扫完整个记忆目录。
如果问题本身就很完整,比如简单翻译、改一句话、一行命令,可以直接跳过记忆。
#记忆的引用与遗忘
Codex 还有一个比较有意思的设计:模型引用用了记忆以后,会提供来源,LLM 会生成如下的信息,但是用户并不可见,codex 的程序读到后,会修改 SQLite 中的 usage_count 字段。
MEMORY.md:10-15|note=[migration workflow]
00000000-0000-4000-8000-000000000001
citation_entries记录文件、行号和用途,方便追溯;rollout_ids记录这些知识来自哪些 thread,用于更新使用次数。
这些 count 次数会影响后续第二阶段的材料选择:
• 先看新鲜度。默认保留最近 30 天内使用过的来源;从未被引用的,则按来源会话更新时间判断。
• 再排优先级。引用次数多的靠前,相同时再比较最近使用时间等信息。
• 取配置数量内的来源结果,供下一轮整合使用。
usage_count决定有限名额里的顺序,last_usage影响来源是否仍在保留期内。一个历史上很常用的来源,如果长期不再被引用,也可能过期。
把整个流程连起来,就能看出设计的闭环:
会话经验 提取 整合 按需读取 引用来源
└──── 使用记录影响保留 ────┘
Claude Code
Claude Code 的整理主要基于旧源码。auto-memory 在较新提示词里依旧存在,但自动提取、相关性召回和 Dream 是否仍按旧代码运行,我个人并不确定,大家可以仅作为参考。
先把几个名字分开:CLAUDE.md和 rules 用来放明确的指令与约定;auto-memory 用来保存对话中值得长期保留的信息。
#CLAUDE.md
CLAUDE.md也被 Claude 描述为记忆系统的一部分。它通常由人维护,或者由人指示 Agent 编辑,不走后面要讲的 auto-memory 自动提取流程。
用户、项目、子目录都可以有自己的文件,组织也可以统一下发:
启动时,会沿目录树读取当前目录及父级的相关文件;子目录里的文件,则在读取该目录下的文件时按需加载。
这些内容会一起进入上下文,更具体的内容通常排在后面。它没有配置文件那样严格的逐字段覆盖规则,所以最好不要让不同层级的要求互相矛盾。
rules 和CLAUDE.md类似,只是把规则按主题拆成了独立文件,方便管理。比如测试规范放testing.md,接口规范放api.md。
如果带有paths,规则只在读取匹配文件时加载。例如.claude/rules/api.md:
---
paths:
- "src/api/**/*.ts"
---
接口需要校验输入,并使用统一的错误响应格式。
没有paths的规则会在启动时加载,和顶层.claude/CLAUDE.md一样作为常驻的项目指令。这个拆分既方便维护,也能让只适用于某个模块的要求按需进入上下文。
#auto-memory
这部分是我理解中的狭义 agent memory。auto-memory 的文件组织很简单:
~/.claude/projects/<项目标识>/memory/
├── MEMORY.md
├── user-background.md
├── feedback-real-db.md
└── project-release.md
MEMORY.md是索引,详细内容放在单独的 Markdown 文件里
记忆分为四种类型:
按本文采用的新提示词,一个文件保存一个事实。下面是一条假设的反馈记忆:
---
name: integration-tests-use-real-db
description: 数据库集成测试使用真实数据库
metadata:
type: feedback
---
数据库集成测试应连接真实数据库。
Why:曾出现 mock 测试通过,但真实数据库迁移失败的情况。
How to apply:涉及数据库行为时,使用真实实例验证。
除了记录“做什么”,还留下“为什么”和“什么时候适用”。下一次遇到类似任务,Agent 才不至于把一条局部经验无限推广。
MEMORY.md索引里只保留一个入口:
- [数据库集成测试](integration-tests-use-real-db.md) — 使用真实数据库验证迁移与查询行为
记忆写入
首先,主 Agent 自己就能写。
用户说“记住,这个项目的集成测试要用真实数据库”,Agent 可以通过 Write/Edit 写入事实文件,再更新MEMORY.md。提示词会要求它先检查已有记忆,尽量更新已有条目,避免同一件事重复保存。
其次,泄露的源码还有回合结束后的后台提取:
主 Agent 完成本轮回答
stopHooks 检查提取条件
extractMemories 检查尚未处理的消息范围
fork 一个 Agent 提取长期信息
写入事实文件并维护索引
它受编译开关、服务端配置和会话条件控制,不是每个 turn 都无条件运行,个人并不确定这个功能还是否存在。
记忆读取
默认路径就是渐进式披露:
加载 MEMORY.md 索引
主 Agent 判断哪些记忆相关
Read 对应 Markdown
文件内容作为工具结果进入上下文
索引让 Agent 知道“这里有什么”,详细文件回答“具体是什么”。旧源码对索引注入有长度限制,其中一个限制是前 200 行;超过限制的内容不会全部进入上下文,原文件本身不会被裁掉。
旧源码还有一条默认关闭的试验功能,开关叫tengu_moth_copse。它会扫描记忆文件的文件名、description 等元信息,再把候选清单和用户问题交给模型,让模型选出最多 5 个相关文件。
用户问题 + 记忆文件元信息
模型选择相关文件
读取正文
包装进 <system-reminder>
提供给主 Agent
预取是异步的,主 Agent 不会停下来等它。每次工具执行后,程序检查结果是否准备好,准备好就注入,否则等同一次请求的下一次循环再检查。请求结束时会取消尚未完成的预取,因此不能直接说它一定会留到下一轮用户对话。
召回内容还带有根据文件修改时间生成的新鲜度提示,提醒模型这条记忆可能已经过时。新提示词也明确要求:记忆提到文件、函数或配置时,使用前要核查它们是否仍然存在。
#Dream
记忆不断写入以后,还会出现重复、矛盾和过期。Dream 处理的就是这些问题。
旧源码里,它是一次由模型完成的记忆整理任务,大致分成四步:
1. 浏览索引、已有记忆和近期线索,了解目前记了什么。
2. 收集需要补充或修正的信息,必要时搜索历史会话。
3. 合并重复主题,修正矛盾,把相对日期变成明确日期。
4. 精简索引,把详细内容移到主题文件,清理失效入口。
它最后修改的仍然是这套 Markdown 文件。
普通 auto-dream 会在回合结束后检查是否需要运行。旧源码的默认条件包括:距离上次整理至少 24 小时,并且至少有 5 个其他会话文件在此后被更新。这些阈值可以配置,也要满足功能开关等条件。
这部分蛮像刚才讲过的 Codex 记忆整理机制
站在巨人的肩膀上:dsh-memory
看完这两套机制,我想先把其中简单、可解释的部分用起来,于是为 DeepSeek Harness 做了dsh-memory。
我设计了两种车次:Global 保存跨项目偏好,Workspace 保存当前项目的事实与反馈。文件都在本地:
$DSH_HOME/memory/
├── GLOBAL.md
└── workspaces/<工作区标识>/
├── MEMORY.md
└── <具体记忆>.md
每次请求先提供GLOBAL.md和当前 Workspace 的MEMORY.md。Agent 看到相关入口,再使用已有的文件工具打开正文。其他 Workspace 的记忆不会一起提供给它。
在线写入通过专门的工具memory_update完成。模型提出记什么、怎么改,采用专门的记忆工具来保证格式的正确。
这个分工是我比较看重的:模型负责理解信息,程序/工具负责把文件和索引写对。
历史 Session 也可以整理。目前由用户手动触发,选择已结束且稳定的 Session,交给独立整理 Agent 检查并提出修改,它借鉴了 Codex 的会话整理思路(也可以说是类似 claude code 的 dream),但没有完整复刻 Codex 的两阶段后台调度。
实现之后,我用 LoCoMo-10 进行了 Eval,结果如下,得分比我想象中要好的多
而且不得不说,跑测评真是实打实的费钱
如果你也在用 DeepSeek Harness,欢迎试用 https://github.com/hr98w/dsh-memory ,提 Issue,也欢迎 Star。
写在最后
Claude Code 与 Codex 都采用了 markdown 组织记忆,主 Agent 写入 + 子 Agent 辅助整理的方式,并且通过 Agentic Search 的方式进行渐进式披露读取。
mem0, openviking 这样的记忆系统固然跑分很高,但是很重,有额外的开销,简单的 markdown + agentic search 就可以实现很高的分数,并且随着基模增强,这样的 memory 机制是会自动进步的。
在记忆系统形成范式前,markdown + agentic search 可能就是目前的记忆范式。