1. Agent 持久化记忆的路线之争:为什么突然绕开了向量库
Agent 的记忆问题,从 2024 年下半年开始就成了开发圈里绕不开的话题。早期大家做 Agent 记忆,第一反应几乎都是“上向量库”——把对话历史切片、embedding、塞进 Chroma 或 Milvus,检索的时候做相似度召回。这套方案在知识库问答场景里确实好用,但放到编码 Agent 的持久化记忆上,问题就暴露了:代码上下文是高度结构化的,函数名、文件路径、依赖关系、报错栈,这些东西用向量相似度去召回,经常召回来一堆“语义相近但实际无关”的片段,反而干扰模型判断。
Codex 和 Claude Code 这两个工具,是目前编码 Agent 里最有代表性的两个。它们在持久化记忆的设计上,走了一条和传统 RAG 完全不同的路——不依赖向量库,而是用文件系统加结构化索引的方式来做记忆的存储和召回。这个选择背后有很实际的工程考量,我下面会一层层拆开讲。
先说清楚这篇文章适合谁看。如果你正在做 Agent 开发,尤其是编码类 Agent,或者你在用 Codex、Claude Code 做日常开发,想搞清楚它们的记忆机制到底怎么运作、能不能自己复刻一套,那这篇内容会对你有直接帮助。如果你只是听说过这两个工具但还没上手,也没关系,我会把基础概念补上,保证你能看懂。
核心关键词先摆出来:Codex、Claude Code、Agent 持久化记忆、向量库。这四个词贯穿全文,后面所有的分析都围绕它们展开。
2. 两条路线的整体设计思路拆解
2.1 为什么编码 Agent 的记忆不适合纯向量检索
要理解 Codex 和 Claude Code 为什么不用向量库,得先搞清楚编码场景下“记忆”到底是什么。
传统 RAG 的记忆模型是这样的:用户问了一个问题,系统把问题转成向量,去向量库里找最相似的 top-k 片段,拼进 prompt 里让模型回答。这个流程在“文档问答”场景下很自然,因为文档本身就是非结构化的自然语言,语义相似度约等于相关性。
但编码场景完全不一样。你在写代码的时候,需要的记忆是什么?是“上次我改了这个文件的那几个函数”、“这个项目用的是 pnpm 不是 npm”、“用户之前明确说过不要动 config 目录”。这些记忆有的是事实性的(项目配置),有的是偏好性的(用户习惯),有的是上下文性的(当前任务进度)。它们的共同特点是:结构清晰、有明确的键值关系、需要精确召回而不是模糊匹配。
举个例子。假设你的 Agent 之前记录过“这个项目的测试命令是pnpm test:unit”。如果用向量库存储,当用户问“怎么跑测试”的时候,向量检索可能会召回“测试”、“单元测试”、“jest 配置”等一堆相关但不精确的片段,而真正需要的那条精确命令反而可能排在后面。但如果用结构化存储,直接按 key 查test_command就能拿到,零歧义。
这就是编码 Agent 记忆的第一个核心需求:精确召回优先于语义召回。
2.2 Codex 的记忆路线:文件系统即记忆
Codex 在持久化记忆上的设计思路,我总结成一句话:把记忆写进文件系统,用目录结构做索引。
具体来说,Codex 会在项目根目录下维护一个隐藏的记忆目录(类似.codex/memory/这样的结构),里面按类别存放不同的记忆文件。比如:
project_context.md:项目的基本信息,技术栈、目录结构、构建命令user_preferences.md:用户的编码偏好,命名风格、注释语言、提交信息格式task_history/:按时间戳存放的任务记录,每次任务一个文件decisions.md:重要的技术决策记录,为什么选了这个方案而不是那个
这种设计的巧妙之处在于,它把“记忆检索”变成了“文件读取”。Agent 在需要某类记忆的时候,直接读对应的文件就行,不需要做任何相似度计算。而且文件系统本身就有层级结构,天然支持分类和归档。
提示:Codex 的记忆文件格式通常是 Markdown,这样人类也可以直接阅读和编辑。这一点很重要,因为 Agent 的记忆不应该是黑盒,开发者需要能随时检查和修正。
2.3 Claude Code 的记忆路线:CLAUDE.md 加分层索引
Claude Code 的记忆机制和 Codex 有相似之处,但更强调“分层”和“自动加载”。
Claude Code 的核心记忆载体是CLAUDE.md文件。这个文件可以放在多个层级:
- 项目根目录的
CLAUDE.md:项目级记忆,所有会话共享 - 子目录的
CLAUDE.md:模块级记忆,只在处理该模块时加载 - 用户主目录的
CLAUDE.md:全局记忆,跨项目共享
当 Claude Code 启动时,它会自动从当前工作目录向上遍历,把所有层级的CLAUDE.md都加载进来,合并成一份完整的上下文。这个设计和 CSS 的层叠规则很像——越靠近当前工作目录的规则优先级越高。
除了CLAUDE.md,Claude Code 还会维护一个会话级的记忆缓存,记录当前对话中已经确认的事实和决策。这个缓存不落盘,只在当前会话有效,但会在会话结束时把重要的部分回写到CLAUDE.md里。
2.4 两条路线的核心差异对比
| 维度 | Codex | Claude Code |
|---|---|---|
| 记忆载体 | 隐藏目录下的多个 Markdown 文件 | 分层级的 CLAUDE.md 文件 |
| 索引方式 | 目录结构 + 文件命名约定 | 目录层级 + 自动向上遍历 |
| 加载时机 | 按需读取,Agent 主动查询 | 启动时自动加载,全量注入 |
| 写入方式 | Agent 显式写入指定文件 | 会话结束回写 + 手动编辑 |
| 人类可读性 | 高,文件独立可读 | 高,单文件集中可读 |
| 跨项目共享 | 较弱,主要靠全局配置 | 强,用户级 CLAUDE.md 天然支持 |
这张表里的差异,本质上反映了两者对“记忆应该由谁管理”的不同理解。Codex 更倾向于让 Agent 自己管理记忆,按需读写;Claude Code 更倾向于让开发者主导记忆,Agent 只是消费者。
3. 核心细节解析与实操要点
3.1 Codex 记忆文件的组织规范
如果你想让 Codex 的记忆机制发挥最大效果,文件组织是有讲究的。我实测下来,下面这套结构比较稳:
.codex/ memory/ project.md # 项目概览:技术栈、目录说明、构建命令 preferences.md # 用户偏好:代码风格、命名习惯、注释语言 decisions/ 2024-01-15.md # 按日期归档的技术决策 2024-01-20.md tasks/ current.md # 当前任务状态 archive/ # 已完成任务归档 glossary.md # 项目术语表:内部缩写、领域概念这套结构的关键在于职责分离。project.md只放稳定的项目信息,不经常变;preferences.md放用户偏好,跨任务复用;decisions/按时间归档,方便追溯;tasks/current.md是易变的,每次任务更新。
为什么要这么分?因为 Agent 在读取记忆的时候,不同场景需要的信息不一样。写新代码的时候需要preferences.md和project.md;排查历史问题的时候需要decisions/;继续未完成任务的时候需要tasks/current.md。分开存储可以让 Agent 精确加载,避免把无关信息塞进上下文浪费 token。
注意:不要把所有记忆都塞进一个文件。我见过有人把几千行的项目说明全写进一个
memory.md,结果每次 Agent 加载都要消耗大量 token,而且真正需要的信息被淹没在噪音里。
3.2 Claude Code 的 CLAUDE.md 编写技巧
CLAUDE.md的写法直接决定了 Claude Code 的记忆质量。我踩过的坑是:一开始把它当成 README 写,堆了一堆项目介绍,结果 Claude 在实际编码时根本不看那些内容。
后来我调整了策略,CLAUDE.md只写对编码行为有直接影响的内容。具体包括:
- 构建和测试命令:精确到可复制粘贴的程度,比如
pnpm test:unit -- --watch - 代码规范:不是泛泛的“遵循最佳实践”,而是具体的“函数名用 camelCase,组件名用 PascalCase”
- 目录约定:哪些目录是源码,哪些是生成物,哪些不要动
- 已知陷阱:这个项目有哪些坑,比如“修改 schema 后必须跑 migration”
- 外部依赖:用了哪些内部库,文档在哪里
这些内容的共同点是:Claude 在写代码时需要知道,但光看代码本身看不出来。项目介绍、架构图这些东西,Claude 自己读代码就能理解,不需要你重复写。
另外,CLAUDE.md支持@import语法,可以把其他文件的内容引入进来。这个功能很适合把长文档拆开:
# 项目记忆 @import ./docs/api-conventions.md @import ./docs/testing-guide.md这样主文件保持简洁,细节放在子文件里,Claude 按需加载。
3.3 记忆写入的时机与触发条件
两条路线在“什么时候写记忆”上也有讲究。写早了浪费,写晚了丢失,时机很关键。
Codex 的写入时机通常是任务完成后。Agent 在完成任务后,会把这次任务的关键信息写入tasks/archive/,如果产生了重要的技术决策,再写入decisions/。这个流程需要 Agent 有明确的“任务结束”信号,否则容易漏写。
Claude Code 的写入时机更灵活。它会在以下几种情况触发记忆回写:
- 用户明确说“记住这个”或“更新 CLAUDE.md”
- 会话结束时,自动把本次会话的重要结论追加到
CLAUDE.md - 检测到用户重复强调某个偏好时,主动提议写入
我个人的经验是,不要完全依赖自动写入。自动写入的内容往往比较粗糙,而且可能把临时性的信息也写进去。更好的做法是:自动写入作为草稿,定期人工审查和整理。
3.4 不用向量库的代价与补偿手段
不用向量库不是没有代价的。最大的问题是:当记忆量大了之后,怎么快速找到需要的那条。
向量库的优势就是模糊召回,你不需要知道具体存在哪里,搜一下就能找到。文件系统方案没有这个能力,你必须知道去哪个文件找。
Codex 和 Claude Code 的补偿手段不太一样:
Codex 靠的是Agent 的主动查询能力。Agent 在需要记忆的时候,会先列出记忆目录的文件列表,根据文件名判断该读哪个。这要求文件命名足够语义化,比如testing-setup.md比note-003.md好得多。
Claude Code 靠的是全量加载 + 上下文筛选。因为CLAUDE.md通常不会太大(几千行以内),Claude 可以全部读进来,然后在推理时自己筛选相关内容。这要求CLAUDE.md保持精简,不能无限膨胀。
如果记忆量确实很大,还有一个折中方案:用简单的全文搜索代替向量检索。比如用grep或者ripgrep在记忆目录里搜关键词,这比向量检索精确,比人工翻文件快。很多团队在记忆量超过几百条之后,都会加一层这样的搜索工具。
4. 实操过程与核心环节实现
4.1 从零搭建 Codex 记忆体系的完整步骤
假设你刚装好 Codex,想给它配一套持久化记忆。下面是我实际操作的步骤,你可以直接抄。
第一步:创建记忆目录结构
mkdir -p .codex/memory/{decisions,tasks/archive} touch .codex/memory/project.md touch .codex/memory/preferences.md touch .codex/memory/glossary.md touch .codex/memory/tasks/current.md第二步:填充初始记忆
project.md的内容模板:
# 项目概览 ## 技术栈 - 语言:TypeScript 5.x - 框架:Next.js 14 (App Router) - 包管理:pnpm - 测试:Vitest + Playwright ## 关键命令 - 开发:`pnpm dev` - 构建:`pnpm build` - 单元测试:`pnpm test:unit` - E2E 测试:`pnpm test:e2e` ## 目录说明 - `src/app/`:页面路由 - `src/components/`:可复用组件 - `src/lib/`:工具函数 - `prisma/`:数据库 schema 和 migrationpreferences.md的内容模板:
# 用户偏好 ## 代码风格 - 函数名:camelCase - 组件名:PascalCase - 常量:UPPER_SNAKE_CASE - 文件命名:kebab-case ## 注释 - 使用中文注释 - 复杂逻辑必须写注释 - 不要写显而易见的注释 ## 提交信息 - 格式:`type(scope): description` - type 可选值:feat, fix, refactor, docs, test, chore第三步:配置 Codex 的加载规则
在 Codex 的配置文件里,指定记忆目录的位置和加载策略。具体配置项名称可能随版本变化,但核心逻辑是告诉 Codex:启动时读project.md和preferences.md,任务相关时读tasks/current.md,需要历史决策时搜decisions/。
第四步:验证记忆生效
开一个新会话,问 Codex“这个项目怎么跑测试”。如果它回答pnpm test:unit,说明project.md加载成功。再问“函数名用什么命名风格”,如果回答 camelCase,说明preferences.md也生效了。
4.2 Claude Code 记忆配置的实操记录
Claude Code 的配置更简单,核心就是写好CLAUDE.md。
第一步:在项目根目录创建 CLAUDE.md
touch CLAUDE.md第二步:写入核心内容
我实际用的模板是这样的:
# 项目记忆 ## 命令 - 安装依赖:`pnpm install` - 开发服务器:`pnpm dev` - 类型检查:`pnpm typecheck` - 单元测试:`pnpm test:unit` - 代码格式化:`pnpm format` ## 规范 - 所有新组件必须写 Storybook story - API 路由必须做输入校验(用 zod) - 数据库查询必须走 prisma,不要写原生 SQL ## 陷阱 - 修改 `prisma/schema.prisma` 后必须跑 `pnpm db:migrate` - 环境变量在 `.env.local`,不要提交到 git - `src/generated/` 是自动生成的,不要手动改 ## 外部资源 - 设计稿:Figma 链接(内部) - API 文档:`docs/api.md` - 部署流程:`docs/deploy.md`第三步:配置分层记忆
在子目录里也放CLAUDE.md,比如src/components/CLAUDE.md:
# 组件开发规范 - 所有组件用函数式写法,不用 class - Props 类型用 interface 定义,不用 type - 样式用 Tailwind,不写 CSS 文件 - 每个组件必须有 default export这样 Claude 在处理组件相关任务时,会自动加载这个子目录的记忆。
第四步:验证分层加载
让 Claude 在src/components/目录下创建一个新组件。如果它自动用了函数式写法、interface 定义 Props、Tailwind 样式,说明子目录的CLAUDE.md生效了。
4.3 记忆冲突的处理策略
多层记忆难免会有冲突。比如项目根目录的CLAUDE.md说“用中文注释”,子目录的CLAUDE.md说“用英文注释”,Claude 该听谁的?
Claude Code 的规则是就近优先:越靠近当前工作目录的记忆优先级越高。所以子目录的“英文注释”会覆盖根目录的“中文注释”。
Codex 没有内置的冲突解决机制,需要你在文件命名和加载顺序上自己控制。我的做法是:在preferences.md里明确写优先级规则,比如“子目录的偏好文件优先于全局偏好文件”,然后让 Agent 按这个规则加载。
提示:记忆冲突是常态,不要试图消除所有冲突。更好的做法是建立清晰的优先级规则,让 Agent 知道冲突时该听谁的。
4.4 记忆的版本控制与回滚
记忆文件应该纳入 git 管理,这样每次记忆变更都有记录,出问题了可以回滚。
我的做法是在.gitignore里排除tasks/current.md(这个是临时状态,不需要版本控制),但保留project.md、preferences.md、decisions/和CLAUDE.md。
每次记忆变更单独提交,commit message 写清楚改了什么、为什么改。比如:
memory: 更新测试命令为 pnpm test:unit 之前的 `pnpm test` 已经废弃,现在拆成了 unit 和 e2e 两个命令。这样回溯的时候一目了然。
5. 常见问题与排查技巧实录
5.1 记忆不生效的排查清单
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| Agent 不读记忆文件 | 文件路径不对 | 检查记忆目录是否在 Agent 的搜索路径内 |
| 记忆内容被忽略 | 格式不对 | 确认是 Markdown 格式,标题层级正确 |
| 分层记忆冲突 | 优先级规则不清 | 检查就近优先规则是否生效 |
| 记忆太大导致超时 | 单文件过大 | 拆分文件,单文件控制在 500 行以内 |
| 记忆写入失败 | 权限问题 | 检查目录写权限 |
5.2 记忆膨胀的治理经验
用久了之后,记忆文件会越来越大。我见过一个项目的CLAUDE.md涨到 3000 多行,加载一次要好几秒,而且 Claude 经常忽略后面的内容。
治理记忆膨胀,我总结了三招:
第一招:定期归档。把超过 3 个月的decisions/移到decisions/archive/,主目录只留最近的。
第二招:合并同类项。如果preferences.md里有 20 条关于命名的规则,合并成 3-5 条概括性的规则。
第三招:删除过时内容。项目重构后,旧的目录说明、旧的命令都要删掉。留着不仅浪费 token,还会误导 Agent。
5.3 跨项目记忆共享的坑
Claude Code 的用户级CLAUDE.md(在~/.claude/CLAUDE.md)可以跨项目共享记忆,这个功能很好用,但有个坑:全局记忆会覆盖项目记忆。
比如你在全局CLAUDE.md里写了“所有项目都用 pnpm”,但某个项目实际用的是 npm,Claude 会优先听全局的,导致用错命令。
我的做法是:全局CLAUDE.md只放真正通用的内容,比如“注释用中文”、“提交信息用 conventional commits”。项目相关的配置一律放在项目级CLAUDE.md里。
5.4 记忆安全与敏感信息处理
记忆文件里很容易混入敏感信息,比如 API key、数据库密码、内部 URL。这些一旦写进记忆,就可能被 Agent 带到不该带的地方。
我的处理原则是:
- 绝不把密钥写进记忆文件。密钥放在
.env里,记忆文件只写“密钥在 .env 的 XXX 变量里” - 内部 URL 用占位符。比如写“API 文档在内部 Wiki”,不写具体链接
- 定期审查记忆文件。每次提交前扫一遍,确认没有敏感信息
注意:如果你用的是云端同步的记忆方案,敏感信息的风险更高。建议在写入前做一次脱敏检查。
5.5 记忆与上下文窗口的平衡
记忆不是越多越好。每次加载记忆都要消耗上下文窗口,加载太多会导致真正重要的代码上下文被挤掉。
我的经验值是:记忆内容控制在上下文窗口的 10%-15%。如果模型上下文是 200k token,记忆控制在 20k-30k token 以内。超过这个比例,就要考虑精简或分层加载了。
Codex 的按需加载在这方面有优势,因为它不需要一次性加载所有记忆。Claude Code 的全量加载则需要更严格地控制CLAUDE.md的大小。
6. 两条路线的选型建议与扩展思路
6.1 什么场景选 Codex 路线
Codex 的文件系统方案适合以下场景:
- 记忆量大:项目历史长,决策记录多,需要按需加载
- 多任务并行:同时处理多个任务,需要任务级隔离的记忆
- Agent 自主性高:希望 Agent 自己管理记忆,减少人工干预
如果你在做的是一个长期维护的大型项目,Codex 的路线更合适。
6.2 什么场景选 Claude Code 路线
Claude Code 的分层方案适合:
- 记忆量适中:项目规模中等,
CLAUDE.md能控制在合理大小 - 团队协作:多人共享项目记忆,需要集中管理
- 快速上手:不想折腾复杂的目录结构,一个文件搞定
如果你刚接触 Agent 记忆,或者项目规模不大,Claude Code 的路线更容易起步。
6.3 混合方案的可行性
实际上,这两条路线不是互斥的。我现在的做法是混合使用:
- 用
CLAUDE.md做项目级记忆,集中管理核心规范 - 用
.codex/memory/做任务级记忆,按需加载历史决策 - 两者通过
@import互相引用,避免重复
这样既有 Claude Code 的简洁,又有 Codex 的灵活。
6.4 后续扩展方向
如果你想把记忆体系做得更完善,可以考虑这几个方向:
方向一:记忆的自动摘要。定期用模型对记忆文件做摘要,把长文档压缩成短摘要,减少加载成本。
方向二:记忆的关联索引。在记忆文件之间建立双向链接,比如decisions/2024-01-15.md里引用project.md的相关章节,方便跳转。
方向三:记忆的时效性标记。给每条记忆加一个“有效期”,过期的自动归档。比如“当前 sprint 的目标”这种记忆,sprint 结束后就该归档。
方向四:记忆的冲突检测。写一个小工具,定期扫描记忆文件,检测相互矛盾的条目,提醒人工处理。
这些方向我自己也在逐步尝试,目前自动摘要和时效性标记已经用上了,效果不错。关联索引和冲突检测还在摸索阶段,等有成熟经验了再单独写一篇分享。
最后说一个我踩过的坑:不要一开始就追求完美的记忆体系。我最初花了两周设计了一套复杂的记忆结构,结果实际用起来发现大部分都用不上。后来改成“先用最简单的方案跑起来,遇到问题再迭代”,反而效率更高。记忆体系是长出来的,不是设计出来的。