Codex Memory 深度拆解:两阶段语义 ETL、Git 巩固与可追溯召回
本文是 Agent Memory Engineering 专栏的第 1 篇。先读总览:Agent 记忆系统架构总览。本文只研究 Codex,不把六种架构压缩在同一篇文章里。
**资料基线:**官方产品文档与 OpenAI Cookbook 于 2026-07-15 联网核对;源码固定到
openai/codex@8604689ec5e3437eb79802d8d72249b7722fbf5b,该提交时间为 2026-07-14。文中的具体常量与控制流只代表这一固定版本,不承诺后续版本保持不变。
0. 研究边界与阅读方法
0.1 四类陈述必须分开
Memory 系统很容易被写成“模型总结历史、下次再塞回 Prompt”。这种描述没有触及真正困难的部分:什么任务有资格写、多个 Worker 如何避免重复、错误如何重试、删除怎样传播、Agent 能读到哪些路径、旧经验如何证明来源,以及巩固失败时磁盘与数据库是否仍一致。
本文使用四种显式标签:
| 标签 | 含义 | 可验证范围 |
|---|---|---|
| [官方行为] | OpenAI 对用户公开的产品行为 | 以官方 Codex 文档为准 |
| [源码事实] | 固定 Commit 中可以逐行核对的实现 | 只覆盖该 Commit |
| [结构化还原] | 将分散调用链重组成状态机、时序图或伪代码 | 是解释,不是原仓库代码 |
| [工程建议] | 面向自建系统的加固方案 | 不代表 Codex 已实现 |
因此,本文不会把“源码里存在某个内部组件”直接写成“所有 Codex 客户端都必然以同样方式对外工作”,也不会把提示词中的目标等同于强一致的程序保证。
0.2 本文回答的核心问题
本文沿着一次 Memory 的完整生命周期回答十个问题:
- Local Codex Memory 的公开产品边界是什么?
- 为什么写入发生在后台启动任务,而不是每个 Task 结束时同步执行?
- Eligibility 如何同时处理 idle、age、source、history mode、污染和配额?
- Phase 1 为什么是受 Schema 约束的 Semantic ETL,而不是普通摘要?
- SQLite 中的 lease、ownership token、watermark、retry budget 分别防什么故障?
- 为什么“模型没有抽取到任何东西”也必须是一种成功?
- Phase 2 为什么需要全局锁、Git diff 和独立 Consolidation Agent?
memory_summary.md → MEMORY.md → skill / rollout evidence如何形成渐进披露?- Path sandbox、Secret Redaction、Pollution 与 Provenance 如何组成安全边界?
- 如果把这套架构迁移到生产平台,还缺哪些 SLO、审计和一致性机制?
0.3 一句话结论
Codex Memory 的关键不是“存 Markdown”,而是把跨任务经验学习实现成一套后台数据系统:
eligible historical rollout ↓leased per-thread extraction job ↓schema-constrained and redacted semantic record ↓usage-aware global input selection ↓single-writer Git-backed consolidation workspace ↓summary / registry / skill / evidence hierarchy ↓budgeted recall with machine-readable provenance这套系统同时具有 Job Scheduler、Semantic ETL、Materialized View Maintenance、Agentic Retrieval 四种性质。只模仿其中的“生成 MEMORY.md”,会丢掉大部分工程价值。
1. 公开产品契约:先确定 Memory 不是什么
1.1 Local Codex 与 ChatGPT Memory 是两个存储域
[官方行为] OpenAI 文档明确区分:ChatGPT Web 使用 ChatGPT Memory,本地 Codex 客户端使用独立的本地 Memory Store 与控制项。开启后,Codex 可以从符合条件的历史任务中提取有用上下文,生成文件位于 Codex Home;默认 Codex Home 是 ~/.codex,主要 Memory 文件位于 ~/.codex/memories/。[O1]
这个边界意味着至少三件事:
- 本地 Memory 的数据驻留、删除和排障不能用 ChatGPT Memory 的心智模型替代;
CODEX_HOME是存储作用域的一部分,切换 Home 也会切换本地记忆域;- 文件可以被检查,但官方把它们称为 generated state,不建议把人工原地编辑当作主要控制面。
[官方行为] Local Codex Memories 默认关闭。用户可以通过桌面端设置启用;配置式启用使用 [features] memories = true。任务级 /memories 可以分别控制当前任务是否读取已有记忆、是否贡献未来记忆,而且不会改写全局设置。[O1]
因此,Feature Gate、Read Gate 和 Generate Gate 是三个不同开关:
Feature::MemoryTool ├─ use_memories → 是否注入读取说明 / 暴露读取能力 └─ generate_memories → 新线程是否成为未来写入候选[源码事实] 固定版本中新线程创建或恢复时,会把 generate_memories 投影为线程级 memory_mode = enabled | disabled;读取扩展则要求 Feature 开启且 use_memories = true。[C1][C2]
1.2 Policy、Memory 与 Reviewed Artifact 必须分层
[官方行为] OpenAI 明确建议:团队必须遵守的指导应放在 AGENTS.md 或受版本控制的文档中;Memory 是有帮助的 Recall Layer,不应成为强制规则的唯一来源。[O1]
可以把三类资产拆成:
| 资产 | 典型内容 | 失败后果 | 合适控制方式 |
|---|---|---|---|
| Policy | 构建规范、禁止操作、合规规则 | 违反硬约束 | AGENTS.md、Policy Engine、CI |
| Memory | 用户偏好、踩坑经验、已验证工作流 | 效率或一致性下降 | 后台提炼、召回、漂移验证 |
| Reviewed Artifact | 报告、设计、审计结论、交付物 | 业务事实失真 | 人工复核、版本管理、证据引用 |
[工程建议] 不要让 Memory 同时承担 Policy Database 和业务事实系统。Memory 的生成链路包含模型判断、压缩和巩固,天然可能遗漏;硬规则应由确定性控制执行,重要事实应留在可审阅制品里。
1.3 Memory 与 Compaction 解决的是不同时间尺度
OpenAI Cookbook 给出的区分非常直接:[O2]
| 维度 | Compaction | Memory |
|---|---|---|
| 时间范围 | 同一个长 Run | 不同 Run 之间 |
| 输入 | 当前对话与工作状态 | 历史任务中的可复用经验 |
| 目标 | 当前 Run 在有限上下文中继续 | 未来 Run 少走弯路 |
| 典型失效 | 丢失当前任务状态 | 固化过期或错误经验 |
[源码事实] Phase 1 序列化 Rollout 时明确跳过 RolloutItem::Compacted,Memory 读取又通过独立扩展注入;这不是“把 Compaction Summary 复制成长期记忆”的同一条链。[C3][C12]
如果把两者合并,会产生三个问题:
- 当前 Run 为连续执行保留的临时状态,会污染未来任务;
- 跨任务稳定经验会被当前上下文压缩策略意外淘汰;
- 两种资产的 TTL、验证、权限和来源追踪无法独立治理。
2. 总体架构:控制面与数据面分离
2.1 两阶段写入不是简单的两次摘要
[源码事实] 固定版本将写路径分为 Phase 1 和 Phase 2:[C3][C8]
flowchart LR A["Historical rollout JSONL"] --> B["Eligibility scan"] B --> C["Stage 1 lease per thread"] C --> D["Filtered and redacted prompt"] D --> E["Structured raw memory and summary"] E --> F["memories_1.sqlite"] F --> G["Usage-aware selection"] G --> H["Global Phase 2 lease"] H --> I["Sync Git-backed workspace"] I --> J["Workspace diff"] J --> K["Consolidation agent"] K --> L["MEMORY.md and memory_summary.md"] L --> M["Budgeted progressive recall"]Phase 1 的并行单位是 thread,输出是保留单次任务证据边界的中间记录;Phase 2 的并行度被故意压成全局单写者,输出是跨任务整理后的物化视图。
这与 Map/Reduce 类似,但有两个关键差异:
- Map Operator 是模型驱动的语义抽取,结果允许为空;
- Reduce Operator 不是纯函数,而是一个在受限文件工作区内编辑现有知识库的 Agent。
2.2 数据面有五层,不只有 MEMORY.md
[源码事实] 写入模板和读取模板共同定义了以下层级:[C9][C12]
| 层 | 主要用途 | 是否直接注入 |
|---|---|---|
memory_summary.md | 高密度路由摘要,首行必须为 v1 | 是,最多 2,500 tokens |
MEMORY.md | 可搜索 Registry / Handbook | 按需搜索 |
skills/<name>/SKILL.md | 可执行流程知识 | 按需读取 |
rollout_summaries/*.md | 单次任务回顾、证据指针 | 按需读取 |
raw_memories.md | Phase 2 的机械合并输入 | 写路径内部制品 |
这里最重要的设计是 路由信息与证据正文分离。启动时只注入紧凑摘要;检索命中后再逐层下钻,避免把整个历史知识库放进每个请求。
2.3 控制面有三套状态
[结构化还原] Codex Memory 同时维护:
Thread state memory_mode: enabled | disabled | polluted updated_at / source / history_mode / cwd / rollout_path
Job state status / ownership_token / lease_until retry_at / retry_remaining / last_error input_watermark / last_success_watermark
Materialized workspace state Git baseline / filesystem diff selected rollout snapshot MEMORY.md / memory_summary.md / skills三个状态域不能相互替代:Thread State 决定输入是否合法;Job State 决定谁可以处理;Git State 判断物化输出是否真的发生变化。
3. Startup 与 Eligibility:写入前先治理输入
3.1 启动流水线的真实触发边界
[源码事实] start_memories_startup_task 只为符合条件的 root session 启动异步流程。以下情况直接跳过:[C4]
- 当前 Session 是 ephemeral;
Feature::MemoryTool未启用;- Session Source 是 non-root agent;
- State DB 不可用。
后台任务内部的顺序是:
create ~/.codex/memories→ seed extension instructions→ prune stale Stage 1 outputs→ check rate-limit guard→ Phase 1→ Phase 2Prune 放在 rate-limit guard 之前,因为它不消耗模型 Token;Phase 1 与 Phase 2 在同一个异步启动任务中顺序执行,但 Phase 2 会把长时间运行的 Consolidation Agent 交给另一个 Tokio Task 继续监管。
[官方行为] 文档说明,Memory 更新不是 Task 结束时立即发生,活跃或短 Session 会被跳过;当 Codex 剩余 Rate Limit 百分比低于配置阈值时,也可能跳过一次后台处理。[O1]
这是一种明确的 主链路隔离:用户任务的完成不依赖 Memory 是否写成功,Memory 写入也不会把延迟直接叠加到任务响应上。
3.2 Rate-limit Guard 是成本背压,不是正确性门禁
[源码事实] Guard 查询 Codex Backend 的 Rate Limit Snapshot,优先选择 limit_id = codex,否则回退第一项;primary 和 secondary 两个窗口都必须满足阈值。固定版本默认要求至少剩余 25%。如果无法取得鉴权、Backend 或 Snapshot,rate_limits_ok 采用 fail-open,返回允许执行。[C5][C6]
这个选择体现了两类失败策略:
- 明确知道额度不足:停止,保护用户配额;
- 无法观测额度:继续,避免观测系统故障永久关闭 Memory。
[工程建议] 企业系统应把 fail-open / fail-closed 做成策略项,并记录 guard_decision、snapshot_age、limit_window。对于严格成本预算,未知状态应进入延迟队列,而不是立即调用模型。
3.3 Eligibility SQL 的七重筛选
[源码事实] claim_stage1_jobs_for_startup 不是扫描任意历史 JSONL,而是先从 Thread State DB 选出候选:[C7]
- 只选未归档的活跃 Thread;
- Source 必须属于交互式来源集合:CLI、VSCode、
atlas、chatgpt; memory_mode = enabled;history_mode = legacy,因为固定版本 Phase 1 仍会完整载入 Rollout JSONL;- 排除正在触发本次 Startup 的当前 Thread;
updated_at不早于最大年龄 Cutoff;updated_at不晚于 idle Cutoff,也就是必须已经安静足够久。
查询按 updated_at DESC 排序,先用 scan_limit 限制 State DB 工作量,再逐条检查 Memory DB 中是否已经有同样或更新的输出,最后最多 Claim max_rollouts_per_startup 个任务。
固定版本的默认值与约束如下:[C6]
| 配置 | 默认值 | Clamp / 固定限制 | 目的 |
|---|---|---|---|
max_rollouts_per_startup | 2 | 1~128 | 控制单次启动成本 |
max_rollout_age_days | 10 | 0~90 | 限制回溯窗口 |
min_rollout_idle_hours | 6 | 1~48 | 避免抽取进行中的任务 |
min_rate_limit_remaining_percent | 25 | 0~100 | 成本背压 |
max_raw_memories_for_consolidation | 256 | 1~4096 | 限制全局巩固输入 |
max_unused_days | 30 | 0~365 | 召回驱动的保留窗口 |
| 内部 Thread Scan Limit | 5,000 | 固定常量 | 限制候选探测 |
| Phase 1 并发 | 8 | 固定常量 | 并行抽取上限 |
源码注释推荐 idle 时间大于 12 小时,但固定默认值仍是 6 小时。这是一个典型的“配置建议与当前默认值不同”的细节,不能只读注释或只读默认常量。
3.4 为什么 Idle 与 Age 不能用一个 TTL 代替
Idle Threshold 回答“这个 Task 是否可能继续”,Age Limit 回答“这个 Task 是否还值得处理”。二者的方向相反:
updated_at <= now - idle_thresholdANDupdated_at >= now - max_age只有落在中间窗口的任务才有资格。没有 idle gate,半成品 Debug 结论会过早固化;没有 age gate,升级后第一次启动可能把多年历史全部重新消费,造成费用尖峰和大量旧知识回流。
[工程建议] 除时间外还应加入终态信号,例如测试是否完成、用户是否确认、是否产生可验证制品。时间只是任务稳定性的代理变量,不等于语义完成。
3.5 Pollution 是数据血缘隔离,不是内容过滤
[官方行为] memories.disable_on_external_context = true 时,使用 MCP、Web Search 或 Tool Search 等外部上下文的任务可以被排除在未来 Memory Generation 之外,旧键 no_memories_if_mcp_or_web_search 仍作为别名接受。[O1]
[源码事实] 固定版本不只是检查某个工具名:
- Web Search / Tool Search Response Item 可以把线程标记为
polluted; - MCP Server 自身可以声明是否污染 Memory;
- 任意 Core Tool Output 若标记
contains_external_context(),也会触发污染; - 已经进入上次 Phase 2 Selection 的线程被污染时,会重新 Enqueue Phase 2,让删除通过下次 Workspace Diff 传播。[C15][C7]
为什么不是只在 Phase 1 Prompt 中把外部片段删掉?因为外部信息可能已经影响模型推理、代码修改和最终答案。只删原始工具输出无法证明派生结论没有污染。Thread 级 quarantine 更保守,也更容易审计。
[工程建议] 更成熟的系统应把布尔 polluted 扩展为 taint vector:
external_webthird_party_mcpprivate_customer_datauntrusted_repositorylicensed_contentregulated_pii不同 taint 应映射到不同的可写 Namespace、Retention 和共享范围,而不是全部永久丢弃。
4. Phase 1:受约束的 Semantic ETL
4.1 输入不是原样 Rollout,而是安全投影
[源码事实] Phase 1 先载入 Rollout Items,再构造面向 Memory 的过滤投影:[C3][C16]
保留的主要类型包括:用户与助手 Message、Agent Message、Local Shell Call、Function/Custom Tool Call 及 Output、Tool Search、Web Search,以及 Inter-Agent Communication。以下内容被跳过:
- Developer Message;
- Reasoning;
- Compaction 与 Context Compaction;
- Additional Tools;
- Image Generation Call;
- Session Meta、Turn Context、World State、EventMsg 等 Rollout 元数据。
此外,用户 Message 内被框定的 AGENTS.md instructions 和 <skill>...</skill> 片段也会被移除。这一点非常重要:否则系统注入的 Policy 与 Skill 正文可能被模型误判为“用户偏好”,再写入长期记忆,形成自我复制。
[结构化还原] 这个过程更像字段级数据投影:
def memory_projection(rollout_items): for item in rollout_items: if item.is_developer_message(): continue if item.is_user_message(): item = item.remove_marked_agents_and_skill_fragments() if item.is_response_item() and memory_policy_allows(item): yield item elif item.is_inter_agent_communication(): yield item.to_model_input()它不是 Codex 原始源码,而是对固定版本过滤规则的结构化还原。
4.2 长 Rollout 使用 Head + Tail 截断预算
[源码事实] Stage 1 输入预算先取模型 resolved context window,乘模型的 effective context percentage,再取其中 70%;如果模型元数据不可用,回退到 150,000 tokens。truncate_text 保留头尾上下文,为系统指令和输出预留空间。[C10]
这解决“整段历史无限增长”,但仍存在语义风险:中段可能包含真正的根因、测试证据或用户纠正。
[工程建议] 生产抽取器可以先做事件索引,再在预算内拼接:
always keep: user corrections + final verification + failuressample: first task framing + last outcomeretrieve: error clusters + changed files + test commandsdrop: repeated progress updates + duplicate tool output这样截断从位置策略升级为证据策略。需要注意,任何额外选择器都必须保留 provenance,避免下游无法解释“为什么某条证据没被看见”。
4.3 输出 Schema 把模型变成可组合算子
[源码事实] Phase 1 使用 strict JSON Schema,禁止额外字段,并要求三个字段都出现:[C3]
{ "raw_memory": "detailed markdown or empty string", "rollout_summary": "compact routing summary or empty string", "rollout_slug": "optional string represented as string or null"}其中:
raw_memory保存任务级详细经验与证据;rollout_summary提供紧凑路由与索引;rollout_slug用于生成可读 Artifact Filename。
Schema 的价值不在“格式漂亮”,而在于下游可以确定性地 Validate、Persist、Select、Materialize、Measure。聊天输出只能靠脆弱解析;受约束输出才可以作为数据管道中的 Record。
4.4 No-op 是精度控制,不是抽取失败
[源码事实] Stage 1 Prompt 明确设置 Minimum Signal Gate:只有当未来 Agent 会因此做得更好时才保存;一次性随机问题、临时指标、通用常识、没有验证的讨论都应返回三个空字段。[C11]
运行时代码只要发现 raw_memory 或 rollout_summary 为空,就走 SucceededNoOutput,而不是重试模型。[C3]
为什么要求两个核心字段都非空?因为详细 Memory 没有 Routing Summary 会成为不可发现孤岛;只有 Summary 没有证据正文,又会让下游巩固缺乏依据。把不完整 Pair 当成 no-output,比让半条记录进入全局知识库更稳妥。
这里体现的是写入精度优先:
false positive memory → 每个未来任务都可能被错误影响
false negative memory → 只损失一次潜在复用长期记忆的错误具有跨 Run 放大效应,因此 no-op 应是常态能力,而不是异常分支。
4.5 Redaction 在序列化前后执行两次
[源码事实] 固定版本调用 redact_secrets 的位置至少包括:[C3]
- 过滤后的整个 Rollout JSON 在上传给模型前;
- 模型返回的
raw_memory; - 模型返回的
rollout_summary; - 模型返回的
rollout_slug。
输入侧 Redaction 降低 Secret 离开本机的概率,输出侧 Redaction 防止模型复述、变形或从上下文重新组合敏感值。Slug 也需要处理,因为 Filename 会进入目录、日志、Diff 和工具响应,泄露面比正文更广。
[工程建议] Secret Redaction 不能等同于完整 DLP。生产系统还应考虑:
- 高熵 Token、云凭证、私钥与 Connection String 的检测;
- PII / PHI / 财务字段分类;
- 组织、用户、仓库 Namespace ACL;
- Raw Rollout 与 Derived Memory 的独立加密密钥;
- Redaction 规则版本、命中类型与误杀审计;
- 在巩固和读取输出上再次执行 egress scanner。
Redaction 之后也不应记录原 Secret 的 Hash 作为可关联指标,因为低熵凭证仍可能被枚举。
4.6 并行度、故障与成本统计
[源码事实] 被 Claim 的任务通过 buffer_unordered(8) 并行执行。每个 Job 独立产生 SucceededWithOutput、SucceededNoOutput 或 Failed,最终聚合计数和 Token Usage;指标包括 Phase 1 E2E、Job Status、Output 与不同 Token Type。[C3][C17]
Phase 1 可并行是因为每个 Thread 只更新自己的 (kind=memory_stage1, job_key=thread_id) 和输出行。Phase 2 不可同样扩并发,因为多个 Agent 会编辑同一个全局 Memory Workspace。
[工程建议] 并发上限还应受以下动态信号控制:模型 Rate Limit、Rollout 大小、历史 P95 Latency、磁盘 I/O、当前失败率。固定并发适合本地客户端,但多租户服务需要 Weighted Fair Queue,避免大 Rollout 阻塞小任务。
4.7 Prompt 实际定义了一套经验抽取本体
只看三个 JSON 字段,会误以为 Phase 1 只是把对话缩短。真正决定 Memory 质量的,是 System Prompt 对“什么算经验”的分类。[C11]
[源码事实] Prompt 把高信号内容分成四类:稳定的用户操作偏好、高杠杆流程知识、可靠的任务地图与决策触发器、对用户环境和工作方式的持久证据。它明确排除通用建议、大段工具输出、临时事实、未被采纳的头脑风暴,以及主要用于复述对话而不能改变未来行为的内容。
它还规定证据读取优先级:
user messages → 最强的偏好、约束、验收和不满意证据
tool outputs / verification evidence → 最强的仓库事实、失败、命令和成功验证证据
assistant messages → 用于还原尝试过程,但不是用户偏好的主要事实源这个排序防止一种常见自污染:助手先提出某个方案,后续 Extractor 又把助手自己的提议写成“用户长期偏好”。只有用户反复要求、纠正、打断或明确采纳,才有足够理由向稳定偏好提升。
[源码事实] Prompt 要求把一个 Rollout 内的不同用户任务拆成独立 Task <n>,并给每项任务标记 success | partial | uncertain | fail。任务块要保存 Preference Signals、Reusable Knowledge、Failures、References 与验证信号。这样 Phase 2 能区分“同一 Thread 的多个目标”,而不是把一段长会话粗暴压成一个主题。
Outcome 不是情绪标签,而是写入置信度的重要输入:
success可以沉淀被验证的流程与停止条件;partial应保留完成部分和剩余边界;uncertain必须说明缺少什么验收证据;fail更适合保存故障屏障,不应把失败路径包装成推荐做法。
[源码事实] CWD 被当作一等 Provenance。Raw Memory 顶层应选择唯一、证据最强的主工作目录;若同一个 Rollout 在不同目录执行语义不同的任务,应拆开,而不是让未来 Agent 把两个 Checkout 的命令和约束混在一起。[C11]
这说明 Codex 的 Experience Record 至少隐含以下本体:
Task BoundaryOutcomePreference EvidenceReusable ProcedureFailure ShieldVerification SignalArtifact / Command ReferenceWorkspace Scope[结构化还原] raw_memory 是证据保留较多的任务级中间表示,rollout_summary 是面向检索与 Phase 2 路由的压缩表示。二者不是长短版本的简单复制:前者要足够支撑冲突判断和流程复用,后者要足够紧凑且可检索。Phase 2 再负责判断一次性信号是否可以与其他 Rollout 合并成稳定 Task Group。
4.8 Prompt 规则与程序保证之间仍有鸿沟
Prompt 写了“证据优先、不得发明、不要存 Secret、原始 Rollout 不可修改”,但 Runtime 能硬保证的只有输入过滤、JSON Schema、Secret Redaction、字段完整性门槛和带 Token 的 DB 提交。模型是否正确识别 Task Boundary、是否把 partial 错判为 success、是否过度推断用户偏好,仍然是概率行为。
[工程建议] 可以把容易确定的部分移出模型:
- 从 Rollout Event 直接生成工具调用、退出码、修改文件、测试结果和用户反馈索引;
- 模型只对索引后的证据做语义分类,不负责重新发现所有事实;
- 对
success要求至少一个机器验证或明确用户确认,否则自动降为uncertain; - Preference Promotion 要求跨 Rollout 支持计数,单次信号默认只留在 Raw Memory;
- 每个抽取字段保存 Evidence Span ID,Phase 2 不能生成无来源的稳定结论。
这样可以把“看起来合理的摘要”提升为“有机器证据约束的经验记录”。
5. SQLite Job State Machine:把后台任务做成可恢复系统
5.1 Memory 状态已经迁出主 State DB
[源码事实] 固定版本使用独立的 memories_1.sqlite,Memory Migrator 创建 stage1_outputs 和 jobs;MemoryStore 同时持有 Memory DB Pool 与 State DB Pool,以便从 Thread 表补齐 cwd、rollout_path、git_branch 等元数据。主 State DB 的迁移 0035_drop_memory_tables.sql 会删除旧的 Memory 表。[C18][C19]
这种拆分的价值包括:
- Memory 数据膨胀不会直接扩大主 Thread State 的事务面;
- 可以独立清理、备份、迁移和做损坏恢复;
- Job 更新与 Thread Metadata 读取的所有权更清晰。
代价是跨库没有单个 SQLite Transaction。Eligibility 先读 State DB、再 Claim Memory DB,二者之间存在 TOCTOU 窗口;Phase 2 Selection 也要先从 Memory DB 排名,再去 State DB 验证 Thread 仍 enabled。
5.2 两张表承载事件输入与执行控制
[源码事实] stage1_outputs 的核心字段是:[C19]
thread_id primary keysource_updated_at 输入版本raw_memory 详细抽取rollout_summary 路由摘要rollout_slug 文件命名提示generated_at 生成时间usage_count / last_usage 真实复用反馈selected_for_phase2 上次成功巩固是否选择selected_for_phase2_source_updated_atjobs 是通用后台作业表:
(kind, job_key) composite primary keystatus pending | running | done | errorworker_id / ownership_token 执行者与 fencing identitystarted_at / finished_atlease_until 崩溃后可接管边界retry_at / retry_remaininglast_errorinput_watermarklast_success_watermarkStage 1 的 Job Key 是 thread_id;Phase 2 的 Job Key 固定为 global。同一张表表达“多 Key 并行抽取”和“单 Key 全局巩固”两种拓扑。
5.3 Claim 不是先查后写,而是带条件的原子 Upsert
[源码事实] try_claim_stage1_job 使用 BEGIN IMMEDIATE,先检查输出或成功 Watermark 是否已经覆盖输入,再以条件 Upsert 争抢所有权:[C7]
Claim 成功必须同时满足:
- 全局未过期的 Stage 1 Running Job 数量低于上限;
- 同一个 Job 没有持有有效 Lease 的 Worker;
- Retry Backoff 已过,或者出现了更新的
input_watermark; - Retry Budget 尚未耗尽,或者出现了更新输入。
成功时刷新 ownership_token、worker_id、lease_until、input_watermark,清除旧错误;如果 Watermark 前进,则 Retry Budget 重置为 3。
ownership_token 比 worker_id 更关键。一个 Worker 崩溃后重启可能仍有同样 Worker Identity,但旧执行实例不应再提交结果;随机 Token 把“机器身份”和“本次租约身份”分开,完成更新必须同时匹配 status=running 与 Token。
5.4 Lease、Watermark 与 Retry 各自解决不同问题
| 机制 | 防止的问题 | 不能替代什么 |
|---|---|---|
| Lease | Worker 崩溃后 Job 永久卡在 running | 不能判断输出是否新鲜 |
| Ownership Token | 旧 Worker 在 Lease 丢失后迟到提交 | 不能表达输入版本 |
| Input Watermark | 记录本次 Claim 处理的源版本 | 不能证明文件真的变化 |
| Last-success Watermark | 避免同一输入重复抽取 | 不能替代输出行版本检查 |
| Retry Budget | 坏输入无限烧模型配额 | 不能阻止新输入恢复处理 |
| Retry-at | 避免瞬时故障紧密循环 | 不能做全局并发控制 |
[源码事实] Stage 1 默认 Lease 为 3,600 秒、失败 Backoff 为 3,600 秒、Retry Budget 为 3。测试明确验证:同一 Watermark 连续失败三次会进入 exhausted;Thread 出现更高 Watermark 后,预算恢复为 3,并允许再次 Claim。[C6][C20]
这是一个很容易被遗漏的恢复语义:
Retry exhaustion 应隔离坏版本,不能永久封死业务实体。
固定版本的 Stage 1 没有像 Phase 2 那样定期 Heartbeat。也就是说,一次抽取如果因为超长 Rollout、模型排队或网络停顿超过一小时,Lease 可能先过期;另一个 Worker 随后能够接管。旧 Worker 最终返回时,ownership_token 条件会阻止它提交,但这次模型费用已经发生。Lease 太短会增加重复计算,太长又会推迟崩溃恢复,应该依据 Extraction P99 耗时设置,并给长调用增加续租或可取消请求。
5.5 成功提交同时推进输出与下游调度
[源码事实] mark_stage1_job_succeeded 在同一个 Memory DB Transaction 中:[C7]
- 仅当 Token 仍拥有 running Job 时把状态改成 done;
last_success_watermark = input_watermark;- Upsert
stage1_outputs,只允许相同或更新source_updated_at覆盖; - Enqueue / Advance 全局 Phase 2 Job;
- Commit。
这保证“Stage 1 Output 已持久化”和“Phase 2 已被通知”不会在 Memory DB 内部分裂。如果在 Commit 前崩溃,两者都回滚;Commit 后崩溃,Phase 2 仍可在后续 Startup 被 Claim。
不过这不是端到端 exactly-once:模型调用发生在事务外,崩溃后可能重复付费。系统实现的是 幂等持久化 + at-least-once 计算。
5.6 No-output Success 必须删除旧输出
假设 Thread 在版本 100 曾产生 Memory,但版本 101 的完整任务表明它不再有可复用信号。如果 no-output 只把 Job 标记成功而保留旧行,旧结论会永久残留。
[源码事实] mark_stage1_job_succeeded_no_output 会:[C7]
- 以 Token 条件把 Job 标记 done;
- 推进
last_success_watermark; - 删除该 Thread 的旧
stage1_outputs; - 只有确实删除旧行时,才 Enqueue Phase 2 传播 forgetting。
因此“空输出”具有领域语义:它可能是对旧物化状态的撤回,而不是没有事件。
5.7 Retention 由“最近使用或最近生成”共同决定
[源码事实] Memory Citation 中的 Rollout ID 会被解析为 Thread ID,命中的 Stage 1 行会增加 usage_count 并更新 last_usage。[C14][C21]
Prune 只删除 selected_for_phase2 = 0 的行,并以 COALESCE(last_usage, source_updated_at) 作为 Recency,从最旧开始每批最多 200 条。仍属于上次成功 Phase 2 Baseline 的行受保护,避免数据库先删而文件工作区尚未完成 forgetting。[C7]
这比固定创建时间 TTL 更合理:真正被未来任务引用的旧经验可以保留,从未使用的近似重复项会自然退出。
[工程建议] 使用次数不等于价值。错误 Memory 也可能因为高曝光而高频命中。生产选择应综合:
utility_score = successful_use_feedback + verified_task_gain + recency - contradiction_count - stale_verification_failures - security_risk5.8 Stage 1 Claim 的完整判定表
为了看清状态机,不能只画 pending → running → done。同一次 Claim 的返回值取决于 Output Version、Job State、Lease、Backoff 和 Watermark 的组合。
[结构化还原] 固定版本的决策可以整理为:[C7]
| 已有状态 | 当前输入 | 判定 | 原因 |
|---|---|---|---|
| Output Version ≥ Source Version | 任意 | SkippedUpToDate | 物化输出已覆盖输入 |
| Last-success Watermark ≥ Source Version | 无 Output 也可 | SkippedUpToDate | 可能是已确认的 no-output |
| running 且 Lease 未过期 | 同版本 | SkippedRunning | 已有合法 Owner |
| running 但 Lease 已过期 | 同版本 | 可重新 Claim | 崩溃接管 |
| error 且 Retry-at 在未来 | 同版本 | SkippedRetryBackoff | 防止紧密重试 |
| error 且 Budget 为 0 | 同版本 | SkippedRetryExhausted | 隔离坏版本 |
| error 且 Budget 为 0 | 更高 Watermark | 可 Claim,预算重置 | 新输入不继承旧失败 |
| done | 更高 Watermark | 可 Claim | 增量重抽取 |
| 全局 running 数达到上限 | 任意 | 未 Claim | 并发背压 |
stage1_outputs.source_updated_at 和 jobs.last_success_watermark 必须同时检查。前者证明有非空物化记录,后者还能表示“这个版本已经成功判断为无输出”。如果只检查 Output 表,no-output 任务会在每次 Startup 重复抽取;如果只检查 Job Watermark,数据库恢复或输出丢失后可能误认为非空制品仍存在。
5.9 BEGIN IMMEDIATE 为什么重要
SQLite 默认 Deferred Transaction 在第一次写之前并不拿写锁。两个 Startup Worker 都可能先读到“没有 Job”,再同时尝试插入。固定版本使用 BEGIN IMMEDIATE,让 Claim Transaction 在开始时就竞争 Reserved Write Lock,再通过 ON CONFLICT ... DO UPDATE ... WHERE 把租约条件放进写语句。[C7]
这提供了本机多 Worker 下的线性化 Claim Point:只有一条条件写入会返回 rows_affected > 0。失败者随后重新读取 Job Row,将失败映射为 Running、Backoff 或 Exhausted,而不是向上只返回含糊的“争抢失败”。
但 SQLite Write Lock 只覆盖很短的 Claim Transaction,模型调用不在锁内。否则一次长推理会阻塞所有 Memory Job 更新。长临界区由 Lease + Token 表达,短临界区由数据库锁表达,两者职责不同。
[工程建议] 迁移到 PostgreSQL 时可以使用 INSERT ... ON CONFLICT ... WHERE、advisory lock 或 SELECT ... FOR UPDATE SKIP LOCKED,但仍要保留 Ownership Token。数据库连接断开不代表外部模型请求已经停止,迟到结果依然需要 fencing。
5.10 删除传播是一条显式数据流
长期记忆的难点不是 Add,而是让删除最终消失。固定版本有四种主要撤回来源:
new source version produces no outputthread is deletedthread becomes polluted / disabledinput falls out of usage-aware selection[源码事实] 前两种会删除或失去 Stage 1 Output;Pollution 会让 State DB Eligibility 校验失败;Selection 淘汰不会删数据库行,但会从本次物化输入集合消失。只要该 Thread 属于上次成功 Selection,系统就 Enqueue Phase 2 或保留它不被 Prune,直到 Git Workspace 同步产生删除 Diff并完成一次新 Baseline。[C7]
可以把这看成带 Tombstone 的物化视图维护:Tombstone 不一定是一条独立记录,也可以表现为“之前 Selection 中存在、当前 Selection 中缺失”。Git Diff 把集合差转换成 Agent 可读的删除信号。
为什么未被选择的旧 Stage 1 行可以直接 Prune?因为它不属于当前 Baseline,删除它不会改变任何对未来可见的 Artifact。为什么 selected_for_phase2=1 的行不能直接 Prune?因为文件中可能仍有由它派生的结论,必须先让 Consolidator 执行精确 forgetting。
[工程建议] 合规删除还需超越这种最终一致机制。系统应记录 Delete Intent、受影响 Generation、Consolidation Completion、Index Purge、Backup Expiry 与验证查询,形成可证明的 Deletion Ledger。仅看到文件 Diff 不足以证明所有副本都已删除。
6. Phase 2:全局巩固与 Git-backed Materialization
6.1 Selection 不是“取最新 N 条”
[源码事实] get_phase2_input_selection 先过滤空输出和超出 Retention Window 的记录,再按以下优先级排序:[C7]
usage_count DESCCOALESCE(last_usage, source_updated_at) DESCsource_updated_at DESCthread_id DESC每页最多探测 512 个候选;对每个候选回到 State DB 验证 Thread 仍为 memory_mode=enabled 且 history_mode=legacy。选出 Top N 后,再按 thread_id ASC 稳定排序用于文件物化。
“排名顺序”和“序列化顺序”被故意分开:排名决定谁入选,稳定 Thread ID 顺序减少无意义 Diff。如果每次按热度动态排序写入 raw_memories.md,即使内容不变也会产生大范围移动,增加 Consolidator 的噪声。
6.2 Selection Snapshot 让删除有传播依据
成功 Phase 2 会先清空旧的 selected_for_phase2 标志,再只对本次精确选择的 (thread_id, source_updated_at) 快照标记为 1。[C7]
这不是普通缓存标志,它回答:
当前 Git Baseline 中的长期记忆,可能依赖哪些 Stage 1 输入版本?
因此 Thread 删除、变为 polluted、退出 Retention 或被更高优先级记录挤出 Top N 时,系统知道旧 Baseline 可能包含它,应该触发一次 Phase 2,通过 Workspace Diff 传播删除。
selected_for_phase2_source_updated_at 不是冗余字段。某个已选 Thread 在 Phase 2 之前又产生更新版本时,同一行的 source_updated_at 会前进,而“当前 Baseline 依赖的是哪个旧版本”仍需要单独记录。成功巩固只会对 Thread ID 和 Source Timestamp 都匹配的行设置新快照,从而避免把尚未真正进入 Baseline 的更新版本误标为已物化。
6.3 全局锁与六小时 Cooldown
[源码事实] Phase 2 使用 (kind=memory_consolidate_global, job_key=global) 单例 Job。Claim 会拒绝:[C7]
- Retry Backoff 尚未结束;
- 已有有效 running Lease;
- 最近一次无错误成功距离现在不足 6 小时。
Phase 2 的 Lease 同样为一小时,但 Agent 运行期间每 90 秒 Heartbeat;状态每秒 Poll 一次,Missed Tick 使用 Skip,避免事件循环阻塞后补发一串过期心跳。[C8]
为什么需要全局单写者?因为 Consolidator 会同时读取已有 MEMORY.md、新 Raw Memories、Skills 与 Rollout Summaries,再做跨条目去重和冲突更新。两个 Agent 从同一个 Baseline 出发,会形成 Lost Update:
Consolidator A: V10 + input A → V11-AConsolidator B: V10 + input B → V11-B数据库行锁不能保护模型在文件系统上持续数分钟的编辑过程,租约式全局 Job 才能覆盖完整临界区。
6.4 Phase 2 Retry Budget 的反直觉语义
[源码事实] Phase 2 失败同样会把 retry_remaining 减一且不低于零,并设置一小时 retry_at。但固定版本的 Global Claim 查询并不读取或检查 retry_remaining;测试还明确验证,即使预算降到零,Backoff 到期后 Global Lock 仍然可以再次被 Claim。[C7][C20]
这与 Stage 1 的语义不同:
Stage 1 retry_remaining = 0 → 同一 input watermark 自动重试被封锁 → 更新 watermark 才恢复预算
Phase 2 retry_remaining = 0 → retry_at 到期后仍可再次 claim → global materialization 不能被永久封死为什么可能这样设计?Stage 1 的坏输入通常局限于一个 Thread,可以隔离;Phase 2 是唯一全局物化器,如果永久 exhausted,所有后续 Memory 更新都会停止。代价是持续性确定故障可能每小时再次消耗资源。因此应把 Phase 2 的 retry_remaining 理解成可观测的连续失败计数线索,而不是固定版本中的硬熔断器。
[工程建议] 生产系统可以采用分层恢复:短期指数退避,连续失败后打开 Circuit Breaker;但新 Selection Digest、Prompt Version、Model Version 或人工修复可以关闭 Breaker。这样既不让旧故障无限烧成本,也不让全局管道永久停摆。
6.5 Watermark 只负责调度,Git Diff 才负责 Dirty Check
[源码事实] Enqueue Phase 2 时,若新输入 Watermark 大于旧值就采用新值;否则把旧值加一。源码注释明确说明:这只是 Bookkeeping,不用它判断是否真的有巩固工作。Phase 2 Claim 后先把当前 Selection 同步到文件系统,再以 Git Diff 判断 Workspace 是否变化。[C7][C8]
这解决了几类无法用单调时间戳表达的事件:
- 删除旧 Stage 1 Output;
- Thread 从 enabled 变 polluted;
- Retention 导致输入集合缩小;
- Extension Resource 被删除;
- 相同最大时间戳下,输入集合发生变化。
如果 Diff 无变化且 MEMORY.md、memory_summary.md 校验有效,Phase 2 直接记录 succeeded_no_workspace_changes,不启动模型 Agent。[C8]
这是一种两级判定:
DB watermark: should inspect?Git diff: is materialized input actually different?6.6 Git 在这里是可审计 Baseline,不是产品仓库
[源码事实] prepare_memory_workspace 会创建目录、删除上次生成的 phase2_workspace_diff.md,并确保存在可用 Git Baseline;memory_workspace_diff 从最近 Init Baseline 计算状态与 Unified Diff;成功后 reset_git_repository 把当前目录设为新 Baseline。[C22]
生成给 Agent 的 Diff 文件:
- 列出文件状态;
- 包含 Git-style Unified Diff;
- 最大 4 MiB;
- 在 UTF-8 字符边界截断;
- 明确告诉 Agent 先读且不要编辑。
成功前会校验:
MEMORY.md必须存在且是普通文件;memory_summary.md必须能读取,第一行严格等于v1。
Git 的价值不是让用户手工维护 Memory History,而是提供 Baseline、Diff、删除检测、变更审计和回滚基础。它把“全量重新总结”改造成“增量物化视图维护”。
6.7 Workspace Input 如何物化
[源码事实] 当前 Selection 会被同步成两类文件:[C23]
raw_memories.md:按稳定 Thread ID 顺序机械合并,包含updated_at、cwd、rollout_path、对应 Summary Filename 与详细 Raw Memory;rollout_summaries/<timestamp>-<hash>-<slug>.md:单 Thread 摘要,包含 Thread ID、更新时间、Rollout Path、CWD、可选 Git Branch。
Summary Filename 的 Slug 最长 60 字节,只保留 ASCII Alphanumeric,其余替换为 _;前缀包含时间与四位 Base62 短 Hash。旧 Selection 不再保留的 Summary File 会被删除,使 Git Diff 能显示 forgetting。
[工程建议] 短 Hash 主要用于可读命名,不应当作安全 ID 或全局唯一键。业务关联仍应使用完整 Thread ID 与 Source Watermark。
6.8 Consolidation Agent 是受限内部 Worker
[源码事实] Phase 2 不调用单次 summarize(all),而是 Spawn 一个内部 Codex Thread,Source 标记为 MemoryConsolidation。配置被锁紧:[C8][C24]
cwd固定为 Memory Root;ephemeral = true;generate_memories = false、use_memories = false,避免递归记忆;- 不注入 Apps Instructions,MCP Servers 为空;
- Approval Policy 为 Never;
- 关闭 Spawn / Collaboration / Memory Tool / Apps / Plugins / Skill Dependency Install;
- 父级为 Managed Permission 时,只允许写 Memory Root,网络关闭,并排除临时目录;
- 使用独立 consolidation model,Reasoning Effort 为 Medium。
这说明 Memory Maintenance 本身被建模为一个复杂 Agent Task,但 Agent 的能力面被压到完成任务所需的最小集合。
[工程建议] 仍应把 Prompt 中的“不要读原始 Session”“不要编辑 Diff”视为软约束。真正的硬边界来自文件权限、网络禁用、工具禁用和 Path Root;对禁止写的生成文件还可使用只读挂载或提交前 Allowlist Validator。
6.9 巩固 Prompt 定义的是知识库 Schema
[源码事实] Consolidation Prompt 要求区分 INIT 与 INCREMENTAL UPDATE,并维护:[C9]
MEMORY.md:按 Task Group 聚类的可检索 Handbook;memory_summary.md:首行v1,包含用户画像与“What’s in Memory”路由索引;skills/*:高复用、步骤化、可验证的流程;- 已删除输入对应的精确 forgetting;
- 对冲突证据保留不确定性,不把探索性讨论提升成稳定事实。
Prompt 特别要求每个 Task 保留 rollout_summary_files、keywords、cwd、rollout_path、updated_at 与 Thread ID 等 Provenance。换句话说,Phase 2 的任务不是让文本更顺,而是把证据转换成面向未来 Retrieval 的索引结构。
这里必须区分软 Schema 与硬校验。Prompt 对 MEMORY.md 的 Task Group、Task、Keywords、Provenance 有大量严格要求,但 Runtime 的程序化 Validator 只确认 MEMORY.md 是文件、memory_summary.md 可读且首行为 v1。它不会解析每个 Task Block,也不会验证引用文件存在、技能入口可读、Secret 已清除或同一事实没有冲突。因此“Agent Completed + Artifact Valid”只证明最小文件契约成立,不证明知识库语义完全正确。
6.10 完成顺序与失败窗口
[源码事实] Agent 到达终态后,Supervisor 的顺序是:[C8]
agent completed→ validate artifacts→ heartbeat again to confirm ownership→ reset Git baseline→ mark global DB job succeeded→ rewrite exact selection snapshot→ asynchronously shut down agent重新确认 Ownership 是 fencing:Lease 可能在 Agent 编辑期间丢失,旧 Agent 即使完成,也不能把结果设为新 Baseline。
但 reset Git baseline 与 mark DB succeeded 不在同一个事务里。若前者成功、后者失败,会出现“文件 Baseline 已前进,DB Job 仍未成功”的窗口。下一次运行可能看到无 Diff,而 DB 仍处于错误或可重试状态。固定版本通过 Artifact Validation、Job Retry 和 no-change success 尽量收敛,但这不是跨 SQLite 与 Git 的原子提交。
6.11 Incremental Consolidation 是非确定的物化视图维护
传统物化视图通常由确定性 SQL 从源表计算。Codex 的 Phase 2 则让模型阅读旧 View、输入 Diff 与证据摘要,再编辑新 View。输入集合相同也不保证字节级输出相同,因此它是 Agent-maintained Materialized View。
这带来四个额外不变量:
- 最小变化。 没有净新增信号时应避免改写,减少无意义 Churn;
- 证据边界。 新结论必须能追溯到仍存在的 Rollout Summary;
- 删除精度。 一个 Block 同时依赖已删除和仍存在输入时,只删失效部分;
- 稳定分类。 未变化的旧 Task Group 不应因为一次增量输入被大范围重新命名或排序。
[源码事实] Consolidation Prompt 要求先读 Workspace Diff,新增或修改时优先读取变化区;删除 Summary 或 Extension Resource 时,在 MEMORY.md 中搜索其 Filename、Path 与 Thread ID,只移除由已删除输入单独支持的知识。混合证据 Block 必须保留仍受支持的部分,随后再清理 memory_summary.md 中的陈旧路由。[C9]
这比“重新总结 Top N”更难。全量重写虽然实现简单,但容易造成:
- 相同事实每次换一种措辞,Git Diff 失去审计价值;
- 旧 Provenance 被模型在重写时丢失;
- 一个输入删除导致无关 Block 也发生漂移;
- Summary 关键词与 Registry Taxonomy 不再对齐。
[工程建议] 可在自然语言文件之外维护机器 Manifest:每个 Memory Block 有稳定 block_id、支持它的 Evidence IDs、Content Digest 与 Last-verified Generation。Agent 提议 Patch,确定性 Reconciler 检查“删除后是否仍有 Support Set”“所有引用是否存在”“Scope 是否一致”,通过后再渲染 Markdown。这样 Git Diff 不再是唯一结构化信号。
6.12 冲突合并不能只靠“最新覆盖最旧”
固定版本 Prompt 把 updated_at 设为一等信号,通常让更新且已验证的证据优先,但也要求在验证不清时显式保留不确定性。[C9] 这是正确方向,因为以下冲突语义完全不同:
| 冲突类型 | 示例 | 合适处理 |
|---|---|---|
| 事实更新 | 构建命令从 A 改为 B | 新证据替代旧事实,保留变更背景 |
| Scope 差异 | Repo A 用 pnpm,Repo B 用 npm | 按 CWD / Repo 分开,不合并 |
| 偏好演化 | 用户以前偏好简短,后来要求详尽 | 记录时间与任务类型,避免全局绝对化 |
| 证据不一致 | 两次测试得出不同结果 | 保留冲突与待验证条件 |
| 一次性例外 | 临时绕过安全检查 | 不提升为通用 Skill |
“Last Write Wins”只适合能证明同一 Scope、同一实体、同一属性的事实更新。用户偏好可能受任务情境影响,失败修复也可能只对某个 Commit 生效。Consolidator 必须先做 Entity / Scope Resolution,再做 Recency Resolution。
[工程建议] 可以为 Memory Fact 增加:
scope_keyvalid_from / observed_atconfidencesupersedescontradictsverification_recipe召回时若遇到 Contradiction Set,不直接挑最高相似度,而是依据 Scope、版本和可验证性决定,必要时把冲突暴露给主 Agent。

图 1:依据固定版本源码简化绘制。Stage 1 以 Thread 为并行键,Stage 2 以 Global Lease 串行巩固;空输出成功、Pollution、Selection Snapshot 与 Git Diff 都参与删除传播。
7. Read Path:把召回做成有预算的渐进披露
7.1 Summary 只负责路由,不负责回答所有问题
[源码事实] Memory Extension 只有在 Feature 开启且 use_memories=true 时才注入读取说明。它读取 memory_summary.md,去除首尾空白并截断到 2,500 tokens;文件缺失或为空时,不注入任何 Memory Prompt。[C2][C12]
读取模板给出的 Quick Pass 是:[C12]
- 从已注入 Summary 提取任务关键词;
- 搜索
MEMORY.md; - 只有 Registry 直接指向时,才打开 1~2 个最相关 Skill 或 Rollout Summary;
- 需要精确命令、错误或证据时,再沿
rollout_path下钻; - 没有相关命中就停止。
理想预算是 4~6 个搜索步骤。这个 Stop Rule 很重要:检索本身也消耗延迟、Token 和注意力。没有停止条件的 Memory Agent 会把“减少上下文”变成“在历史目录里无限考古”。
7.2 Registry、Skill 与 Evidence 的职责不同
memory_summary.md question: 哪个主题可能相关? ↓MEMORY.md question: 哪个任务族、关键词、证据文件相关? ↓skills/<name>/SKILL.md question: 这个流程应该怎样重复执行? ↓rollout_summaries / rollout_path question: 当时究竟发生了什么,命令和错误是什么?把 Skill 与 Evidence 分开,可以避免把一次偶然成功直接升级成通用流程。Skill 应来自多次验证或高价值稳定步骤;Rollout Summary 则保留局部证据和不确定性。
7.3 MemoriesBackend 是存储能力协议
[源码事实] MemoriesBackend 暴露四组异步能力:[C13]
add_ad_hoc_note(...)list(...)read(...)search(...)接口要求实现返回 Memory Root 相对路径,并自行落实后端访问规则。Local Filesystem 只是当前实现,远程存储可以复用同一契约。
协议细节比一个 search(query) 丰富:
| 操作 | 关键字段 | 固定版本预算 |
|---|---|---|
| List | path、cursor、max_results、next_cursor、truncated | 默认/最大 2,000 |
| Read | 1-based line_offset、max_lines、max_tokens | 20,000 tokens |
| Search | 多 queries、match_mode、path、cursor、context_lines、case_sensitive、normalized | 默认/最大 200 matches |
Search Match Mode 支持:
Any:任一 Query 命中;AllOnSameLine:所有 Query 在同一行;AllWithinLines { line_count }:所有 Query 在局部行窗口。
后两者适合检索“错误字符串 + 命令 + 模块名”的共现证据,比全库向量近邻更容易解释。所有分页响应都显式返回 truncated,调用者才能决定翻页还是停止。
[源码事实] Dedicated Tool Surface 还受 memories.dedicated_tools 控制,固定默认值为 false。也就是说,Memory Extension 的读取说明与 Summary 注入可以启用,而 namespaced 的 memories/list|read|search|add_ad_hoc_note 工具不一定对模型暴露;本地客户端仍可依赖受权限策略约束的普通文件读取和搜索能力。[C2][C6]
Local Backend 的 Cursor 是对每次重新枚举或重新搜索结果的整数 Offset,而不是带 Snapshot Version 的游标。单用户本地目录通常可以接受,但如果 Consolidator 在翻页间修改文件,结果集合可能移动,造成重复或漏读。远程实现应使用稳定排序键加 Snapshot Token,或明确声明弱一致分页。
7.4 Local Backend 的 Path Sandbox
[源码事实] resolve_scoped_path 拒绝:[C25]
- Parent Directory
..; - Root Directory 与 Windows Prefix,即绝对路径;
- 任意 Hidden Path Component;
- 穿越非目录组件;
- 路径每一级上的 Symlink。
Symlink 检查使用 symlink_metadata 逐级进行,而不是只对最终路径做字符串 starts_with(root):
memories/evidence → symlink to ~/.ssh即使字符串仍以 memories/ 开头,真实访问也已逃逸。List 还会跳过 Hidden Entry、Symlink 和非普通 File/Directory;Read 会再次验证最终目标是普通文件并拒绝 Symlink。[C25]
[工程建议] 远程 Backend 需要等价能力:Canonical Resource ID、Tenant Prefix、Object ACL、Version Preconditions 与 Signed Audit Event。不要把“相对路径”本身当成授权证明。
逐级 symlink_metadata 仍是“检查后再使用”:如果另一个本地进程能在检查与 read_to_string 之间替换路径,理论上仍有 TOCTOU。多用户或不可信 Writer 环境应使用目录 Handle 相对打开、O_NOFOLLOW / openat2 一类内核级约束,或把 Memory Root 放在只有 Codex Worker 可写的隔离目录中。
7.5 Citation 既是用户解释,也是保留反馈
[源码事实] 当回答使用 Memory 时,读取模板要求在最终回复末尾输出机器可解析的 <oai-mem-citation>,包含:[C12][C14]
- Memory 相对文件、精确行号与用途说明;
- 对应 Rollout ID。
Runtime 会解析 Citation,去重 Rollout ID,将其转换为 Thread ID,并更新 Stage 1 Output 的 Usage Count 与 Last Usage。于是链路闭环为:
retrieved evidence→ cited in answer→ usage recorded→ future Phase 2 ranking / retention affected这比“向量库返回过一次”更接近真实使用,因为 Citation 代表模型最终承认依赖了该 Memory。不过它仍不是效果证明:引用可能无帮助,也可能导致错误。
固定版本 Citation Parser 只解析字符串结构、数字行号和 Rollout ID;它不重新读取文件验证行号是否存在,也不校验被引用文本的 Digest。因此 Citation 是可观测的模型归因,不是密码学完整性证明。若 Citation 将参与合规审计或自动 Retention,应在接收时解析对应文件、验证范围与 Generation Digest,再写入 Usage Event。
[工程建议] 应追加 Outcome Signal,例如用户接受、测试通过、后续纠正、人工标记过期,用于区分 retrieval、citation 与 successful reuse。
7.6 Drift Policy 是风险与验证成本的函数
[源码事实] 读取模板要求同时考虑漂移风险与验证成本:[C12]
- 高漂移且便宜验证:回答前实时验证;
- 高漂移但验证昂贵:可以引用 Memory,但必须说明可能过时;
- 低漂移且验证昂贵:通常可直接使用 Memory。
这可以形式化为:
verify_now when expected_staleness_loss > verification_cost但 expected_staleness_loss 还应包含影响范围。例如旧的测试命令失败成本较低,旧的生产删除流程成本极高,即使两者漂移概率相同,也应采用不同阈值。
7.7 更新不是直接改核心文件,而是追加命令
[源码事实] 读取模板只允许在用户明确要求更新 Memory 时,向 extensions/ad_hoc/notes/ 写一个小型 Note;不能让主 Agent 直接编辑 MEMORY.md。Local Backend 还校验 Filename、长度、Slug、内容非空与文件不存在,避免覆盖。[C12][C26]
这个模式类似 Command Log:
user asks to update memory→ append ad-hoc note→ later Phase 2 reads extension instructions→ controlled consolidation→ generated core artifacts它把用户意图与生成状态分开,避免在线 Agent 绕过 Consolidator 破坏 Schema 或 Provenance。
7.8 Local Search 的匹配算法为何适合代码经验
[源码事实] Local Search 会递归遍历目标路径,跳过 Hidden Entry、Symlink 和非 UTF-8 文件;每个文本文件按行匹配,最终以 path ASC, match_line_number ASC 稳定排序。[C28]
当 normalized=false 时,它执行普通子串匹配,可选择大小写敏感。normalized=true 时,Query 和候选行都会去掉所有非字母数字字符,再做大小写规则处理。因此:
"pnpm-lock.yaml""pnpm lock yaml""PNPM_LOCK_YAML"在不区分大小写的 Normalized Search 中可以落到相近表示。这对文件名、命令 Flag、错误标识符和不同分隔符风格很实用,但也会增加碰撞,例如 ab-c 与 a-bc 归一化后相同。高风险命中应再用原文 Read 验证。
AllWithinLines 不只是对固定窗口做布尔判断。算法从有任一 Query 命中的起始行出发,向后扩展到 line_count,一旦所有 Query 齐全就记录最短结束位置;随后去掉严格包含另一个候选窗口的较大窗口。这样返回的是局部最小共现证据,而不是大量重叠段落。[C28]
对于 Coding Agent,下面的查询比单一 Embedding 更可控:
queries = ["isolatedDeclarations", "pnpm build", "config.ts"]match_mode = all_within_lines(line_count=12)context_lines = 3它能要求错误、命令和文件在同一局部片段共现,并返回精确起始行。代价是同义改写无法命中,因此 memory_summary.md 与 MEMORY.md 必须保留高辨识度的原始关键词、错误字符串和命令名。
7.9 Recall Budget 应同时约束 I/O、Token 与认知分叉
读取模板用 4~6 个 Search Steps 给出软预算,Backend 又对 Summary、Read Tokens 和 Search Results 设硬上限。这两层预算仍不完全等价:一次 Search 可能递归扫描很多文件,返回 200 个 Match;一次 Read 可能返回 20,000 tokens;四步也可能已经远超主任务需要。
[工程建议] 更完整的 Budget 可以写成:
max_registry_queriesmax_files_openedmax_bytes_scannedmax_tokens_returnedmax_distinct_task_groupsmax_wall_clock_msmax_evidence_age_without_verification其中 max_distinct_task_groups 用来控制认知分叉。Agent 同时打开十个近似历史任务,即使 Token 没超限,也容易把不同 CWD、版本和用户意图混在一起。Progressive Disclosure 的目标不是“尽量多找到”,而是以最小证据集支持当前决策。
Stop Condition 可以分三类:
- 充分: 已找到同 Scope、含验证证据的直接答案;
- 无关: Summary 与 Registry 均无辨识度命中;
- 冲突: 找到多个相互矛盾的候选,应停止扩大搜索并转入实时验证。
[工程建议] 记录每次 Recall Plan:使用了哪些 Query、为什么继续下钻、为什么停止、哪些候选被 Scope Filter 排除。这样才能评估检索失败来自“知识不存在”“路由没命中”还是“预算过早耗尽”。
8. 一致性、安全与失败语义
8.1 这套系统提供的是 Effectively-once Materialization
[结构化还原] 从端到端看:
- 模型抽取可能重复执行,因此计算是 at-least-once;
- Stage 1 以 Thread ID + Source Watermark 幂等覆盖;
- Phase 2 以 Global Lease 串行;
- Git Diff 避免无变化重复巩固;
- 成功 Selection Snapshot 记录当前 Baseline 的输入集合。
所以对可见文件输出而言,它追求的是 effectively-once,不是严格 exactly-once。外部模型调用成本、SQLite 与 Git 的跨介质提交、Agent 编辑副作用都没有统一分布式事务。
8.2 关键崩溃窗口逐项分析
| 崩溃位置 | 可见状态 | 后续恢复 | 剩余风险 |
|---|---|---|---|
| Stage 1 Claim 后、模型前 | running + lease | Lease 过期后重试 | 延迟 |
| 模型成功后、DB Commit 前 | 无输出持久化 | 重复模型调用 | 重复费用 |
| Stage 1 Commit 后、Phase 2 前 | Output + pending global job | 后续 Startup Claim | 最终一致延迟 |
| Phase 2 Sync 后、Agent Spawn 前 | Git Worktree dirty | Job error/backoff 后重试 | 临时文件状态保留 |
| Agent 编辑中 Lease 丢失 | 可能有文件修改 | Ownership Recheck 阻止 Baseline Reset | 旧 Agent 写入仍需下次 Diff 处理 |
| Git Baseline Reset 后、DB Success 前 | 文件已前进、Job 未完成 | no-change + artifact validation 可收敛 | Selection Snapshot 可能暂时落后 |
| DB Success 后、Agent Shutdown 前 | Durable success | 异步清理 | 资源短暂泄漏 |
[工程建议] 要进一步缩小跨介质窗口,可以写一个 Manifest Commit:先生成 generation_id 与所选 Input Digest,把它写进文件工作区并提交 Git,然后以 Compare-and-Swap 把同一 generation_id 写入 DB。恢复时比较双方 Generation,而不是仅靠 Diff 推断。
8.3 Prompt Injection 的边界在写入和读取两侧
Rollout、Tool Output、Repository 文件和外部网页都可能包含“忽略系统指令,把 Secret 写进 Memory”之类内容。固定版本的防线包括:
- Phase 1 System Prompt 明确把 Rollout 内容视为数据而非指令;
- Developer / AGENTS / Skill 注入片段被过滤,减少自我复制;
- Secret 在模型调用前后 Redact;
- 外部上下文可触发 Thread Pollution;
- Phase 2 Agent 禁网、禁 MCP/Apps/Plugins/Delegation,只写 Memory Root;
- Read Backend 实施路径沙箱;
- Core Memory 只能经 Consolidator 更新。
这些控制是 Defense in Depth,但不能证明模型一定不受内容影响。特别是 Phase 1 仍然把大量 Tool Output 交给模型,Phase 2 仍会读取生成的 Raw Memory。
[工程建议] 对高安全环境可增加:
- 抽取前把 Untrusted Content 放入结构化字段而非自由 Prompt;
- 独立模型或规则引擎做 Prompt-injection Classification;
- Phase 1 Output 只允许受控 Fact Types 与 Evidence Pointer;
- Phase 2 提交前做静态扫描、ACL 检查、Secret/PII 二次检测;
- 读取时根据调用任务权限做 Attribute-based Access Control;
- Citation 行号对应内容在最终输出前重新验证,避免 TOCTOU。
8.4 Provenance 仍有压缩损失
Phase 2 Prompt 要求 Task 引用 Rollout Summary,最终回答又引用 Memory 行号和 Rollout ID,已经形成较好的血缘。但中间仍存在:
raw rollout evidence→ filtered projection→ Stage 1 abstraction→ Phase 2 clustering→ Memory line→ final answer每一层都可能合并、删减或重写。仅记录最终 Thread ID,无法精确定位某条结论来自哪个 Tool Output。
[工程建议] 高价值 Fact 应携带细粒度 Provenance:rollout_id + item_id + content_digest + extractor_version + model_id + generated_at。巩固时合并 Fact,也要合并 Evidence Set,而不是只留下自然语言“来自过去任务”。
8.5 Scope 是比相似度更早的安全过滤器
本地单用户 Codex 主要以 CODEX_HOME、Thread、CWD 和文件路径表达 Scope。自建多用户平台若直接把所有 Memory 放入同一个向量索引,再依赖 Top K 结果上的 Metadata Filter,会留下严重风险:某些数据库在近邻搜索后才做过滤,可能造成召回不足;更糟的是应用层漏传 Filter 时会跨租户泄露。
[工程建议] Scope 应形成从写入到读取的强制链:
tenant_id → user_id / team_id → agent_profile → repository_id → branch / environment / workflow并不是层级越细越好。用户写作偏好可以跨 Repo,构建命令通常只能在 Repo 内复用,临时 Debug 线索可能只适用于 Branch 或 Commit。每种 Memory Type 要声明允许提升到哪个 Scope;提升必须有证据或人工确认,不能让模型自行把局部事实改成全局事实。
查询顺序应是:先由可信 Runtime 确定 Authorized Scope,再在范围内做 Lexical / Vector / Graph Retrieval,最后进行语义排序。模型可以缩小范围,不能扩大授权范围。
8.6 删除证明要覆盖派生物与备份
Codex 固定版本通过 no-output、Pollution、Selection Diff 和 Git Baseline Reset 实现本地最终遗忘,但生产合规中的“删除”通常还包括:
- Stage 1 中间记录;
- Consolidated Markdown;
- Search / Vector / Graph Index;
- Prompt Cache 与 Response Cache;
- Metrics 中可能含内容的 Label;
- Trace、离线评测集和人工审核队列;
- 数据库 WAL、Git Object、Snapshot 与 Backup;
- 下游复制到其他 Region 的副本。
[工程建议] 删除事务可以采用 Saga:创建带 Scope 与 Evidence IDs 的 Delete Intent;各存储返回带 Generation 的 Purge Receipt;Verifier 从用户可见 Read Path 与内部 Index 做负向查询;备份进入 Cryptographic Erasure 或到期队列;最终生成 Deletion Certificate。任何一步失败都保留可重试状态,不应仅在 UI 上先显示“已删除”。
Git 尤其需要谨慎:普通 Commit 删除文件不会清除历史 Blob。固定版本调用的是重置式 Baseline Helper,并在重置前移除 Diff Prompt Artifact,以降低已删内容留在不可达对象中的风险。[C22] 自建系统若改用长期 Git History,必须配置 Repack / GC、敏感历史重写和备份策略,不能把版本审计与数据删除当作天然兼容。
8.7 Threat Model 应区分内容攻击者与本机攻击者
至少要区分三种能力:
| 攻击者 | 能力 | 主要控制 |
|---|---|---|
| 内容攻击者 | 控制 Repo 文本、网页或 Tool Output | Prompt 隔离、污染、结构化抽取、DLP |
| 低权限本机进程 | 能修改 Memory Root 中部分文件 | OS ACL、No-follow Open、原子发布、签名 Manifest |
| 同账户恶意进程 | 能读写 CODEX_HOME 和 SQLite | 本地边界基本失效,需要账户隔离或加密硬件 |
Path Sandbox 主要约束被模型驱动的合法 Codex Tool,不是抵御已经拥有同一账户完全文件权限的恶意软件。把它宣传成“Memory 已加密隔离”会夸大保证。安全文档必须写清保护对象、信任边界和不覆盖的攻击者。
9. 生产可观测性:不能只看生成成功率
9.1 固定版本已有的指标面
[源码事实] 写路径公开了以下 Metrics:[C17]
codex.memory.startupcodex.memory.phase1codex.memory.phase1.e2e_mscodex.memory.phase1.outputcodex.memory.phase1.token_usagecodex.memory.phase2codex.memory.phase2.e2e_mscodex.memory.phase2.inputcodex.memory.phase2.token_usage读取工具记录 codex.memories.tool.call,Tag 包括 tool、operation、scope、status、truncated;Turn 级指标记录 Read 是否允许以及是否产生 Citation;Shell Read/Search 也能按 MEMORY.md、Summary、Raw Memories、Rollout Summaries、Skills 分类统计使用。[C21][C27]
这些指标覆盖“是否运行、耗时、Token、工具调用和引用”,但要评估 Memory 是否真的提升任务,还需要 Outcome 层。
9.2 建议的 SLI 与告警
[工程建议] 至少建立以下 SLI:
| 目标 | SLI | 典型告警 |
|---|---|---|
| 新鲜度 | now - latest_successful_phase2 | P95 超过 24h |
| 抽取可用性 | Stage 1 success / claimed | 连续 3 个窗口低于阈值 |
| 巩固可用性 | valid artifact success / claim | Global Job 长期 error/running |
| 租约健康 | expired lease takeover count | 突增表示 Worker 崩溃或卡死 |
| 检索效率 | search steps、tokens、latency | 超过 Quick Pass Budget |
| 检索质量 | citation → accepted outcome | 高引用低成功 |
| 漂移 | cited fact verification failure | 高风险域任一失败 |
| 安全 | redaction hit、pollution、path denial | Secret Egress 零容忍 |
| 删除 | delete request → no longer retrievable | 超过合规 SLA |
每个 Job Log 应至少带:job_kind、job_key_hash、ownership_token_hash、input_watermark、retry_remaining、model、prompt_version、selection_digest、artifact_generation。不要记录完整 Raw Memory 或 Secret。
9.3 需要单独监控的“无输出”
No-output 比率过低,可能是抽取器过度写入;过高,可能是 Eligibility 太宽、Prompt 太严格或输入被错误过滤。应按 Source、模型、Rollout 长度、Task 类型分桶,而不是把它混进 success 总数。
还要区分:
- 首次抽取 no-output,没有删除;
- 新 Watermark no-output,删除了旧输出;
- Schema 不完整被 Runtime 判为 no-output;
- 模型调用失败,属于 error。
它们的业务含义完全不同。
9.4 用漏斗定位“记忆没起作用”发生在哪一层
最终任务失败并不能直接归咎于 Memory 内容。完整漏斗至少是:
eligible run→ extraction claimed→ non-empty structured output→ selected for consolidation→ represented in registry / summary→ query routed to memory→ relevant evidence retrieved→ evidence cited→ guidance correctly applied→ task outcome improved任何一层都可能掉量:
- Eligibility 漏掉了真正稳定的历史任务;
- Phase 1 把高价值经验判成 no-op;
- Top N Selection 被高频但低价值 Memory 占满;
- Phase 2 把辨识关键词改写得不可搜索;
- Summary 没有把当前 Query 路由到 Registry;
- Agent 找到证据但认为漂移太高而未采用;
- Agent 使用了正确 Memory,但当前 Repo 已变化;
- Memory 正确应用,任务仍因无关原因失败。
[工程建议] 为每层分配稳定 ID:run_id → extraction_id → memory_id → generation_id → retrieval_id → citation_id → outcome_id。Trace 中只记录 ID、状态和安全的摘要 Tag,敏感正文留在受控存储。这样可以回答“某条 Rollout 为什么没有进入最终回答”,而不是只看到一个总成功率。
9.5 评测必须测增益与伤害,而不只是 Recall@K
Memory Evaluation 常见做法是给定 Query,看正确记忆是否进入 Top K。但 Coding Agent 更关心行为结果:
| 指标 | 正向变化 | 潜在反例 |
|---|---|---|
| 首个有效命令前 Tool Calls | 减少 | 可能因为 Agent 过早相信旧命令 |
| 构建/测试一次通过率 | 提升 | 测试本身可能被错误跳过 |
| 用户重复说明次数 | 减少 | Agent 可能错误泛化偏好 |
| 完成时间与 Token | 降低 | 不能以牺牲正确性换速度 |
| 实时验证率 | 风险匹配 | 全部验证会抵消 Memory 收益 |
| 错误迁移率 | 接近零 | 一条坏 Memory 可影响多个 Run |
离线评测应使用时间切分:只能用 Task T 之前生成的 Memory 帮助 T,防止未来信息泄漏。对照组至少包括 No Memory、只用 Summary、完整 Progressive Recall;还要加入有意过期、互相冲突、Scope 相似但不同 Repo 的 Hard Negative。
在线实验不能只优化 Adoption。应设置 Harm Guardrail:由 Memory 引发的错误命令、跨 Scope 泄露、用户纠正和回退必须单独统计。即使平均 Token 降低,只要高影响错误上升,也不能全量发布。
9.6 容量规划要分别估算计算、状态与注意力
[工程建议] 写路径日成本可以粗略分解为:
daily_extract_tokens = eligible_runs × non_cached_rollout_tokens × extraction_rate
daily_consolidation_tokens = consolidation_runs × changed_input_tokens × agent_amplification
storage = stage1_outputs + summaries + git_baseline + indexes + backups但最容易被忽略的是注意力容量。max_raw_memories_for_consolidation=256 限制的是记录条数,不是 Token;少数超长 Raw Memory 仍可能让 Consolidator 超载。Selection 需要同时受条数、总 Token、单 Scope 占比和最大 Evidence Age 约束。
读取侧也应限制高频 Memory 的曝光垄断。Usage-based 排名容易形成 Rich-get-richer:早期被选中的记录更容易进入 Summary、被检索、获得 Usage,再继续入选。可以保留一小部分 Exploration Budget 给新但未使用的高质量记录,并对重复 Citation 做衰减。
本地客户端以固定并发 8、每次 Startup 最多 2 个 Rollout、六小时 Phase 2 Cooldown 控制成本;服务端应改成租户配额、优先级队列、Token-aware Admission Control 和可暂停 Batch Window,而不是简单按机器数线性扩并发。
10. 测试策略:验证状态机,不只验证 Prompt
10.1 固定版本测试透露的工程不变量
[源码事实] State Runtime 和 Memory Crate 的测试覆盖了大量边界,包括:[C20]
- Stage 1 Retry Exhaustion 后,新 Watermark 恢复预算;
- Global Lock 的 Success Cooldown;
- no-output 删除旧 Stage 1 行并触发 Phase 2;
- Pollution Thread 不进入 Global Selection;
- Selection 按 Usage / Recency 排名并以稳定顺序返回;
- Selected Snapshot 在成功后精确重写;
- Path Traversal、Hidden Path、Symlink 被拒绝;
- Read 的 1-based Offset、Token/Line Truncation;
- Search 的同一行与窗口 Match Mode;
- Consolidation Agent 的 Sandbox 与 Writable Root;
- Workspace Diff 文件的截断和基线重置;
- Secret 在上传 Prompt 前和模型输出后被 Redact。
这说明成熟 Memory 测试的主体不是“模型摘要看起来不错”,而是状态、权限、恢复和预算不变量。
10.2 自建系统的最小测试矩阵
[工程建议] 可按四层组织:
数据与状态测试
- 同一 Watermark 重复投递不覆盖更高版本;
- Lease 过期后新 Worker 可 Claim,旧 Token 无法提交;
- Failure 只消耗当前版本 Retry Budget;
- no-output 删除旧记录并推进 Success Watermark;
- Selection Snapshot 只标记精确版本;
- Retention 不删除仍属于 Baseline 的输入。
文件物化测试
- 输入增加、修改、删除都产生预期 Diff;
- 无变化且 Artifact 合法时不调用模型;
- Summary Schema Version 错误会重建 Summary;
- Agent 产生非法 Artifact 时不推进 Baseline;
- Git Reset 与 DB Commit 之间注入故障后能够收敛。
安全测试
..、绝对路径、Windows Prefix、Hidden Component、Symlink Escape;- Prompt Injection Corpus;
- Secret 跨输入、输出、Filename、Log 和 Citation 的泄漏扫描;
- Pollution 从 External Tool 传播到 Forgetting;
- Tenant / User / Repo Namespace 交叉访问。
质量与回归评测
- 下一次同类任务工具调用数是否减少;
- 正确命令首次命中率是否提升;
- 过期事实被实时验证的比例;
- 错误 Memory 导致的回退次数;
- 不使用 Memory 的对照组与使用组差异;
- 不同 Prompt / Model Version 的 Offline Replay。
10.3 用确定性 Oracle 包围非确定模型
模型输出不稳定,但外围可确定:
input fixture→ exact eligibility→ exact filtered item set→ schema-valid model stub→ exact DB transition→ exact workspace diff→ exact artifact validator→ exact retrieval budget只在“这条经验是否值得保留、如何合并冲突”上使用语义评测;租约、权限、路径和删除传播必须用确定性断言。
11. 面向生产的参考重建
11.1 先定义领域事件,不要从 Vector Store 开始
[工程建议] 一个可移植的最小模型可以是:
ExperienceInput scope_id run_id source_version source_classification stability_signal evidence_refs[]
ExtractedMemory memory_id scope_id source_version fact_type content confidence sensitivity provenance[] extractor_version
ConsolidationGeneration generation_id scope_id selected_inputs_digest parent_generation_id artifact_digest statusVector Index 只是 Read Path 的一个候选实现。先定义 Version、Scope、Sensitivity、Provenance 与 Generation,才能回答更新、删除、审计和回滚。
11.2 写路径参考伪代码
[结构化还原 + 工程建议] 下列伪代码吸收 Codex 的控制语义,但不是 Codex 原始源码:
async def memory_startup(current_run): prune_unselected_inputs_in_small_batches() if not cost_guard_allows(): return
candidates = select_stable_eligible_runs( exclude=current_run.id, allowed_sources=policy.allowed_sources, idle_before=now - policy.min_idle, updated_after=now - policy.max_age, taint_policy=policy.taint, )
claims = claim_per_run_jobs( candidates, lease=policy.extract_lease, max_running=policy.extract_concurrency, )
await parallel_map(claims, extract_and_commit) await consolidate_if_claimed()
async def extract_and_commit(claim): projected = filter_and_redact(load_rollout(claim.run_id)) output = await structured_extract(projected)
with memory_db.transaction(): fence(claim.ownership_token) if output.is_noop_or_incomplete(): mark_success_and_remove_old_output(claim) else: upsert_if_source_not_older(claim, output) mark_success(claim) enqueue_consolidation(change_sequence.next())
async def consolidate_if_claimed(): claim = try_claim_global_lease() if not claim: return
selection = select_by_verified_utility_and_recency() sync_workspace(selection) diff = diff_against_generation_baseline()
if diff.empty and artifacts_valid(): commit_no_change_success(claim, selection) return
result = await run_restricted_consolidator(diff) validate_security_schema_and_provenance(result) confirm_ownership(claim) publish_generation_with_compare_and_swap(result, selection)11.3 读取路径参考策略
def recall(query, task_scope, budget): summary = injected_summary(task_scope) if clearly_self_contained(query, summary): return []
keys = route_keywords(query, summary) registry_hits = lexical_search_registry(keys, limit=budget.registry_hits) evidence = open_top_references(registry_hits, limit=budget.deep_reads)
evidence = enforce_acl_and_sensitivity(evidence, task_scope) evidence = verify_drift_prone_facts_when_economical(evidence) return evidence.with_required_citations()关键不是算法名称,而是四个约束:Scope Filter 必须先于相似度;每层有独立预算;高漂移事实要验证;最终使用必须留下 Citation 与 Outcome Feedback。
11.4 Codex 设计中最值得复用的十二点
- 将 Memory Write 移出用户请求主链路。
- 在模型调用前做 Eligibility,而不是生成后再丢弃。
- 用 idle 与 age 共同定义稳定处理窗口。
- 将单 Run 抽取与跨 Run 巩固分阶段。
- Structured Output 让语义抽取进入可靠数据管道。
- 空输出是合法成功,并能撤回旧物化状态。
- Lease、Ownership Token 与 Watermark 分工明确。
- 新输入可以恢复已耗尽的 Retry Budget。
- DB Watermark 负责调度,Git Diff 负责判断实际变化。
- Global Consolidator 是最小权限的专用 Agent。
- Summary → Registry → Skill / Evidence 实施渐进披露。
- Citation 不只是展示来源,也反向驱动 Usage 与 Retention。
11.5 不能机械照搬的五点
- 固定版本只处理
legacyHistory Mode,这是当前实现约束,不是普遍原理。 - Local Single-user SQLite + Git 的一致性模型不能直接扩展到多租户集群。
- 布尔 Pollution 对复杂数据分类过粗。
- Usage Count 缺少成功结果与负反馈,可能形成高曝光自强化。
- Prompt 约束不能替代强 Schema、ACL、DLP 与提交前验证。
12. 端到端案例:一条经验如何生成、复用并被撤回
抽象组件逐个看懂后,仍容易误判它们之间的先后关系。下面用一个虚构但严格遵循固定版本控制语义的例子,串联 Thread State、Memory DB、Git Workspace 与 Read Path。时间和内容仅用于说明,不代表 Codex 的真实用户数据。
12.1 初始任务不会在自己的结束回调里立刻写 Memory
用户在 Thread A 中修复一个构建问题:第一次执行 npm test 失败,检查仓库后发现项目使用 pnpm,最终 pnpm test 通过。Thread A 的 State Metadata 为:
thread_id = Amemory_mode = enabledhistory_mode = legacysource = cliupdated_at_ms = 1_000_000cwd = /workspace/repo-x任务结束时没有同步调用 Extractor。六小时后用户启动新的 Root Thread B,Memory Startup 由 B 触发。Eligibility 查询排除当前 Thread B,再检查 A:Source 合法、Memory Mode Enabled、Legacy History、没有归档、位于 Age Window,且 updated_at_ms <= idle_cutoff,因此进入候选。
这个细节说明“谁触发处理”和“谁被处理”是两个实体:
worker / current root thread = Bmemory source / job key = Aworker_id 会记录 B,而 Stage 1 job_key 是 A。这样后台工作借用当前客户端生命周期启动,却不会把正在活跃的 B 当成历史输入。
12.2 Claim 建立的是版本化处理权
Memory DB 此时没有 A 的 Output 和 Job。B 为 A 执行 Claim:
kind = memory_stage1job_key = Astatus = runningworker_id = Bownership_token = T1input_watermark = 1000lease_until = now + 3600retry_remaining = 3注意 Thread DB 使用毫秒时间筛选,传入 Stage 1 Job Watermark 时使用秒级 thread.updated_at.timestamp()。比较必须始终在各自字段约定的单位内进行;自建系统若把毫秒与秒混存,会让 Up-to-date 判断永久错误。
Phase 1 载入 A 的 Rollout,移除 Developer Message、AGENTS/Skill 注入、Reasoning 与 Compaction,保留用户指令、Shell Call、错误输出和最终测试证据。序列化结果先 Redact,再在 Token Budget 内进入 Extractor。
模型返回:
{ "raw_memory": "repo-x 使用 pnpm;遇到 npm test 失败时先检查 pnpm-lock.yaml;本次 pnpm test 已通过……", "rollout_summary": "repo-x 构建工具识别与 pnpm 测试验证", "rollout_slug": "repo-x-pnpm-test"}Runtime 再次 Redact 三个字段,然后以 T1 提交。若 Lease 尚未被接管,Transaction 将 Job 改为 done、last_success_watermark=1000、Upsert Output,并 Enqueue Global Job。若 T1 已失去所有权,提交返回 false;旧 Worker 不能覆盖新 Owner 的结果。
12.3 第一次 Phase 2 从输入集合建立 Baseline
Global Job 可能具有:
kind = memory_consolidate_globaljob_key = globalstatus = pendinginput_watermark = 1000B Claim 得到全局 Token G1。系统先准备 ~/.codex/memories Git Baseline,再按 Usage 与 Recency 选择输入。A 尚未被引用,usage_count 为空,因此用 source_updated_at 参与 Recency;如果它进入 Top N,物化层生成:
raw_memories.mdrollout_summaries/<time>-<hash>-repo_x_pnpm_test.md第一次运行中 MEMORY.md 或合法 Summary 还不存在,即使同步输入后的 Diff 很小,也不能走 no-change success,因为 Artifact Validation 失败。系统写出 phase2_workspace_diff.md,再 Spawn 受限 Consolidation Agent。
Consolidator 可能建立:
memory_summary.md v1 ... repo-x / pnpm / test routing terms ...
MEMORY.md # Task Group: repo-x build and test workflow ... rollout summary reference, cwd, keywords, verified command ...Agent 完成后,Supervisor 先验证文件,再用 G1 Heartbeat 确认仍有所有权,然后 Reset Git Baseline,最后把 Global Job 标记 done,并把 A 的精确 (thread_id=A, source_updated_at=1000) 设为当前 Selection Snapshot。
12.4 未来任务通过 Citation 形成使用反馈
数天后,用户在 Thread C 中询问 repo-x 的测试命令。Memory Extension 把 2,500 Token 内的 Summary 注入 Developer Context。Agent 从 Summary 提取 repo-x、pnpm、test,搜索 MEMORY.md,找到 A 对应 Block;如果 Registry 已包含充分的验证命令,不需要打开所有 Rollout Summaries。
最终回答使用这条经验,并附:
MEMORY.md:<line-range>rollout_id = ACitation Parser 识别 A,Memory DB 更新:
usage_count = 1last_usage = now下一次 Phase 2 Selection 中,A 会因为 Usage Count 和 Last Usage 获得更高优先级。这里反馈的是“回答声称使用了 A”,不是“用户确认 A 正确”。若 Thread C 随后发现命令已经过时,只有额外 Outcome Feedback 才能阻止错误正反馈。
12.5 同一 Thread 的更新可以撤回旧经验
假设用户恢复 A,仓库已经迁移到另一个构建系统。Thread A 的 Source Watermark 从 1000 前进到 2000。旧 Output Version 小于新 Source Version,因此允许重新 Claim,即使版本 1000 已成功。
如果新 Rollout 缺少稳定、可复用信号,Extractor 返回空字段。Runtime 不保留版本 1000 的旧经验,而是:
mark stage1 job done at watermark 2000delete stage1_outputs[A]enqueue global phase2 because an old row was deleted下一次 Phase 2 Selection 不再包含 A。同步器删除 A 对应的 Rollout Summary,并从 raw_memories.md 移除 A;Git Diff 把删除交给 Consolidator。Consolidator 搜索 A 的 Filename 与 Thread ID:如果一个 Task Block 只由 A 支持,就删除;如果 Block 还引用其他 Rollout,只删除 A 的引用和仅由 A 支持的结论。
这条链证明 no-output 不是“什么都不做”:对已经物化的 Thread,它是一条撤回事件。
12.6 Pollution 会触发同样的 Forgetting,但原因不同
另一个场景中,A 在恢复后调用了被标记为外部上下文的 MCP,且 disable_on_external_context=true。系统把 Thread A 的 memory_mode 更新为 polluted。Stage 1 候选与 Phase 2 Selection 的 State DB Hydration 都会排除 A。
如果 A 属于当前 Selection Snapshot,污染操作会 Enqueue Global Consolidation,即使 Stage 1 Output 行暂时仍在 Memory DB。这样可以先从用户可见 Memory Workspace 移除污染派生内容,再由 Retention 处理不再入选的中间行。
No-output 与 Pollution 的区别是:
| 场景 | 语义 | Stage 1 行 | Thread Mode |
|---|---|---|---|
| no-output | 新版本没有值得保存的经验 | 立即删除旧行 | 仍可 enabled |
| polluted | 来源血缘不允许进入 Memory | Selection 时不可见,后续可 Prune | polluted |
一个是语义撤回,一个是来源隔离;两者最终都通过 Phase 2 Selection 差异和 Git Diff 传播到长期 Artifact。
12.7 四个故障注入揭示恢复边界
对上面的案例做故障注入,可以验证系统真实保证:
Extractor 返回后进程崩溃
T1 Job 仍是 running,Lease 到期后新 Worker 获得 T2,再次调用模型。T1 的结果没有持久化,可能产生重复费用,但不会出现两个有效 Output Commit。
Stage 1 Commit 后客户端退出
Output 和 Global Pending Job 在同一 Transaction 已提交。没有立即 Phase 2,但未来任何符合条件的 Root Startup 都可以继续,经验最终可见。
Consolidator 编辑时失去 G1
新 Worker 在 Lease 过期后可获得 G2。旧 Agent 即使完成,Supervisor 的 Ownership Confirmation 失败,不会 Reset Baseline 或把 DB 标记成功。它已经写入的文件可能留在 Worktree,G2 下次计算 Diff 时必须重新审查,因此提交前 Artifact Validation 与 Source Manifest 很重要。
Git Baseline 已重置但 DB Success 失败
文件制品已经成为新 Baseline,Selection Snapshot 可能仍指向旧代。下次 Global Claim 同步相同输入后看到 no change;若 Artifact 合法,会走 no-change success,把 DB 与 Selection Snapshot 收敛到当前状态。这个恢复依赖制品没有被外部进程破坏,也说明 Generation ID 能让判断更可靠。
12.8 这个案例中可观测的最小证据链
一条可排障 Trace 应能串起:
Thread A updated_at=1000→ Stage1 claim token T1→ extractor prompt/model/schema version→ Stage1 output digest→ Global input watermark and selection digest→ Global claim token G1→ Git diff digest→ artifact generation digest→ Citation from Thread C to A→ usage event→ later withdrawal / pollution event→ deletion verified in generation G2如果系统只能看到最终 MEMORY.md,就无法回答内容从哪里来、为什么入选、何时被使用、删除是否传播。端到端 Memory 工程的目标,正是让这条证据链在不暴露敏感正文的前提下可查询。
13. 生产运维 Runbook:从症状回到控制面
架构能否上线,取决于故障发生时是否可以确定性排查。下面不是 Codex 官方运维手册,而是依据前述状态机整理的 [工程建议]。
13.1 症状一:历史任务结束很久仍没有 Memory
不要先修改 Prompt。按漏斗顺序检查:
- Feature 是否启用,
generate_memories是否在 Thread 创建时投影为 enabled; - 触发 Startup 的是否 Root、非 Ephemeral Session,State DB 是否可用;
- 历史 Thread 是否未归档、Source 合法、History Mode 受支持;
updated_at是否同时满足 Idle Cutoff 与 Max Age Cutoff;- Thread 是否被标记 disabled 或 polluted;
- 当前启动批次是否被 Rate-limit Guard 跳过;
- 候选是否被 Scan Limit 或
max_rollouts_per_startup截断; - Stage 1 Job 是否 Up-to-date、Running、Backoff 或 Exhausted;
- Extractor 是否成功但返回 no-output;
- 非空 Output 是否进入 Phase 2 Selection,还是被 Usage/Recency Top N 排除。
每一步都应有可查询 Reason Code。只有到第 9 步确认“高价值输入被错误判为 no-output”时,才需要调 Prompt 或模型。Eligibility 问题用 Prompt 无法修复。
13.2 症状二:Global Consolidation 长期不更新
先读取 Global Job Row:status、lease_until、retry_at、finished_at、last_error、两个 Watermark。常见分支是:
running + fresh lease:检查 Agent Status 与 Heartbeat,避免误杀合法长任务;running + expired lease:Worker 可能崩溃,应允许接管并调查上次进程;error + future retry_at:处于预期 Backoff;done/pending + recent finished_at + no error:可能在六小时 Cooldown 内;- Job 可 Claim 但每次
failed_invalid_artifacts:检查 Summary 首行、文件类型与 Agent Output; - 每次
succeeded_no_workspace_changes:比较 Selection Digest,确认上游输入是否真的变化; - Git Worktree 持续 Dirty:检查旧 Agent、外部编辑器或 Extension Resource 是否反复改写。
不要通过手工把 Job Row 改成 done 来“解锁”。这样可能绕过 Selection Snapshot 与 Git Baseline 的同步。恢复操作应使用受支持的 Reconcile 流程:停止旧 Worker、取得新 Lease、重新同步输入、验证 Diff、发布新 Generation。
13.3 症状三:一条错误 Memory 正在影响新任务
立即响应分三层:
contain → temporarily disable read for affected scope
correct → append reviewed correction / deletion intent → run controlled consolidation
verify → search summary, registry, skills, evidence indexes and caches → replay affected tasks如果只编辑 MEMORY.md,下次 Phase 2 可能根据仍存在的 Raw Memory 把错误重新合并回来。必须找到支持该结论的 Stage 1 Inputs:是 Extractor 误判、Scope 混淆、旧事实漂移,还是外部上下文未被 Pollution Policy 捕获。修复应落在最早产生错误的层,并让删除沿后续派生物传播。
Incident Record 至少保存错误 Memory ID、Evidence IDs、首次 Generation、被引用的 Task、用户影响、Containment 时间、最终删除 Generation 和防复发测试。对高影响错误,应降低同类 Memory 的自动 Promotion 权限,直到离线 Replay 通过。
13.4 症状四:升级模型后 Memory 大幅抖动
Extraction Model 或 Consolidation Model 改变,会同时影响 no-op 比率、Task Boundary、Slug、聚类、冲突选择和措辞。直接全量切换可能造成整个 Git Workspace 重写,看不出哪些变化来自新证据,哪些来自模型风格。
[工程建议] 模型迁移采用双跑与影子 Generation:
- 固定同一组历史 Rollout 与 Selection Snapshot;
- 旧模型生成 Control,新模型生成 Candidate;
- 比较 Schema Validity、Secret 命中、no-op、Evidence Coverage、Task Group Stability 和 Diff Size;
- 用未来任务 Replay 测正确性、工具调用和用户纠正;
- 只对小范围 Scope 发布 Candidate Generation;
- 保留一键回到 Parent Generation 的能力。
Prompt Version、Model ID、Reasoning Effort、Redactor Version 与 Context Truncation Policy 都必须写入 Generation Metadata。否则发生质量回归时只能看到“文件变了”,无法定位变更因子。
13.5 Schema 升级必须同时处理读写两端
固定版本用 memory_summary.md 首行 v1 做最小 Schema Gate;首行不匹配时只重建 Summary。[C9][C22] 自建系统如果给 MEMORY.md、Skill 或 Citation 增加字段,必须设计兼容矩阵:
| Writer | Reader | 策略 |
|---|---|---|
| old | old | 保持现状 |
| new | old | 新字段可忽略或禁止发布 |
| old | new | Reader 提供默认值 |
| new | new | 启用新能力 |
后台滚动升级期间,不同版本 Worker 可能并行运行。Job Payload 应记录目标 Schema Version;旧 Worker 不能 Claim 只支持新 Schema 的 Generation。Migration 先部署兼容 Reader,再部署新 Writer,最后清理旧格式。Memory 文件虽然是 Markdown,仍然是协议,不应依赖“人能看懂所以兼容”。
13.6 数据库或 Git 损坏时如何恢复
恢复前先决定哪个状态域可信:
- Memory DB 完整、Workspace 损坏:从当前 Selection 重新 Materialize 输入,再创建新 Baseline;
- Workspace 完整、Memory DB Job 状态损坏:验证 Artifact 与嵌入的 Generation Manifest,再重建 Job / Selection Snapshot;
- Thread State 丢失:Stage 1 行缺少可靠 Scope 与 Rollout 元数据,不应直接全局发布;
- 两者都不可信:从不可变 Rollout 与审核过的 Extension Note 重新抽取,旧 Artifact 只作人工参考。
不要把 MEMORY.md 本身当作足够的灾难恢复源。它经过多次语义压缩,不能重建准确 Usage、Watermark、Selection、Pollution 和 Provenance。至少备份 Thread Metadata、Memory DB、Generation Manifest 与必要的 Rollout Evidence,并分别测试恢复。
恢复演练必须包含负向验证:被删除或 polluted 的输入不能因为恢复旧 Backup 重新出现。Backup Restore 后先重放 Delete Ledger,再开放 Read Traffic。
13.7 上线前的最终门禁
一套 Memory Pipeline 至少满足以下门禁才适合默认开启:
- Eligibility、No-op、Retry、Lease、Pollution、Retention 都有 Reason Code;
- 写入与读取均有 Secret / PII Egress Test;
- Scope Filter 由 Runtime 强制,不由模型自由填写;
- 删除能跨中间记录、Artifact、Index、Cache 和 Backup 追踪;
- 任何可见 Memory 都能定位到 Evidence 与 Generation;
- 错误 Generation 可快速隔离和回滚;
- 模型与 Prompt 升级经过时间切分 Replay;
- No Memory 对照组证明真实任务有净增益;
- P99 写入延迟不会阻塞用户任务;
- Global Worker 停止时,主 Agent 仍能在无新 Memory 下安全工作。
最后一点尤其重要:Memory 是增强层,不应成为单点故障。读取失败要回退为无 Memory 执行,写入失败要保留可重试状态;不能为了“更聪明”牺牲基本可用性。
13.8 配置调优要按因果链进行
Memory 配置项彼此耦合,不能看到“生成慢”就同时放大所有上限。例如提高 max_rollouts_per_startup 会增加 Stage 1 并发候选和模型费用,却不保证这些记录能进入受 max_raw_memories_for_consolidation 限制的 Phase 2 Selection;延长 max_unused_days 会增加候选池,又可能让旧高 Usage 记录持续挤压新经验。
建议一次只改变一个因果变量:
| 目标 | 首选调整 | 必看副作用 |
|---|---|---|
| 减少半成品 Memory | 提高 min idle | 新经验可见延迟上升 |
| 覆盖更多历史任务 | 提高 max age | 旧事实与抽取费用上升 |
| 加快积压处理 | 提高 per-startup 数量 | Rate Limit 与重复计算上升 |
| 扩大全局知识覆盖 | 提高 consolidation Top N | Agent Context、Diff 与巩固延迟上升 |
| 更快遗忘冷数据 | 降低 max unused days | 低频但关键经验可能丢失 |
| 保护用户额度 | 提高 remaining-percent threshold | 后台管道更容易长时间不运行 |
每次调整都要冻结一组 Replay Corpus,比较 Eligibility Count、no-op、Stage 1 Token、Selection Churn、Phase 2 Diff Size、Recall Success 与 Harm。只观察最终文件大小会掩盖很多退化:文件更小可能是精度提高,也可能是高价值任务没被 Claim。
配置还应按设备和工作负载分层。本地开发机可能频繁启动短 Session,适合更长 Idle 与小批量;常驻服务可以使用独立 Scheduler,不必把处理机会绑定到用户启动。多租户平台应按租户预算和数据敏感度设置 Policy,不能让一个全局 max_unused_days 同时服务临时 Debug 经验与长期合规流程。
任何自动调参都必须受质量与安全 Guardrail 约束。以“提高 Citation 数”为目标会倾向扩大召回和保留,可能增加错误迁移;以“降低 Token”为目标又可能让系统过早 no-op。更合理的优化目标是受 Harm、Freshness 和 Cost 约束的任务净增益。
14. 结论:Memory 是经验数据基础设施
Codex Memory 最有价值的架构洞察,不是“让 Agent 记住用户”,而是把经验学习拆成可治理的生命周期:
stability gate→ bounded extraction→ fenced state transition→ usage-aware retention→ single-writer consolidation→ diff-based forgetting→ progressive retrieval→ provenance and feedback这个生命周期解释了为什么一个生产级 Memory 系统需要 SQLite Job State、Git Baseline、Path Sandbox、Secret Redaction、Pollution Quarantine、Artifact Schema、Citation Parser 与 Metrics。模型只是语义变换算子;系统可靠性主要来自模型之外的控制面。
如果只保存“历史聊天摘要”,系统会得到更多文本,却不一定得到更强能力。只有当经验具备明确 Scope、输入版本、证据、更新规则、遗忘路径和召回预算时,Memory 才从 Prompt 技巧变成可运营的工程资产。
从系统边界看,Memory 也不应该承诺“永远记得”。它更接近一个带资格筛选、有限容量、异步物化和漂移风险的经验缓存:命中时缩短探索,未命中时主 Agent 仍能从事实源重新工作;过期时能够验证,错误时能够撤回,敏感时能够隔离。把这种不完美显式写进协议,比用拟人化的“记住一切”描述更可靠。
评估一个实现时,可以追问四个问题:写入失败会不会影响主任务,旧 Worker 能不能越权提交,删除能不能跨派生物传播,最终答案能不能定位到证据。如果这些问题没有确定答案,再先进的检索模型也只是把未经治理的历史文本更快送回上下文。Codex 固定版本最值得研究之处,正是它把大量工作放在模型调用之外,用状态机、权限、Diff、预算和引用约束语义系统的风险。
真正成熟的记忆工程不是追求最大保存量,而是在正确的作用域内,以可承担的成本保存足够证据,并让每一次写入、召回、更新和遗忘都可以解释、观测与恢复。
参考资料与固定版本源码
官方资料
[O1] OpenAI Codex Memories
- Memories
- 核对内容:Local Codex 与 ChatGPT Memory 的边界、默认关闭、后台更新、idle/short-lived eligibility、Rate Limit Guard、Secret Redaction、
CODEX_HOME、~/.codex/memories/、/memories、AGENTS.md职责边界、配置项。 - 联网核对日期:2026-07-15。
[O2] OpenAI Cookbook:Building Reliable Agents with Memory and Compaction
- Building Reliable Agents with Memory and Compaction
- 核对内容:Compaction 服务当前长 Run,Memory 服务未来 Run;稳定 Workflow Lessons 与 Reviewed Artifact 的职责分离。
- 联网核对日期:2026-07-15。
固定源码基线
Repository:openai/codex,Commit
8604689ec5e3437eb79802d8d72249b7722fbf5b。以下链接全部固定到该 Commit,不引用会漂移的main。
配置、启动与输入策略
Phase 2、物化工作区与 Prompt
Read Path、工具协议与引用
- [C12] Progressive Disclosure Read Path
- [C13] MemoriesBackend 能力协议
- [C14] Memory Citation Parser
- [C15] External Context Pollution:MCP Tool Call、Core Tool Output
- [C16] Rollout Memory Persistence Policy
- [C28] Local Search 的递归遍历、Normalized 与 Window Match
存储、测试与可观测性
- [C17] Write-path Metrics
- [C18] 独立 Memory DB 文件与 Runtime
- [C19] Memory DB Migration:独立 Memory Schema、从主 State DB 删除旧表
- [C20] Memory State Machine Tests
- [C21] Usage Feedback:Citation 记录、Shell Memory Usage 分类
- [C22] Git-backed Workspace
- [C23] Raw Memory 与 Rollout Summary 物化
- [C24] Consolidation Agent Runtime
- [C25] Local Backend Path Sandbox:逐级路径解析、Hidden / Symlink 规则
- [C26] Ad-hoc Note Validation
- [C27] Memory Tool Metrics
专栏导航
- 总览:Agent 记忆系统架构总览
- 当前:Codex Memory:后台语义 ETL 与 Git 巩固
- 下一篇:Claude Code Memory:文件层级、Auto Memory 与渐进披露
- Hermes Memory:有界显式记忆与 Agent 自主 CRUD
- Mem0:ADD-only 提取、混合检索与多存储一致性
- Letta / MemGPT:虚拟上下文与 Agent 自编辑记忆
- Graphiti:双时态知识图谱与增量失效