写在前面
这个博客最近一年多基本是我在 Claude Code 里维护的。用得久了有个很明显的感觉:它越来越”懂”我。刚开始每次都要交代一遍”改完跑 hexo g 验证、Git 要走代理、文章别用主题私有标签”,后来这些话说一次就不用再说了——它自己记得。
之前我把这归结为”上下文里塞了 CLAUDE.md”,直到最近读到两篇拆解文章(Claude Code 与 Codex 的自动记忆系统),才发现背后是一套完整得多的机制。记忆不是魔法,就是一堆本地 markdown 文件,加上一套”自动记、自动注入”的工程。但正是这些不起眼的工程细节,决定了一个 Agent 是”越用越懂你”,还是每次都像刚认识。
这篇是学习笔记:先把两家的实现捋清楚,再放在一起对比,最后写写我自己的体会。
一句话结论
两家都在给 LLM 加持久记忆,但取舍完全不同:
- Claude Code:边聊边记,偏好当轮生效。像一个实时贴心的助理。
- Codex:会话结束后复盘提炼,新记忆要等后续新会话才生效。像一个会自己复盘的工程师。
记忆到底是什么
先去掉滤镜。所谓 Agent 的长期记忆,工程上就两件事:
- 存:把值得记的事实写到某个持久化的地方;
- 取:在合适的时机把相关记忆注回模型上下文。
Agent 没啥神秘的,无非就是在 LLM 的上下文里做文章。记忆系统只是把”上下文工程”从单次会话扩展到了跨会话。
理解了这一点,剩下的全是工程问题:哪些内容值得记?写入怎么保证不漏?记忆过期了怎么办?积累多了怎么整理?两家的答案在这几个问题上分了岔。
Claude Code:随手记的实时助理
存储:朴素到出乎意料
第一反应可能是上数据库?其实都不用。Claude Code 每条记忆就是一个 markdown 文件,存在本地用户目录里。以我的博客项目为例,记忆目录长这样:
1 | ~/.claude/projects/-Users-astonwang-sswzone-codehub-sswfive-blogs-blogsite/memory/ |
MEMORY.md 不是记忆本体,而是索引:每行一条”标题 + 链接 + 一句话摘要”,模型每轮对话都能看到这份全局目录,需要细节再按图索骥去翻具体文件。每个记忆文件则是 YAML frontmatter(name / description / type)加 markdown 正文。
有个坑值得留意:记忆目录的路径是根据项目绝对路径生成的。如果把项目目录改名或挪了位置,Claude Code 会把它当成一个没有任何记忆的新项目,只能手动去改 ~/.claude/projects/ 下的目录名找回来。
记什么:四类记忆 + 一条铁律
记忆分四类,也就是 frontmatter 里的 type:
| 类型 | 记什么 | 例子 |
|---|---|---|
user |
用户身份、目标、知识背景 | 用户是编程初学者 |
feedback |
用户对工作方式的纠正或确认 | 代码注释用中文 |
project |
项目当前在做什么、为什么做 | 正在重构登录模块 |
reference |
外部系统的引用 | API 文档在某个 URL |
feedback 和 project 还有约定的正文结构:规则/事实 + Why + How to apply。Why 这一行的设计意图很关键——记下”为什么”,模型才能在边界 case 里判断规则该不该套用,而不是死板照搬。比如”注释用中文”配上 Why”这是个人中文项目”,下次给开源项目写注释时就知道该切换成英文。
这四类有个共同点:都是从代码、git log、CLAUDE.md 推导不出来的东西。System prompt 里专门列了不该记的内容:代码风格、架构、文件路径、git 历史、调试修复方案——这些读源码就能知道,存进 memory 反而是噪音,挤占召回名额。
更狠的是,即使用户主动要求”把这次的 PR 列表记一下”,模型也不能照单全收,而要反问:这里面有什么是意外的、非显然的? 只记那部分。memory 不是日志,而是”可以指导未来行为、又不能从别处推断出来的事实”。这条边界守不住,memory 就会变成垃圾场。
对照我自己的博客记忆,这几条确实都符合铁律:
- “Git 操作要走本地代理”——不是代码里能看出来的,是网络环境事实;
- “文章要写作者自己的想法,不做纯链接堆砌”——这是编辑偏好,只能从对话里学;
- “优先标准 Markdown,避免主题私有标签”——这是踩过坑之后的约束。
写入:双通道,防漏记
写入完全靠 system prompt 注入的指令驱动。最容易想到的方案是给主对话装个工具让模型自己调,但模型聊得起劲时顾不上停下来记笔记。
Claude Code 的做法是开两条通道并行:
- 主对话实时写:模型觉得有必要、或用户明确说”把 XXX 记一下”时,当场 Write 文件;
- 后台 agent 复查:每轮对话结束后,起一个后台 agent 复查这轮有没有漏记的。它走 Fork Agent 机制——把主对话的 messages 完整复刻当起点,追加一条”抽取记忆”的指令再跑一次,能共享 prompt cache,成本接近零。
召回:两层结构省 token
读取分两层:
- 第一层是
MEMORY.md索引,每轮全量注入 system prompt,让模型知道”手头有哪些主题的记忆”; - 第二层是按需召回:每次新消息到来时,系统扫一遍 memory 目录把 frontmatter 摘要凑成清单,单独起一个线程请模型挑出最多 5 个最相关的文件名(输出锁成 JSON),挑中的记忆读出来插在用户消息前面。
挑选手只看 frontmatter 不读全文,开销很小;记忆积累多了也不会每轮全量注入。被召回的记忆会包成 <system-reminder> 消息,带上 Memory (saved 3 days ago) 这种相对时间——让模型知道这是系统注入的背景,不是用户当下的指令,顺便感知新鲜度。
保鲜与整理:相对时间 + autoDream
记忆会过期。”三个月前说用 webpack”,后来切了 vite,旧记忆还注入进来就会误导模型。
Claude Code 的解法有两个小心思:
- 用相对时间描述新鲜度。不用
2026-03-23这种结构化日期,而用today/yesterday/47 days ago。因为模型对日期算术不敏感,看到具体日期不会自动觉得”这是几个月前的”,看到47 days ago就更容易判断该存疑。超过一天的记忆还会附一段警告:这是时点观察,不是实时状态,先核对再当事实用。 - 注入时提醒验证。记忆里提到的文件路径要先 check 存在,函数名要先 grep。”记忆说 X 存在”不等于”X 现在存在”。
记忆膨胀则靠一个叫 autoDream 的定期合并机制——趁不忙时让 agent 自己回顾整理,类似人睡觉时大脑整理记忆。四步:盘点目录 → 收集矛盾与待优化项 → 合并同主题碎片、把”一周后要做 XXX”这种相对日期改成绝对日期、删除过期内容 → 修剪 MEMORY.md 到 200 行 + 25KB 上限内。
Codex:会复盘的工程师
时机:关掉会话才记
Codex 走的是另一条路:主对话默认不动记忆文件。每次对话的完整过程(用户消息、模型回复、工具调用)被记成一个 rollout(.jsonl 回放录像),等会话结束后,后台跑一套”复盘流程”(源码里叫 pipeline):
- Phase 1 提炼:小模型把整个 rollout 过一遍,挑出值得记的经验;
- Phase 2 归档:更强的模型把新经验整合进统一的记忆文件。
整套流程后台静默执行。代价是:你跟 Codex 说”以后用中文回答”,要等下一次(甚至下下次)新会话才真正生效——中间隔着 Phase 2 的整合和新摘要的注入。
存储:全局一份,靠 cwd 区分来源
和 Claude Code 按项目隔离不同,Codex 用全局记忆 ~/.codex/memories/,所有会话共用一份。好处是换项目也带得走;缺点是项目间偏好可能冲突,所以每条记忆都带”从哪个项目目录产生”的元信息(applies_to: cwd=...; reuse_rule=...),模型读的时候自己过滤。
目录里的文件按作用分三组:
- 每次启动就注入的:
memory_summary.md,高度压缩的全局摘要(≤5000 token),记录固定偏好、常用项目要点、近期工作; - 对话中按需翻阅的:
MEMORY.md(按 Task Group 组织的记忆手册,每个任务记关键词和对应复盘文件的路径)、rollout_summaries/(每次会话的详细复盘); - 中间产物:
raw_memories.md(整理前的原始记忆)、extensions/ad_hoc/notes/(用户当场说”记一下”写的临时笔记)、.git/(整个目录是 git 仓库,后台流程靠 diff 检测变化)。
Codex 记忆的粒度是任务级别:不只记偏好,还记”为什么这么做、踩过什么坑、具体在哪行代码”。这和 Claude Code 的 Rule + Why + How to apply 思路一致——光记”是什么”不够,还得留下推理过程。
质量闸门:no-op gate
Phase 1 之前先层层过滤:临时会话、功能未开、子 agent 的 rollout、距上次活动太近的,统统跳过;system 指令、项目指令文件这些静态内容剔除;API Key 等敏感串先替换成 [REDACTED_SECRET]——这道脱敏是 Claude Code 没有专门做的。
过滤完交给小模型,其 system prompt 的核心是一个 no-op gate:
我现在写的这条记忆,真的能让未来的 agent 表现更好吗?如果 NO——比如只是一次性闲聊、没有 takeaways 的状态汇报、临时事实、常识性内容——直接返回空,什么都不写。
只有稳定偏好、高杠杆的程序性知识、可靠的任务地图、环境与工作流的持久证据这四类高价值内容才配进记忆。大多数会话会被这个闸门拦下。核心原则写得很直白:优化的是未来的用户时间,不只是未来的 agent 时间。
Phase 2 的杀手锏:git diff 短路
Phase 2 由一个严格限权的 consolidation agent 承担:拿全局锁防并发,把 Phase 1 的产物从 SQLite 同步到记忆目录,然后——
用 git diff 检查目录有没有真的变化。 如果这轮没生成任何新东西,diff 为空,整个 Phase 2 直接跳过,连大模型都不用调一次。”没事可干”这个最常见情况的成本被压到了接近零。相比之下,Claude Code 的 autoDream 没有这层短路,每次触发都得让大模型走一遍流程。
diff 不空时,consolidation agent 才开工:把 raw memory 整合进 MEMORY.md 的 Task Group、更新摘要、合并重复、清理过期。它被严格限权——不能再消费旧记忆(防递归生成)、不能上网、不能再开子 agent、MCP 清空,只许写记忆目录里的几个文件。它同时承担了 autoDream 的职责,防止记忆库碎片化。
读取:摘要导引 + 专用工具 + 强制引用
主对话里的读法也不同:
- 被动注入:
memory_summary.md启动时塞进上下文,一打开就有全局印象; - 主动深挖:摘要不够用时,模型自己提关键词去匹配 Task 块,按需打开 1-2 个 rollout 复盘看细节,整轮查记忆限 4-6 次工具调用;
- 专用 MCP:不让模型用通用 grep/Read 翻 memory 目录,而是挂一个 memory 专用 MCP server,只暴露
list/read/search三个限定范围的工具。好处是范围隔离(不会顺手搜到用户工作区)和可溯源(返回结构化的路径 + 行号 + 片段); - 强制引用:回答末尾附
<oai-mem-citation>块,标注用了哪条记忆、具体到MEMORY.md:45-47这种行号粒度、出自哪个 rollout。这层是 Claude Code 没有的——那边记忆是自动注入的,某次回答到底有没有受某条记忆影响,从结果上看不出来。
新鲜度方面,Codex 不做相对时间警告,而是让每条记忆带 updated_at 绝对时间戳,靠 Phase 2 的 consolidation agent 顺手清掉过期或矛盾的内容。
对比:两种”性格”
| 维度 | Claude Code | Codex |
|---|---|---|
| 写入时机 | 对话中实时写 + 后台 fork agent 兜底 | 会话结束后后台两阶段复盘 |
| 生效延迟 | 当轮生效 | 隔一到两次新会话 |
| 存储范围 | 按项目隔离 | 全局一份,cwd 标注来源 |
| 质量控制 | 主模型自觉 + 显式”不该记”清单 | no-op gate + 密钥脱敏 + 强制结构 |
| 读取方式 | 系统自动挑 5 条注入(被动消费) | 摘要导引 + 模型主动检索(按需深挖) |
| 可溯源 | 无 | citation 标注到文件行号和 rollout |
| 记忆保鲜 | 相对时间警告 + 建议验证 | 绝对时间戳 + 归档时清理 |
| 空转成本 | autoDream 每次都走大模型 | git diff 空则整段跳过 |
最直观的差别是”性格”:Claude Code 优化当下交互——你说的话马上被记住、马上生效,粘性自然高;Codex 优化跨会话经验沉淀——复盘质量可控、记忆更工程化,但实时性明显更慢。
我的几点体会
1. “越用越懂你”的本质是上下文,不是模型。
读完最大的祛魅是:记忆系统里没有一项技术是难的。markdown 文件、索引、system prompt 指令、后台复查、定期合并——每条单看都很朴素。但组合起来,就让”部署到线上”四个字能唤起完整的部署流程。这对做 Agent 开发的人是个好提示:与其等一个”有记忆的模型”,不如在上下文工程上做文章,今天就能动手。
2. 该记什么,比怎么记更重要。
两家在这一点上高度一致:只记”推导不出来的东西”,并且要带上 Why。这一点对我触动最大。以前我觉得 CLAUDE.md 里写满项目规范就是”记忆”了,但其实那叫文档——能从代码里读出来的,都不是记忆。真正的记忆是”用户纠正过你一次的偏好”、”只有踩过坑才知道的环境事实”。Claude Code 那句”用户要求保存时要反问:有什么是意外的、非显然的”,值得抄进自己做 Agent 的提示词里。
3. 实时性和质量是一对天然矛盾。
Claude Code 的实时写入换来了”当轮生效”的爽感,代价是质量主要靠模型自觉;Codex 延后复盘换来了 no-op gate、脱敏、结构化这些质量闸门,代价是”说了不马上听”。哪边更好取决于场景:日常结对编程显然前者体验好;沉淀团队经验、构建任务知识库,后者的模式更靠谱。做 Agent 系统设计时,这恐怕是记忆模块最先要做的取舍。
4. 几个可以直接抄的工程细节。
- 相对时间戳(
47 days ago而非绝对日期)——利用模型对自然语言时间的感知来提示新鲜度,比让它做日期算术可靠; - git diff 短路——把”没事可干”的成本压到零,这在任何定期维护任务里都值得抄;
- Rule + Why + How to apply——给每条规则写上理由和触发条件,模型才敢在边界 case 里自己判断;
- 专用的、限权的记忆工具——范围隔离 + 可溯源,比放任模型用通用文件工具乱翻要可控得多;
- ad hoc 笔记与正式记忆分流——Codex 把”当场记一下”写到临时笔记,等复盘时再整合,避免主流程被随手改乱。
5. 记忆正在变成独立基础设施。
文章末尾提到的方向我挺认同:mem0、Letta 这类项目在把记忆从”某个工具的内部能力”剥离成”跨工具的共享层”。一份属于用户的记忆,应该能被任何 agent 读写,而不是锁在单一工具里。如果做成了,工具竞争就不再围着”谁更能记住用户”,而是回到”谁更会用这份记忆”。到那时,今天这些各家私有的 memory 目录,大概会像今天的浏览器书签一样,是可以导出和迁移的。
参考
- 《Claude Code 自动记忆系统》《Codex 自动记忆系统》两篇拆解文(文风和内容组织方式类似 labuladong 的 AI 编程系列)
- 本站旧文:从Claude Code的架构设计浅析Agent、Agent系统开发手记(三):上下文管理
- mem0、Letta——通用 Agent 记忆层的两个代表项目