先把直觉放慢一点。很多人听到 agent memory,会马上想到“更长的聊天记录”:
旧 thread 越多,下一次 prompt 里塞得越多。Codex 的实现没有走这条路。
官方文档把 Memories 描述成 useful context from earlier threads,源码里也把它拆成
memories/read 和 memories/write 两条路径:
一条负责把已有记忆带进当前线程,另一条在后台把历史 rollout 提炼成以后可用的本地材料。
Codex 会在后台从 eligible rollout 中抽取高价值经验,先写入 state DB,再合并到
~/.codex/memories/ 下的文件化 workspace;未来 turn 读取这些文件时,
还会记录引用,并在配置启用隔离时跳过受外部资料影响的 thread。它能减少重复说明,但不替代规则文件、
rollout 恢复记录或上下文窗口。
阅读目标。
本篇只追一条生命周期:哪些线程可以生成记忆,后台怎样抽取和合并,
记忆怎样进入未来 turn,以及它和 AGENTS.md、rollout、Chronicle 的区别。
读完以后,应该能判断一条信息应该写进规则文件、留在 rollout,还是适合存成 memory。
取材范围。 源码快照:5c5308fc9a9e。
产品行为来自 OpenAI Codex Memories、AGENTS.md 和 Chronicle 官方文档;
feature switch、配置、读写 pipeline、citation 和污染隔离来自
openai/codex 公开源码。本文不读取或引用任何本机
~/.codex/memories 内容,也不推断私有服务端策略。
一、先拆开“会记住”这句话
Codex 里有好几种东西都会让人感觉“它记得”。第二篇讲过的
TurnContext 是本轮运行快照;第十篇讲过的 rollout 是持久证据;
AGENTS.md 是进入任务前就该读到的规则;Memories 则是另一层:
它从过往工作里提炼可复用经验,下一次需要时再检索。
| 看起来都像“记忆”的东西 | 负责实现 | 生命周期 | 适合放什么 |
|---|---|---|---|
| 当前上下文 | TurnContext 和 model-visible view。 |
一次 turn 内生效,可压缩,也可转换成客户端状态。 | 本轮必须看到的输入、工具结果、环境和指令。 |
| 项目规则 | AGENTS.md / checked-in docs。 |
每次进入仓库时读取,靠文件维护。 | 必须稳定执行的团队约定、命令、权限和交付规则。 |
| 运行证据 | rollout / state DB。 | thread 执行后留下,供 resume、fork、审计和恢复使用。 | 曾经发生过什么、工具输出是什么、回滚点在哪里。 |
| Memories | codex-memories-read / codex-memories-write。 |
后台从旧 thread 生成,本地文件化,未来 turn 选择性读取。 | 稳定偏好、重复工作流、项目习惯、已知坑和高杠杆步骤。 |
| Chronicle | Codex app 的 opt-in research preview。 | 用屏幕上下文辅助构建 memory。 | 最近正在看的工具、文档、网页或工作流线索。 |
这个表先给出结论:需要强制执行的规则不要靠 memory;需要精确重放的历史不要靠 memory; memory 的价值在于减少重复说明,让未来 agent 更快找到“这类任务通常该怎么做”。
二、开关先决定有没有 memory
官方文档第一句就把预期压住:Memories 默认关闭。源码里的 feature spec 也对应这一点:
Feature::MemoryTool 的 key 是 memories,
stage 是 Stable,default_enabled 为 false。
所以配置要分两层看:外层 feature flag 决定有没有 Memories;内层
[memories] 决定生成、使用、外部上下文隔离和模型选择。
[features]
memories = true
[memories]
version = "v1"
dual_write = false
generate_memories = true
use_memories = true
disable_on_external_context = true
这三个 memory 子开关容易混淆。generate_memories 影响新 thread
是否能作为未来生成输入;use_memories 影响当前线程是否注入已有 memory
读取指令;disable_on_external_context 则用于把用了 web search、tool search、
或会污染 memory 的 MCP 调用的 thread 排除出生成路径。TUI 里的
/memories 也按这个顺序工作:功能没开时先弹启用提示;功能已开时再展示
use / generate 的 thread-level 设置。
| 开关 | 控制什么 | 读源码时的落点 |
|---|---|---|
features.memories |
是否启用 Memories 功能。 | Feature::MemoryTool,默认关闭。 |
memories.generate_memories |
新 thread 是否可作为 memory 生成输入。 | thread 创建时写入 memory_mode。 |
memories.use_memories |
当前线程是否使用已有 memory。 | read extension 是否注入 developer instruction。 |
memories.disable_on_external_context |
外部上下文参与后是否标记污染。 | web/tool search 和 MCP 路径可把 thread 置为 polluted。 |
三、写入为什么要放到后台
如果每条回复结束后立刻总结,任务未完成、用户纠正和临时失败都会增加噪声。当前 app-server 只有在 turn/start 含非空用户输入、core 确实启动新 turn、且主环境已配置时,才尝试 start_memories_startup_task。补充已有 turn 或提交独立工具输出不会走这个触发分支。后台函数再跳过 ephemeral、feature 关闭和非 root agent,并确认 memory store 可用。
后台默认只跑所选版本;dual_write = true 才分别启动 v1 与 v2 pipeline。每条 pipeline 先准备自己的 root 和 extension instructions,再 prune、检查额度,依次执行 Phase 1 和 Phase 2。版本目录 分别是 memories/ 与 memories_v2/;默认仍为 v1。双写不等于把两份摘要一起注入:读取仍由 version 选择一个命名空间。示例显式开启的 disable_on_external_context 也不是默认值;源码默认是 false。
turn/start: nonempty user input + new turn + primary environment ready
-> start_memories_startup_task
-> skip checks: ephemeral / feature off / sub-agent / no state DB
-> selected version (or both when dual_write)
-> create version-specific memories root
-> prune
-> rate-limit guard
-> Phase 1 extraction
-> Phase 2 consolidation
这也是 memory 文章要放在 rollout 之后讲的原因。写入路径不是从可见 UI 消息开始, 而是从 state DB 里挑 eligible rollout;不了解第十篇的 rollout 记录,读这一段很容易把 memory 误读成普通聊天摘要。
3.1 一条可复用经验怎样移动
先看一条具体经验:某个仓库的文章改动通常要中英文同步,最后还要做移动端视觉检查。 如果它真的反复出现,memory writer 不会把整段旧对话塞进未来上下文,而是让这条经验穿过几层收缩。
| 阶段 | 这时的单元 | 谁能看或改 | 产出 |
|---|---|---|---|
| 候选 rollout | 旧 thread 里的用户要求、文件 diff、验证结果。 | state DB 按 idle、age、memory mode 选择。 | 一条可被 Phase 1 claim 的 job。 |
| Phase 1 | 从旧证据里抽出的 reusable lesson。 | 后台 writer 只把 rollout 当数据读。 | raw_memory、rollout_summary、rollout_slug。 |
| Phase 2 | 多条 raw memory 合并后的 workspace patch。 | 内部 consolidation agent 维护对应版本 workspace;实际隔离取决于父级权限配置。 | memory_summary.md、MEMORY.md、summary 文件或 skill。 |
| 未来读取 | 当前任务命中的 memory 条目。 | 前台 agent 按 read-path prompt 少量打开。 | 回答末尾隐藏 citation,usage 回写 state DB。 |
四、Phase 1:先把 rollout 变成 raw memory
Phase 1 从最近、空闲足够久、允许生成 memory 的 thread 中抽取结构化经验。候选查询 排除当前 thread 和 memory_mode != enabled,限制 age、idle、来源以及 scan / claim 数量;不再要求 history_mode = legacy,分页历史也可参与。以下示例和文件表先沿默认 v1 路线解释。v2 使用单独的分层输入序列化与抽取模板,不能把两条路径的记录形状混为一谈。
模板里还有一个很重要的判断:如果一条 rollout 没有可复用洞察, memory writer 可以返回全空字段。高价值记忆通常是稳定用户偏好、高杠杆步骤、 任务地图、决策触发器和可靠环境事实;泛泛的“跑过测试”“看过日志”不该占用未来注意力。
{
"raw_memory": "修改这个仓库的文章 flow 时,要同步更新 zh/en 页面,并在发布前检查移动端视觉效果。",
"rollout_summary": "一次 Codex 文章改稿在同时检查双语页面和窄屏布局之后,才把叙述顺序调顺。",
"rollout_slug": "bilingual-article-visual-check"
}
这里的关键词是 evidence-based。Phase 1 prompt 明确要求把 rollout 和工具输出当作数据而不是指令,不能保存 secrets,不能复制大段输出,宁可 no-op。 对未来 agent 有帮助,才值得进入下一步。
五、Phase 2:把零散记忆合并成本地 workspace
Phase 2 负责把 stage-1 outputs 变成文件化 memory workspace。
README 里列出的动作很有工程味:先拿全局 lock,再按 usage_count、
last_usage / generated_at 和 max_unused_days
选择输入;把 raw_memories.md 和 rollout_summaries/
同步到 ~/.codex/memories/;用 git baseline 记录 workspace diff;
如果没有变化,就直接结束。
有变化时,Codex 启动内部 consolidation agent,关闭审批、memory 再生成及协作,避免递归提炼或委派。它的权限继承有条件分支:父级使用 Codex 管理的权限时,worker 限于 memory root 写入且禁用网络;父级为 Disabled 或 External 时保留该配置。因此“任何情况下都只有本地 memory 目录写权限”并不是源码保证。执行结束还要通过 artifact 校验并确认仍持有 lock,才接纳这次合并结果。
| 文件或目录 | 用途 | 读取方式 |
|---|---|---|
memory_summary.md |
高密度导航摘要。 | read path 会把摘要放进指令区域,先用于快速判断。 |
MEMORY.md |
可 grep 的 handbook。 | 按关键词查,找到相关条目后再深入。 |
rollout_summaries/ |
每条 rollout 的压缩证据和经验。 | 只有被 MEMORY.md 指到时才打开少量文件。 |
skills/ |
可复用流程或脚本。 | 作为 progressive disclosure 的下一层。 |
raw_memories.md |
Phase 1 的合并输入。 | 主要给 Phase 2 consolidation 使用。 |
六、读取路径:memory 怎样回到未来 turn
读取 extension 仅在 Feature::MemoryTool 和 memories.use_memories 都开启时注入规则,并从所选版本读取非空 memory_summary.md。配置更新 会保留该 thread 初始选定的版本,避免摘要和检索工具读取不同目录;只有 dedicated_tools 同时开启时才暴露专用 memory 工具。
这段 read-path prompt 并不是“看到 memory 就全读”。它先要求判断任务是否需要 memory;
需要时做 quick memory pass:先看 memory_summary.md,再 grep
MEMORY.md,只有命中直接指向 rollout summary 或 skill 时,才打开一两个具体文件。
这个设计和 skill 的 progressive disclosure 很像:先给导航,再按需展开证据。
future user query
-> memory_summary.md hints
-> grep MEMORY.md
-> open 1-2 rollout_summaries or skills
-> answer with hidden memory citation
-> record usage for future selection
还有一个容易忽略的机制:memory citation。read-path prompt 要求如果使用了 memory,
最终回复末尾要带一个隐藏的 <oai-mem-citation> block。
MemoryCitation 结构保存 entries 和 rollout ids;流式输出处理会剥掉隐藏标记,
解析 citation,并把相关 stage-1 输出的 usage 记回 state DB。用户看到的是干净答案,
runtime 得到的是“哪条记忆真的被用上”的反馈。
v2 的 读取模板 更强调直接利用已注入摘要:仅当额外证据、时间顺序或不确定性会改变答案时,再读对应 rollout summary;缺少必要线索才搜索。它要求核实可能变化的事实,不把 memory 当作当前行为的证明。只有实际读取的 summary 参与回答时才附 citation,不引用摘要本身;用户明确要求记住、忘记或纠正时,写入 extensions/ad_hoc/notes/,由后续 consolidation 应用,不能直接改生成文件。
七、为什么要有 polluted
memory 最大的风险不是“忘了”,而是把来源不稳定的东西记得太牢。
如果一条 thread 的答案依赖 web search、tool search 或某个会污染 memory 的 MCP server,
这些外部材料可能很快过期,也可能只适合当次使用。Codex 提供了
disable_on_external_context:配置打开后,相关 response item 或 MCP tool call
会把 thread 标记成 polluted。
污染标记还影响已经合并过的材料。mark_thread_memory_mode_polluted 在该 thread 曾参与最近一次成功 Phase 2 baseline 时,会安排新一轮合并;后续选择会排除这个来源。这样既阻止新抽取,也让已有 workspace 有机会移除失效依据。标记和排队不等于文件已经删除,忘记动作仍要等后台合并成功。
还是用刚才的例子对比:“这个仓库的文章要中英文同步” 是稳定工作流, 值得候选;“今天某个网页上显示的最新价格是 19.99 美元” 是一次外部查询事实,过期概率很高。前者可以进入 memory 候选,后者更适合留在当前 turn 或引用来源,不适合自动保存成长期偏好。
这条规则很适合迁移到自己的 agent 设计里: 临时外部事实可以进入当前答案,但不应该默认进入长期偏好和工作流记忆。 真要保存,也应该先确认来源、时效,并让用户明确同意。
八、最后把三类长期材料放回各自的位置
到这里,Codex 的 memory 就不神秘了。它不替代上下文管理,也不替代 AGENTS.md; 它站在 rollout 后面,把旧线程里真正能减少未来重复沟通的东西抽出来,再通过 read path 带回未来任务。放错位置时,问题也很明显:规则放进 memory 会变得不可靠; 短期事实放进 memory 会过期;恢复证据只靠 memory 又丢失精度。
| 信息类型 | 应该放哪里 | 为什么 |
|---|---|---|
| 团队要求、提交流程、必须执行的验证。 | AGENTS.md 或 checked-in docs。 |
需要每次稳定加载,不能依赖后台抽取是否命中。 |
| 某条 thread 中到底发生过什么。 | rollout / state DB。 | 恢复、fork、审计需要可重放证据。 |
| 重复出现的偏好、工作流和已知坑。 | Memories。 | 它们能减少未来重复说明,但允许被验证、更新和淘汰。 |
| 正在屏幕上看的临时材料。 | Chronicle 或当前 turn 的具体工具读取。 | 它适合帮助定位上下文,不适合未经处理直接长期化。 |
这也给 Codex 系列补上了最后一块长期状态拼图:第二篇讲“本轮看见什么”, 第十篇讲“旧 thread 怎样重放”,第十二篇讲“哪些旧经验值得带到新 thread”。 这三件事相互连接,但由不同模块负责。把职责分清楚,才不会把 agent memory 想成一个无限增长的 prompt,也不会把需要强约束的规则交给概率性的召回层。
参考源码
- OpenAI Codex Memories 官方文档:默认关闭、配置、存储和 per-thread 控制
- OpenAI Codex AGENTS.md 官方文档:规则文件的发现和层级
- OpenAI Codex Chronicle 官方文档:用屏幕上下文辅助 memory 的 opt-in preview
Feature::MemoryToolfeature spec 与默认关闭MemoriesToml/MemoriesConfig配置项和默认值- TUI
/memories入口和启用提示 memories/read与memories/writecrate 分工- 后台 memory startup task 的跳过条件、rate-limit guard 与 Phase 1 / Phase 2 顺序
- app-server 在 turn 提交后触发 memory startup task
- Phase 1 startup claim 的候选选择说明
- Phase 1 选择 enabled thread,包括分页历史
- Phase 1 prompt:从 rollout 转成 raw memory / rollout summary,且允许 no-op
- Phase 1 prompt:高价值 memory 的判断标准
- Phase 2 consolidation 的输入选择、文件同步、git baseline 和内部 agent
- Phase 2 memory folder 结构:summary、MEMORY、raw、skills、rollout_summaries
- read extension 根据 feature 和
use_memories注入 developer policy - read path prompt:什么时候使用 memory、memory layout 和 quick pass
- read path prompt:memory citation 和 ad-hoc update 规则
MemoryCitation/MemoryCitationEntry协议结构- memory citation parser 解析 citation entries 和 rollout ids
- 流式输出剥离隐藏 citation 并解析 memory citation
- response item 完成后记录 memory citation usage,并标记外部上下文污染
- MCP tool call 根据 server metadata 标记 thread memory pollution
mark_thread_memory_mode_polluted将 thread 置为polluted