雨天小六

读懂 Codex(8.25):Compact Checkpoint 的 Replacement History

· 更新于 2026-08-03 · 专栏:读懂 Codex

#Codex#Agent Runtime#持久化#Rollout#软件架构

Compaction 的耐久结果不是一条“已经总结”的提示,而是一份可直接接管 Context History 的 replacement_history,加上窗口身份、WorldState full 和可选 TurnContext 基线。

本节解决的不是“把对象存一下”这种抽象问题,而是把问题限定在:研究 checkpoint 安装的原子语义与恢复起点,不比较本地/远端生成方法。输入:压缩后的 ResponseItem、reference TurnContext、WorldState 和新窗口元数据;状态所有者:Session::replace_compacted_history;成功结果:热 Session 与未来 Resume 共享的同一 replacement history。先把这些边界钉住,后面的顺序、失败和恢复才不会混成一句“持久化失败后重试”。

状态边界

问题本节答案
输入压缩后的 ResponseItem、reference TurnContext、WorldState 和新窗口元数据
状态所有者Session::replace_compacted_history
成功产物热 Session 与未来 Resume 共享的同一 replacement history
研究范围研究 checkpoint 安装的原子语义与恢复起点,不比较本地/远端生成方法

正常路径:先看顺序点

Compact Checkpoint 的 Replacement History的正常路径时序图,展示为缺失 ResponseItem 分配稳定 ID、替换内存 Context History、追加携带相同数组的 CompactedItem、追加 WorldState full、追加 reference TurnContext 并开启新窗口
图 8.25-1:为缺失 ResponseItem 分配稳定 ID → 替换内存 Context History → 追加携带相同数组的 CompactedItem → 追加 WorldState full → 追加 reference TurnContext 并开启新窗口。这张图标出本节的实际顺序点与状态所有者。

这条路径可以压缩成五步:

  1. 为缺失 ResponseItem 分配稳定 ID。
  2. 替换内存 Context History。
  3. 追加携带相同数组的 CompactedItem。
  4. 追加 WorldState full。
  5. 追加 reference TurnContext 并开启新窗口。

图中的箭头不是“可能调用”的依赖图,而是源码中决定可见性和所有权转移的先后关系。前一步没有确认时,后一步不能替它作出更强的成功承诺。

源码机制拆解

Replacement 是完整基座而非增量

CompactedItem.replacement_history=Some(items) 表示恢复器可以直接 history.replace(items),无需读取 checkpoint 之前的 ResponseItem 才能得到当前上下文。

内存与落盘使用同一 item 副本

replace_compacted_history 先为缺 ID 的 ResponseItem 分配 ID,再用 items.clone 同时更新 ContextManager 和构造 CompactedItem。避免热 Session 与冷 Resume 在 item identity 上分叉。

窗口元数据属于 checkpoint

message 之外还保存 window_number、first_window_id、previous_window_id 与 current window_id。Checkpoint 同时是上下文内容边界和请求缓存/追踪边界。

WorldState 在新窗口写 full

旧 patch chain 已被 replacement 截断,不能继续引用不可见 baseline。Session 在 CompactedItem 后持久化 WorldStateItem::full,并同步 ContextManager baseline。

TurnContext 决定是否继续 diff

Mid-turn compact 注入完整初始上下文时,checkpoint 后可写 reference TurnContext,下一步继续 diff;standalone/pre-turn compact 不注入时清除 reference,让下一普通 Turn 全量重建。

Python 风格伪代码

下面的伪代码只保留设计职责、状态和失败顺序;它不逐行翻译 Rust,也不借 Python 语法虚构源码中不存在的事务:

async def install_checkpoint(session, items, reference_context,
                                 world_state, window):
    items = assign_missing_stable_item_ids(items)
    checkpoint = CompactedItem(
        replacement_history=copy(items),
        window_number=window.number,
        first_window_id=window.first,
        previous_window_id=window.previous,
        window_id=window.current,
    )

    session.history.replace(items)
    session.history.reference_context = reference_context
    if world_state is not None:
        session.history.world_state_baseline = world_state.snapshot()

    await session.persist(checkpoint)
    if world_state is not None:
        await session.persist(WorldState.full(world_state.snapshot()))
    if reference_context is not None:
        await session.persist(reference_context)
    session.queue_session_start_source(COMPACT)

阅读时要特别看三处:哪个对象拥有可变状态,哪一个 await 是可观察屏障,以及失败后保留的是已提交前缀、未提交后缀,还是完全独立的外部副作用。

失败、取消与恢复

Compact Checkpoint 的 Replacement History的失败路径图,区分只替换内存不写 checkpoint、CompactedItem 在 ID 分配前构造、新窗口只写 patch、错误保留 reference context
图 8.25-2:同一操作在不同故障点留下的状态不同,恢复责任必须回到拥有该状态的组件。
故障点已留下的状态可观察结果恢复责任
只替换内存不写 checkpoint热 Session 已变短重启又看到旧历史持久化相同 replacement
CompactedItem 在 ID 分配前构造磁盘/内存 item ID 不同后续更新和缓存相关性破裂先规范 identity 再 clone
新窗口只写 patch旧 baseline 不在 replacement恢复 WorldState 无法解释checkpoint 后写 full
错误保留 reference contextreplacement 没有对应初始上下文下一 Turn 只发 diffDoNotInject 模式清除 reference

这里没有统一的“回滚一切”。内存状态、日志行、SQLite 投影、父子拓扑和工具造成的文件/网络变化分别有自己的提交点。恢复代码只能根据已经存在的权威证据继续,不能用较弱的投影替较强的事实背书。

必须保持的不变量

  • 热 Context History 与 checkpoint replacement 逐项相同
  • checkpoint 之后建立独立 WorldState baseline
  • 窗口 ID 与 replacement 在同一语义边界生成
  • 旧日志保留但不再是有效历史基座

这些不变量比“最终能 Resume”更严格:正常路径要成立,Writer 竞争、任务取消、坏尾行、投影落后和旧格式兼容时也必须成立。

设计取舍

把 replacement 整体写入日志会重复一部分文本、增加磁盘占用,却显著缩短恢复扫描并保持旧记录可审计。

源码可以直接证明字段、分支、调用顺序和测试期望;“为什么这样设计”的表述是基于这些事实作出的工程归纳,不把它包装成未公开的产品承诺。

Mini Codex 复刻

Checkpoint 结构直接存 replacement list 和 window tuple;安装函数是唯一入口,禁止调用方分别改内存与写日志。

复刻时先验证协议不变量,再补性能优化。一个能在故障注入下说明“留下了什么”的小实现,比一个只在正常路径调用 save() 的演示更接近真实 Runtime。

源码导航

相邻测试也很重要:

本节结论

Compaction 的耐久结果不是一条“已经总结”的提示,而是一份可直接接管 Context History 的 replacement_history,加上窗口身份、WorldState full 和可选 TurnContext 基线。

阅读导航

上一节:8.24 · 下一节:8.26

评论


← 返回文章列表