1. 为什么 Agent 的"记忆"问题值得单独拎出来聊
做 Agent 开发的人迟早会撞上一堵墙:模型本身很聪明,但每次对话结束,它就把你忘得一干二净。你昨天告诉它"我们项目的 API 前缀统一用/v2",今天它照样给你生成/api。这不是模型不行,而是记忆层缺失。
市面上讲 Agent 记忆的方案,十篇有八篇会把你引向向量数据库:Pinecone、Weaviate、Milvus、Chroma,先做 embedding,再搞相似度检索,最后拼进 prompt。这条路没错,但它有个隐性成本——你得维护一套额外的存储服务,得处理 embedding 模型的版本漂移,得调 top-k 和相似度阈值,还得面对"检索出来的东西到底该不该信"的信任问题。对于个人开发者或者小团队来说,这套东西的运维负担经常比 Agent 本身还重。
而 Codex 和 Claude Code 这两个工具,走的是另一条路:不依赖向量库,用文件系统 + 结构化文本做持久化记忆。它们的思路很朴素——既然 Agent 运行在本地,那记忆就存本地文件,用人类可读的格式(Markdown、JSON、TOML),靠明确的规则去读写,而不是靠模糊的语义相似度。
这篇文章就把这两条路线拆开对比。我会讲清楚它们各自的记忆机制是怎么设计的、为什么这样设计、实际用起来有什么坑、以及你在自己的 Agent 项目里该怎么抄这套思路。不管你是刚接触 Agent 开发的新手,还是已经在用向量库但想找个更轻量方案的老手,这篇都能给你一些可以直接落地的东西。
2. 先搞清楚:Agent 记忆到底分几层
在对比具体工具之前,得先把"记忆"这个词拆解清楚。很多人一说 Agent 记忆就想到"把历史对话存起来",这只说对了一小部分。一个完整的 Agent 记忆体系,至少分四层,每层的生命周期、存储介质、读写频率都不一样。
2.1 工作记忆:当前会话的上下文窗口
工作记忆就是模型当前这次推理能看到的全部内容——系统提示词、历史对话、工具调用结果、当前用户输入。它的物理载体是模型的上下文窗口,生命周期就是这一次会话。会话一结束,工作记忆就蒸发了。
这一层的核心矛盾是窗口有限。你不可能把过去三个月的对话全塞进去,所以必须做取舍:哪些历史要保留、哪些要压缩、哪些要丢弃。向量库方案在这里做的是"检索相关历史片段",而 Codex 和 Claude Code 做的是"把关键信息提前写进文件,需要时再读回来"。
2.2 会话记忆:跨轮次但限于单次任务
会话记忆比工作记忆长一点,它覆盖的是"同一个任务从开始到结束"这个跨度。比如你让 Agent 重构一个模块,它可能来回几十轮,中间涉及读文件、改代码、跑测试。这些轮次之间的状态需要保持,但任务完成后就不需要了。
这一层通常用会话状态文件或者内存中的对话历史数组来实现。Claude Code 在这里有个很实用的设计:它会把当前任务的进度写进一个临时文件,即使进程崩了重启,也能从断点续上。
2.3 长期记忆:跨会话、跨任务的知识沉淀
这才是真正意义上的"持久化记忆"。它要解决的是:用户上周告诉过 Agent 的偏好、项目里约定俗成的规范、某个模块的历史决策原因——这些东西不该每次重新说一遍。
长期记忆的存储方案分两大流派:
| 流派 | 代表方案 | 检索方式 | 运维成本 |
|---|---|---|---|
| 向量检索派 | 向量库 + embedding | 语义相似度 | 高,需维护存储服务 |
| 结构化文件派 | Markdown/JSON 文件 | 明确规则 + 关键词 | 低,就是普通文件 |
Codex 和 Claude Code 都属于第二派。它们的长期记忆就是一堆放在特定目录下的文本文件,Agent 通过读文件来"回忆"。
2.4 程序记忆:技能与工具的定义
程序记忆指的是 Agent "会做什么"——它能调用哪些工具、每个工具怎么用、有哪些预设的技能模板。这部分通常不叫"记忆",但它在功能上就是记忆的一种:它决定了 Agent 的行为模式。
Claude Code 的 skill 机制、Codex 的 agent 配置文件,都属于这一层。它们用声明式的格式(Markdown 描述 + 参数定义)来告诉 Agent "遇到这类任务该怎么做"。
把这四层分清楚之后,你会发现 Codex 和 Claude Code 的"不用向量库"路线,主要是在长期记忆这一层做了取舍:放弃语义检索的灵活性,换取零运维成本和完全可解释性。这个取舍值不值,取决于你的场景。
3. Codex 的记忆路线:靠 AGENTS.md 和文件约定
Codex 的记忆机制,核心就一个词:约定优于配置。它不搞复杂的存储层,而是依赖一套文件命名和目录结构的约定,让 Agent 自己去读写。
3.1 AGENTS.md:项目级的记忆锚点
Codex 最核心的记忆载体是项目根目录下的AGENTS.md文件。这个文件的作用类似于给 Agent 的一份"项目说明书"——里面写清楚这个项目的技术栈、代码规范、目录结构、常用命令、注意事项。
它的工作逻辑很直接:Codex 启动时,会从当前工作目录向上逐级查找AGENTS.md,找到就加载进上下文。这意味着你可以做分层记忆:
- 项目根目录的
AGENTS.md写全局规范 - 子目录的
AGENTS.md写该模块的特殊约定 - 用户主目录的
AGENTS.md写个人偏好
这种分层设计的好处是,Agent 在不同目录下工作时,自动加载对应层级的记忆,不需要你手动切换上下文。我实测下来,这个机制对多模块项目特别友好——前端目录的 Agent 不会去读后端目录的规范,避免了上下文污染。
一个典型的AGENTS.md长这样:
# 项目约定 ## 技术栈 - 后端:Python 3.11 + FastAPI - 前端:React 18 + TypeScript - 数据库:PostgreSQL 15 ## 代码规范 - Python 用 ruff 格式化,行宽 100 - 提交信息用 conventional commits 格式 - 所有 API 路由必须以 /v2 开头 ## 常用命令 - 启动开发服务:`make dev` - 跑测试:`make test` - 数据库迁移:`alembic upgrade head` ## 注意事项 - 不要直接改 migrations 目录下的历史文件 - 环境变量统一从 .env 读取,不要硬编码这份文件本质上就是把人类脑子里的项目常识,翻译成 Agent 能读的格式。它不追求智能,追求的是明确。
3.2 会话状态文件:任务进度的断点续传
Codex 在处理长任务时,会把中间状态写进一个会话文件。这个文件通常放在项目的隐藏目录下(比如.codex/sessions/),记录当前任务的进度、已完成的步骤、待办事项。
这个设计的价值在于容错。Agent 跑长任务时,进程可能因为各种原因中断——网络抖动、token 超限、你手动 Ctrl+C。如果没有状态文件,重启后一切从头来;有了状态文件,Agent 能读回上次的进度,接着干。
我踩过的一个坑是:早期版本的 Codex 在会话文件损坏时不会报错,而是静默地从头开始,导致重复执行了已经完成的步骤。后来我养成了一个习惯——在关键任务开始前,手动备份一下会话目录。这个习惯救过我好几次。
3.3 为什么 Codex 不选向量库
这个问题值得单独说。Codex 的定位是本地优先的编码助手,它的用户大多是个人开发者,跑在自己的机器上。如果引入向量库,就意味着:
- 用户得额外装一个数据库服务(哪怕是 SQLite 版的向量扩展)
- 得处理 embedding 模型的下载和版本管理
- 得为不同项目维护不同的索引
- 检索结果不可解释——你不知道为什么这条记忆被召回了
而文件方案把这四个问题全绕开了。文件就在那里,你随时能打开看,随时能手动改,随时能删。可解释性和可控性,是 Codex 记忆设计的最高优先级。
代价是什么?代价是它没法做语义检索。你问"上次那个关于缓存的讨论",它没法通过语义相似度找到相关文件,只能靠文件名和关键词匹配。所以 Codex 的记忆使用,高度依赖你主动、规范地写文件。
4. Claude Code 的记忆路线:CLAUDE.md 加 skill 体系
Claude Code 的记忆机制和 Codex 有相似之处,但它在"程序记忆"这一层做得更细,引入了 skill 的概念。
4.1 CLAUDE.md:和 AGENTS.md 同源不同名
Claude Code 的项目记忆文件叫CLAUDE.md,放在项目根目录。它的加载逻辑和 Codex 的AGENTS.md几乎一样:启动时读取,注入上下文。
内容组织上,Claude Code 官方推荐的结构是:
# 项目概述 一句话说明这个项目是干什么的 # 技术栈 列出主要语言、框架、工具 # 目录结构 关键目录的用途说明 # 开发命令 构建、测试、部署的命令 # 代码风格 命名规范、格式化规则、注释要求 # 注意事项 容易踩坑的地方、禁止的操作和AGENTS.md相比,CLAUDE.md更强调给 Agent 的行为指令。比如你可以在里面写"修改代码后必须运行测试"、"不要自动提交 git",这些是行为约束,不只是信息告知。
4.2 skill 体系:把"怎么做"沉淀成可复用模块
Claude Code 的 skill 机制是它记忆路线里最有特色的部分。一个 skill 本质上是一个 Markdown 文件,放在.claude/skills/目录下,描述"遇到某类任务时该怎么做"。
举个例子,你可以写一个"数据库迁移"的 skill:
# Skill: 数据库迁移 ## 触发条件 当用户要求修改数据库 schema 时 ## 步骤 1. 先检查当前 migration 版本:`alembic current` 2. 生成新的 migration:`alembic revision --autogenerate -m "描述"` 3. 检查生成的 migration 文件,确认没有误删字段 4. 在测试库上跑一遍:`alembic upgrade head` 5. 确认无误后再应用到生产 ## 注意事项 - 永远不要修改已应用的 migration 文件 - 删除字段前必须确认没有代码引用 - 大表加索引要用 CONCURRENTLY这个 skill 一旦写好,Agent 在遇到数据库迁移任务时就会自动加载它,按照预设的步骤执行。这相当于把资深工程师的经验固化成了 Agent 的程序记忆。
skill 和向量库的区别在于:skill 是主动定义的,你知道它存在、知道它内容、知道它什么时候被触发;向量库是被动检索的,你只能控制写入什么,控制不了召回什么。对于流程性、规范性的知识,skill 这种明确的方式比向量检索靠谱得多。
4.3 会话记忆的落盘策略
Claude Code 在会话记忆的持久化上,比 Codex 更激进一些。它会把整个会话的对话历史、工具调用记录、文件修改记录都写进本地文件,放在~/.claude/目录下。
这个设计带来两个好处:
- 可回溯:你可以翻看任何一次历史会话的完整记录,知道 Agent 当时为什么做了某个决定
- 可恢复:会话中断后,能从文件里恢复上下文,继续之前的任务
但代价是磁盘占用增长快。如果你天天用,几个月下来这个目录能到几个 G。我的做法是定期清理超过 30 天的会话记录,保留最近的和标记为重要的。
4.4 Claude Code 为什么也绕开向量库
Claude Code 的定位和 Codex 类似,都是本地优先的编码工具。它绕开向量库的理由,除了 Codex 那几条之外,还多了一条:skill 体系本身就是一种结构化的知识组织方式。
当你把知识写成 skill 时,你其实已经完成了"知识分类"和"触发条件定义"这两件事。这两件事在向量库方案里,是靠 embedding 和相似度阈值来隐式完成的,效果不稳定。而 skill 是显式的,触发条件写清楚了,就不会误触发。
所以 Claude Code 的逻辑是:与其让机器猜什么时候该用什么知识,不如让人提前定义好。这个思路在知识边界清晰的场景下(比如编码规范、操作流程),比向量检索更可靠。
5. 两条路线的正面对比:从存储到检索的全链路
前面分别讲了 Codex 和 Claude Code 的记忆机制,现在把它们放在一起,从几个关键维度做对比。
5.1 存储格式与目录结构对比
| 维度 | Codex | Claude Code |
|---|---|---|
| 项目记忆文件 | AGENTS.md | CLAUDE.md |
| 分层加载 | 支持,逐级向上查找 | 支持,逐级向上查找 |
| 程序记忆 | agent 配置文件 | .claude/skills/下的 skill 文件 |
| 会话记录 | .codex/sessions/ | ~/.claude/下的会话文件 |
| 存储格式 | Markdown + JSON | Markdown + JSON |
| 是否依赖外部服务 | 否 | 否 |
两者的存储格式高度一致,都是纯文本。这意味着你可以用 git 管理这些记忆文件,可以 code review,可以回滚。这是向量库方案做不到的——你没法对 embedding 做 diff。
5.2 检索机制的差异
这是两条路线最本质的区别。
Codex 的检索是基于文件路径和关键词的。Agent 需要某条记忆时,它得知道去哪个文件找。这依赖你在AGENTS.md里写清楚"什么信息放在哪里"。比如你写"API 规范见docs/api-conventions.md",Agent 就知道该去读那个文件。
Claude Code 的检索多了一层skill 触发。skill 文件里写了触发条件,Agent 会根据当前任务判断该加载哪个 skill。这个判断是模型做的,比纯关键词匹配灵活,但仍然比向量检索可控——因为触发条件是明确定义的。
向量库的检索是基于语义相似度的。你不需要告诉 Agent 信息在哪,它自己算相似度找。这在信息量大、结构松散时是优势,但在信息量小、结构清晰时是过度设计。
5.3 写入时机与维护成本
记忆不是写完就完事的,它需要维护。两条路线在维护上的差异很大。
Codex 的记忆写入基本是手动的。你得自己判断哪些信息值得沉淀,然后手动写进AGENTS.md。好处是你完全掌控,坏处是容易忘、容易过时。
Claude Code 在写入上做了部分自动化。它会在会话结束时,提示你是否要把某些内容沉淀成 skill 或更新CLAUDE.md。这个提示机制降低了维护负担,但仍然需要你确认。
向量库方案的写入通常是自动的——对话内容自动 embedding 入库。但自动写入带来的是垃圾信息堆积,检索时经常召回无关内容。我见过不少项目,向量库里存了几万条对话,实际有用的不到十分之一。
5.4 适用场景的边界
说了这么多,到底什么时候该用文件方案,什么时候该上向量库?我的判断标准是:
用文件方案:
- 知识是结构化的、边界清晰的(规范、流程、配置)
- 知识量在几百条以内
- 你需要完全的可解释性和可控性
- 你不想维护额外服务
用向量库:
- 知识是海量的、非结构化的(大量文档、历史对话)
- 你需要模糊匹配和语义检索
- 你能接受一定的不可解释性
- 你有运维能力
对于大多数个人开发者和小团队的 Agent 项目,文件方案够用了。真正需要向量库的场景,往往是企业级的文档问答或者大规模知识管理。
6. 实操:把文件记忆方案落地到自己的 Agent
光看对比不够,得能上手。这一节我给出具体的落地步骤,你可以直接抄。
6.1 目录结构设计
先规划好记忆文件的存放位置。我推荐的目录结构:
project-root/ ├── AGENTS.md # 项目级记忆(Codex) ├── CLAUDE.md # 项目级记忆(Claude Code) ├── .agent/ │ ├── skills/ # 程序记忆:技能定义 │ │ ├── db-migration.md │ │ ├── api-design.md │ │ └── deploy.md │ ├── sessions/ # 会话记忆:任务进度 │ │ └── 2024-xx-xx-task.json │ └── knowledge/ # 长期记忆:沉淀的知识 │ ├── architecture.md │ ├── conventions.md │ └── troubleshooting.md这个结构的关键是分层:项目级记忆放根目录,Agent 专属记忆放.agent/下,再按类型分子目录。这样 Agent 加载时能按需读取,不会一次性把所有文件塞进上下文。
6.2 写一份高质量的 AGENTS.md
AGENTS.md的质量直接决定 Agent 的表现。我总结了几个写作要点:
第一,命令要具体到可复制。不要写"运行测试",要写"运行pytest tests/ -v"。Agent 不需要理解,它需要执行。
第二,规范要带例子。不要写"遵循 PEP8",要写"函数名用 snake_case,如get_user_by_id"。例子比规则更容易被模型理解。
第三,注意事项要写原因。不要写"不要改 migrations",要写"不要改 migrations,因为已应用的 migration 改动会导致数据库状态不一致"。带上原因,Agent 在边界情况下能自己判断。
第四,定期更新。项目变了,AGENTS.md也要变。我习惯在每个 sprint 结束时过一遍,把过时的内容删掉。
6.3 设计一个可复用的 skill
skill 的设计核心是触发条件要准,步骤要细。以"代码审查"为例:
# Skill: 代码审查 ## 触发条件 当用户要求审查代码、review PR、或检查代码质量时 ## 检查清单 1. 命名:变量、函数、类名是否表意清晰 2. 边界:数组访问、空值处理、异常捕获是否完整 3. 安全:是否有 SQL 注入、XSS、硬编码密钥 4. 性能:是否有 N+1 查询、不必要的循环、内存泄漏风险 5. 测试:新增逻辑是否有对应测试 ## 输出格式 按严重程度分级: - 阻塞:必须修复才能合并 - 建议:可以合并但建议改进 - 提示:仅供参考 ## 注意事项 - 不要只指出问题,要给出修改建议 - 不要对格式问题过度挑剔,交给 linter这个 skill 写好后,Agent 每次做代码审查都会按这个清单走,不会漏项。这就是程序记忆的价值——把专家的检查习惯固化下来。
6.4 会话状态的读写时机
会话状态文件什么时候写、什么时候读,有讲究。
写入时机:
- 完成一个可独立验证的步骤后(比如改完一个文件、跑完一次测试)
- 遇到需要用户决策的分叉点前
- 任务即将进入长耗时操作前
读取时机:
- 进程启动时,检查是否有未完成的会话
- 用户说"继续上次的任务"时
- 任务中断后重启时
不要每做一步就写一次,那样 IO 开销大且文件膨胀快。也不要只在最后写一次,那样中断就全丢了。按里程碑写是个平衡点。
6.5 记忆冲突的处理
当多个记忆文件对同一件事有不同描述时,Agent 会困惑。比如AGENTS.md说"用 4 空格缩进",子目录的AGENTS.md说"用 2 空格缩进"。
处理原则是就近优先:越靠近当前工作目录的记忆,优先级越高。这个规则要在项目级AGENTS.md里写清楚,让 Agent 知道冲突时怎么选。
另一个冲突来源是时间。旧记忆和新记忆矛盾时,应该以新的为准。所以我在记忆文件里会标注更新日期,Agent 加载时优先读日期新的。
7. 踩过的坑与实测经验
这一节讲几个我在实际使用中踩过的坑,都是文档里不会写的。
7.1 记忆文件过大导致上下文爆炸
我一开始图省事,把所有项目知识都塞进一个AGENTS.md,结果这个文件涨到了 8000 多字。Agent 每次启动都加载全文,直接吃掉一大块上下文窗口,导致真正干活的空间不够。
解决方案:拆分。AGENTS.md只放最核心的规范和索引,详细内容拆到.agent/knowledge/下的独立文件,AGENTS.md里只写"详细架构说明见.agent/knowledge/architecture.md"。Agent 需要时再去读。
这个改动的效果立竿见影——启动上下文从 8000 字降到 1500 字,留给任务的空间多了。
7.2 skill 触发条件写太宽导致误触发
我写过一个"性能优化"的 skill,触发条件写的是"当涉及性能问题时"。结果 Agent 在改任何代码时都觉得"这可能涉及性能",频繁加载这个 skill,干扰了正常任务。
解决方案:触发条件要具体到动作。改成"当用户明确要求优化性能、或代码中出现明显的性能反模式(如循环内查询数据库)时"。加上"明确"和"明显"这两个限定词后,误触发率大幅下降。
7.3 会话文件损坏导致任务重复执行
前面提过,早期 Codex 在会话文件损坏时会静默从头开始。我遇到过一次,Agent 把一个已经改好的文件又改了一遍,覆盖了我手动做的调整。
解决方案:
- 关键任务前备份会话目录
- 在
AGENTS.md里加一条"执行破坏性操作前,先检查目标文件是否已被修改" - 用 git 做兜底,每次 Agent 操作前先 commit
第三条最有效。git 是 Agent 记忆方案的最佳搭档——它提供了文件级别的版本控制,任何误操作都能回滚。
7.4 记忆更新滞后于代码变更
项目重构后,AGENTS.md里还写着旧的目录结构,Agent 按旧结构找文件,找不到就瞎猜。这种问题很隐蔽,因为 Agent 不会报错,它会用错误的信息继续干活。
解决方案:把记忆文件的更新纳入代码审查流程。每次 PR 如果涉及目录结构、命令、规范的变更,必须同步更新AGENTS.md。我在 CI 里加了一个检查:如果src/下的目录结构变了但AGENTS.md没变,就警告。
7.5 多 Agent 共享记忆的冲突
如果你同时用 Codex 和 Claude Code,它们各自读自己的记忆文件(AGENTS.md和CLAUDE.md),容易出现两边不一致的情况。
解决方案:用一个共享的知识目录(比如.agent/knowledge/),两个工具的入口文件都指向它。AGENTS.md和CLAUDE.md只保留各自特有的配置,通用知识放共享目录。这样改一处,两边都生效。
8. 什么时候该考虑引入向量库
虽然这篇文章讲的是不用向量库的路线,但我得客观说一句:文件方案不是万能的。以下几种情况,你该认真考虑向量库。
知识量超过人工维护的极限。当你有一千份以上的文档需要 Agent 检索时,靠文件名和关键词已经找不过来了,这时候语义检索的价值就体现出来了。
知识是非结构化的。比如你要让 Agent 回答"我们过去半年在客户支持中遇到的所有关于支付的问题",这种查询没法用文件路径表达,只能靠语义检索。
知识更新频繁且来源分散。如果知识来自多个系统、频繁变动,手动维护文件不现实,自动 embedding 入库更合适。
需要跨语言检索。向量检索天然支持跨语言,中文查询能召回英文文档。文件方案做不到这点。
但即便引入向量库,我也建议混合使用:结构化的规范、流程、配置仍然用文件,非结构化的文档、历史记录用向量库。两者不是替代关系,是互补关系。
9. 我个人的选型建议
最后说说我自己的做法。我在个人项目里用的是纯文件方案,理由很简单:项目规模不大,知识量在可控范围内,我更需要的是确定性而不是智能性。Agent 每次读到的记忆都是我明确写进去的,不会有意外的召回,调试起来心里有底。
在团队项目里,我用的是混合方案:AGENTS.md管规范和命令,向量库管历史决策和文档检索。规范类的东西必须确定,历史类的东西可以模糊。
如果你刚开始做 Agent 开发,我的建议是先从文件方案起步。它简单、透明、零依赖,能让你快速跑通整个记忆流程。等你真正遇到文件方案解决不了的问题时,再引入向量库也不迟。过早引入复杂方案,往往是在解决你还没有的问题。
一个具体的起步动作:今天就在你的项目根目录建一个AGENTS.md,写下三条最重要的项目约定,然后让 Agent 读它。你会发现,就这么一个简单的文件,Agent 的表现就会有明显提升。记忆这件事,很多时候不是技术不够先进,而是该写的没写。