二、Codex Memories 源码解析:记忆的生成、整理与存储

第一章回答了什么信息值得长期保存。接下来,我们沿着一段历史任务的处理过程,看看 Codex 如何把它转化为能够在未来使用的记忆。

Codex 不会在任务结束时直接改写长期记忆。后台管线启动后,系统先从历史记录中找出符合条件的任务,再逐个提取其中值得保留的信息。提取结果不会立即写入最终文件,而是先进入状态数据库,等待全局整理。

整理阶段会把多项候选记忆放在一起,处理重复、冲突和已经发生变化的信息,最后更新本地记忆文件(Memory)。单项提取彼此独立,可以有限并发;全局整理会修改同一组文件,因此同一时刻只能有一个整合任务执行。状态数据库负责保存候选结果和管线进度,本地 Memory 目录负责保存整理后的长期产物。

css 复制代码
flowchart TD
    A["主任务收到新的用户输入"] --> B["启动后台记忆管线"]
    B --> C["筛选符合条件的历史任务线程(thread)"]
    C --> D["读取对应的任务运行记录(rollout)"]
    D --> E["并发提取候选记忆(Phase 1)"]
    E --> F["将候选结果写入状态数据库"]
    F --> G["选择本轮需要整理的候选记忆"]
    G --> H["生成全局整理所需的输入文件"]
    H --> I["整合 Agent 统一整理记忆(Phase 2)"]
    I --> J["更新本地长期记忆文件"]

一、后台管线的启动与历史任务筛选

Memory 生成不是等当前任务关闭以后才进行的一次总结。根任务提交一轮包含用户输入的消息后,Codex 会尝试在后台启动记忆管线。当前回答继续生成,记忆处理不会阻塞这次交互。

这条管线并非每次都能启动。Memory 生成功能必须处于启用状态,当前任务不能是临时任务或子 Agent,并且系统需要能够访问状态数据库。当前正在执行的根任务只负责唤醒后台工作,它本身会被排除在本轮候选之外。因此,用户刚刚输入的内容不会在同一轮里立刻成为长期记忆。

/memories 提供了两个相互独立的控制项。「生成记忆」决定任务能否在以后参与记忆提取,最终记录为任务的记忆模式(memory_mode);「使用记忆」决定新任务是否读取已经存在的 Memory。关闭使用不会停止后台生成,关闭生成也不会删除已有的长期记忆。

后台管线在发起模型请求以前还会检查 Codex 的 rate limit。剩余额度低于配置门槛时,本轮提取和整理都会停止。清理过期记录不需要调用模型,可以先执行。若额度查询失败,管线仍会继续运行,避免一次监测故障长期阻断记忆生成。

thread 与 rollout 分别记录什么

系统扫描历史任务时会遇到两个容易混淆的概念。

任务线程(thread)表示一项逻辑上的 Codex 任务。它有稳定标识,并关联更新时间、工作目录、来源、模型和当前状态等信息。用户在界面中看到的一项任务,通常对应一个 thread。

任务运行记录(rollout)是该 thread 在磁盘上持续追加的执行记录。它包含用户消息、Agent 回复、工具调用与结果、会话元数据和执行事件,更接近一份按时间写入的运行日志,而不是整理好的对话稿。后续的记忆提取 Agent 真正读取的是 rollout。

状态数据库保存 thread 的元数据以及 rollout 的文件位置,但不复制完整运行记录。原始 rollout 仍保存在会话存储目录,也不属于 ~/.codex/memories/。这形成了第一道边界:rollout 保存历史证据,状态数据库负责寻找和调度任务,本地 Memory 文件保存整理后的长期知识。

哪些历史任务会进入提取流程

后台管线不会读取全部历史任务。只有同时满足以下条件的任务,才会进入记忆提取流程:

  • 由用户直接发起,尚未归档且不是空任务。
  • 记忆模式处于启用状态。
  • 不是当前任务,且已达到最短空闲时间,未超过最长回溯期限。
  • 尚未完成过提取,或在上次成功提取后产生了新内容。

记忆模式也可能在任务执行过程中改变。如果启用了外部上下文保护,使用 Web Search、工具搜索或特定外部工具的任务会被标记为「受外部上下文影响(polluted)」,从而失去记忆提取资格。这个标记只表示任务混入了外部来源,并不表示其中的内容错误。

通过上述筛选后,符合条件的历史 rollout 才会交给记忆提取 Agent。

二、单任务记忆的提取与候选结果入库

单任务记忆提取(Phase 1)每次分析一份 rollout,并为这项历史任务生成候选记忆。不同 rollout 之间互不依赖,因此可以同时提取。具体的并发限制和重复处理保护,将在后文的后台运行保障中统一说明。

提取 Agent 会看到哪些内容

rollout 保存了一项任务的完整运行记录,但记忆提取 Agent 只会看到其中与任务过程有关的部分。

系统会保留用户真正输入的任务内容、Agent 对任务的回复、工具调用与结果,以及 Agent 之间的通信。这些记录可以还原任务目标、执行过程和实际结果。

由 Codex 运行环境注入的开发者指令(developer message)不会进入提取请求。这类消息用于规定 Agent 应当怎样工作,并不是当前任务产生的事实或用户偏好。模型推理、上下文压缩记录和内部运行信息也会被排除。AGENTS.md 与 Skill 说明即使出现在用户上下文中,也会被单独删除,因为它们是系统已有的规则,不是当前任务产生的新信息。

工具结果仍会保留。测试结果、命令反馈和错误信息可以证明任务实际发生了什么;其中夹带的外部指令只能作为数据处理,不能被当成用户要求。

发送给模型以前,系统会遮盖记录中的密钥、令牌等秘密信息。rollout 过长时,内容还会根据模型的上下文容量进行裁剪,同时保留记录的开头和结尾。

这些程序规则只限定记忆提取 Agent 能够参考哪些历史记录。记录中的内容是否值得成为长期记忆,仍由提取 Agent 判断。

一次提取会产生什么

记忆提取 Agent 根据处理后的 rollout 生成 3 项结构化结果。

  • 候选记忆正文(raw_memory)记录从这项任务中提炼出的偏好、事实、操作经验和判断依据,等待全局整理。
  • 任务运行摘要(rollout_summary)概括任务目标、实际结果、验证证据和可复用结论。以后需要核对细节时,可以先读取这份摘要,不必重新打开完整 rollout。
  • 任务摘要文件标识(rollout_slug)为摘要文件提供简短的主题名称。存储层会把它与时间和短哈希组合成文件名,方便读者从文件名辨认任务内容;它本身不保存记忆。

如果这份 rollout 没有达到记忆提取门槛,Agent 可以把 3 项结果都留空。程序会检查候选记忆正文和任务运行摘要,其中任何一项为空,这次作业就记为「成功但无输出(succeeded no output)」,不会保存空的候选记忆。任务摘要文件标识不参与有效性判断;它缺失时,存储层会生成备用文件名。

模型完成提取后,系统会再次遮盖 3 项输出中的秘密信息。这样做不能证明模型从未接触敏感内容,却能降低秘密被写进持久化候选结果和本地文件的风险。

状态数据库保存的不是最终记忆

有效结果不会直接进入 MEMORY.md,而是先写入状态数据库中的候选结果表(stage1_outputs)。每个 thread 保存一份当前有效的提取结果,同时记录来源更新时间和生成时间。

这里的「候选」表示内容尚未经过跨任务整理,并不意味着数据库是一块随时可以丢弃的临时缓存。状态数据库是持久化的中间层。除了候选正文和任务摘要,它还记录使用次数(usage_count)、最近使用时间(last_usage)、作业状态、租约、重试次数、最近错误以及处理水位。

这些状态让后台工作能够跨越应用重启继续运行。worker 中途失去响应后,租约到期可以由其他 worker 接管;一次提取失败也不会被误记为完成。如果同一个 thread 过去存在候选结果,而最新提取变成空结果,旧结果会被删除,并触发后续的全局整理。

经过 Phase 1,一份 rollout 已经转化成数据库中的候选记忆,但它还不是未来任务会直接读取的长期记忆。

三、候选记忆的选择与整理输入生成

状态数据库可能保存多项尚待整理的候选记忆。Codex 不会把它们全部交给整合 Agent,而是先确定本轮真正需要处理的内容。

系统会排除长期未使用或来源过旧的记录,再按照使用次数、最近使用或更新的时间以及来源的新近程度排序,只选择配置允许的前 N 项。使用次数更高的候选会获得更高优先级,因此 Memory 是否在后续任务中被使用,确实会影响它以后参与全局整理的机会。

如果一项任务此前已经参与过全局整理,后来又被标记为「受外部上下文影响」,系统会安排新一轮整理。下一轮选择会排除这项任务,再由整合 Agent 根据输入变化更新长期记忆文件。

数据库中的候选记忆是持久化状态。经过规则筛选后得到的「本轮需要整理的候选记忆」,只表示一次全局整理的输入范围。

选择顺序与文件写入顺序也不相同。系统先按相关字段确定哪些候选入选,再按照稳定的 thread 标识顺序生成文件。即使使用次数发生变化,只要入选内容没有改变,文件也不会因为排序波动而反复重排。

本轮选中的内容会被同步成两类输入文件:

  • 原始候选记忆合集(raw_memories.md)汇总入选记录的 raw_memory,供整合 Agent 查看本轮有哪些内容需要处理。
  • 任务运行摘要目录(rollout_summaries/)为每项入选任务保存一份 rollout_summary,并附带 thread 标识、原始 rollout 路径、工作目录、更新时间和 Git 分支等回溯信息。

这两类文件不是最终长期记忆。它们只是把数据库中的结构化结果转换成整合 Agent 能够读取和比较的本地输入。某项候选不再入选时,对应摘要会从目录中移除,raw_memories.md 也会重新生成。新增、修改和删除都会成为下一阶段需要处理的变化。

四、全局记忆的整合与本地文件更新

全局记忆整合(Phase 2)同时查看多项候选结果。整合 Agent 需要识别重复内容,处理相互冲突或已经变化的事实,并把仍然有效的信息组织进同一套本地文件。

为什么这一阶段只能有一个写入者

Phase 1 可以并发,因为不同请求处理不同的 rollout,结果写入不同的数据库记录。Phase 2 修改的是同一个 Memory 目录。如果两个整合 Agent 同时更新 MEMORY.md,其中一个可能覆盖另一个刚完成的合并、删除或冲突处理。

因此,系统会在读取本轮候选和修改目录以前领取全局租约。同一时刻只有一个整合任务能够取得写入权。运行时间较长时,整合任务会通过心跳续租;一旦失去所有权,它就不能把自己的结果登记为成功。

整合 Agent 可以读取候选记忆、任务摘要和已有 Memory,并在 Memory 根目录内更新产物。它不能修改原始 rollout,也不会把自己的内部任务再次送进记忆生成流程。实现还关闭了 Memory、协作 Agent、Apps、Plugins 和 MCP 等能力,减少递归调用及外部状态进入整理过程的机会。

在 Codex 管理的沙箱中,整合 Agent 只能写入 Memory 根目录,并且不能访问网络。如果父任务明确使用外部权限配置,或者关闭了 Codex 管理的沙箱,实现会保留父任务的选择。因此,「整合 Agent 在任何运行模式下都绝对不能联网」并不成立。

Git 基线如何支持增量整理

状态数据库能够判断候选来源是否更新,却不能仅凭时间水位判断本地输入文件是否真的发生了变化。Codex 因此在 Memory 目录中维护一份内部 Git 基线,记录上一次成功整合后的文件状态。

系统取得全局租约后,先把本轮候选同步到 raw_memories.mdrollout_summaries/,再与 Git 基线比较。若文件没有变化且现有核心产物仍然有效,本轮可以直接完成,不调用整合模型。若出现新增、修改或删除,系统会把有大小上限的差异写入临时文件 phase2_workspace_diff.md,让整合 Agent 先了解这次发生了什么变化。

数据库水位和 Git 基线解决不同问题。水位负责记录后台管线处理到了哪个来源版本,Git 基线负责判断本地整理输入发生了哪些实际变化。候选来源时间发生变化时,生成的内容可能完全相同;某项候选被撤回时,Git 差异又能直接呈现相应文件的删除。

整合 Agent 退出后,系统还要检查 MEMORY.md 是否存在,并确认 memory_summary.md 符合规定格式。只有产物有效、Agent 已经关闭,而且全局租约仍属于当前 worker,系统才会更新 Git 基线和数据库中的完成状态。失败运行不会推进成功基线,后续重试仍能看到未处理的变化。

模型调用和失败如何受到控制

Codex 没有为 Memory 维护一笔精确的货币预算,它主要限制模型调用数量和单次输入规模。

Phase 1 每轮只领取有限数量的 rollout。一条后台管线最多同时执行 8 个提取请求,状态数据库还会限制所有后台管线正在执行的提取任务总数。前者限制一次启动产生的并发,后者防止多次启动叠加后突破全局上限。rollout 过长时,系统最多使用提取模型有效输入窗口的 70%,为系统指令和模型输出预留空间。

Phase 2 只整理有限数量的候选记忆。长期未使用的记录会失去资格,本地输入文件没有变化时则跳过整合模型。两个阶段也可以分别选择不同模型和推理强度。

为了避免重复提取,系统领取一份 rollout 时,会在状态数据库中登记由哪条后台管线处理,并设置到期时间。这就是处理租约(lease)。租约有效期内,其他后台管线不能重复领取;处理过程意外中断后,租约到期,这份 rollout 可以重新进入处理流程。

Phase 1 和 Phase 2 都有延迟重试和重试次数限制。新的 rollout 内容可以刷新单任务提取的处理机会。整合 Agent 异常退出、产物缺失、Git 基线更新失败或租约丢失,都会留下失败状态,不会被当成一次成功整理。

这些措施无法消除模型成本,也不保证每轮后台工作都能完成。它们限制了单轮调用规模,并避免部分失败污染已经确认的长期产物。

本地 Memory 目录保存什么

状态数据库负责协调后台管线,整理后的长期内容则保存在 ~/.codex/memories/。下面的目录树同时列出了整理输入、长期产物和运行设施:

ruby 复制代码
~/.codex/memories/
├── .git/                                  # 上一次成功整合后的内部文件基线
├── raw_memories.md                        # 本轮选中的候选记忆合集
├── rollout_summaries/                     # 本轮入选任务的运行摘要
│   └── <时间>-<短哈希>-<主题>.md           # 单项任务摘要及回溯信息
├── MEMORY.md                              # 整理后的详细长期记忆和检索入口
├── memory_summary.md                      # 新任务优先读取的紧凑记忆摘要
├── skills/                                # 从长期经验中整理出的可复用流程
│   └── <skill-name>/
│       ├── SKILL.md                       # 流程入口与使用说明
│       ├── scripts/                       # 可选的辅助脚本
│       ├── templates/                     # 可选的模板
│       └── examples/                      # 可选的示例
├── extensions/                            # 手动笔记或其他记忆来源的扩展入口
│   ├── ad_hoc/
│   │   ├── instructions.md                # 解释如何处理手动补充内容
│   │   └── notes/                         # 等待整合的补充记录
│   └── <other-extension>/
│       ├── instructions.md                # 扩展来源的处理说明
│       └── resources/                     # 可选的扩展资源
└── phase2_workspace_diff.md               # 整理期间使用的临时文件,成功后删除

这些文件不能一概视为「记忆正文」。raw_memories.mdrollout_summaries/ 是从状态数据库同步出来的整理输入;MEMORY.md 是详细的长期记忆,并在需要时指向任务摘要;memory_summary.md 更短,用来告诉新任务记忆中有哪些主题以及应当继续查找什么;skills/ 保存已经稳定到可以复用的操作流程。

.git/phase2_workspace_diff.md 属于运行设施,不会作为用户记忆注入模型。extensions/ 为手动笔记或其他记忆来源提供入口,是否出现以及包含什么内容取决于实际启用的扩展。

至此,一项历史任务已经经过筛选、单任务提取和全局整理,转化为保存在本地的长期记忆。下一章关注相反的方向:新的任务启动后,系统会读取哪些文件,又会以什么顺序把这些记忆带入模型上下文。

相关推荐
wangruofeng1 小时前
新 Mac 到手先装什么:AI Builder 的 44 款工具,基础层照抄、场景层按需
github·aigc·ai编程
面向Google编程1 小时前
GitHub 宕机近 8 小时!一次 sidecar 配置失误,如何放倒全球最大代码平台
github
峰向AI2 小时前
Modular 平台:一个人想改写 AI 开发的底层规则
github
IvanCodes4 小时前
GitHub 本周热门开源项目:Agent Infra与端侧 AI|8.17–8.23
开源·github
高频因子挖掘机5 小时前
量化交易系统的数据层和策略层如何解耦?从紧耦合泥潭到优雅分层架构
后端·github
量化小c5 小时前
从数据到策略:QuantDash + DuckDB 搭建 5 分钟 K 线本地量化数据仓库
后端·github
fthux5 小时前
装修怕增项、合同看不懂?我做了 RenoPit,帮普通业主提前发现装修坑
人工智能·ai·开源·github·open source·renopit
流量猎手6 小时前
上传github为什么优先创建.gitignore
github
逛逛GitHub8 小时前
概念版德州扑克功能,还真应有意思的,不过 Codex并没上线
github