雨天小六

读懂 Codex(8.29):Reference-backed Fork 的冻结前缀

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

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

Reference-backed Fork 不复制祖先字节,而是把 ThroughTurn/BeforeTurn/Latest 解析成 HistoryPosition,并在 child SessionMeta.history_base 中冻结该物理前缀。

本节解决的不是“把对象存一下”这种抽象问题,而是把问题限定在:研究 prepare_fork 的锁、物化、边界解析、model context 加载和 PreparedFork reservation。输入:Paginated source ThreadId 与 ForkBoundary;状态所有者:LocalThreadStore::prepare + PreparedFork;成功结果:history_base、边界模型上下文和删除保护 reservation。先把这些边界钉住,后面的顺序、失败和恢复才不会混成一句“持久化失败后重试”。

状态边界

问题本节答案
输入Paginated source ThreadId 与 ForkBoundary
状态所有者LocalThreadStore::prepare + PreparedFork
成功产物history_base、边界模型上下文和删除保护 reservation
研究范围研究 prepare_fork 的锁、物化、边界解析、model context 加载和 PreparedFork reservation

正常路径:先看顺序点

Reference-backed Fork 的冻结前缀的正常路径时序图,展示预留 source 生命周期并 persist、解析 plain lineage、物化必要 segment 到 SQLite、把 boundary 转成 ordinal+offset、加载 frozen model context 并保持 reservation 至 child 耐久
图 8.29-1:预留 source 生命周期并 persist → 解析 plain lineage → 物化必要 segment 到 SQLite → 把 boundary 转成 ordinal+offset → 加载 frozen model context 并保持 reservation 至 child 耐久。这张图标出本节的实际顺序点与状态所有者。

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

  1. 预留 source 生命周期并 persist。
  2. 解析 plain lineage。
  3. 物化必要 segment 到 SQLite。
  4. 把 boundary 转成 ordinal+offset。
  5. 加载 frozen model context 并保持 reservation 至 child 耐久。

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

源码机制拆解

Reservation 覆盖异步准备全程

prepare 先 reserve_lifecycle(source),并把 persist+lineage resolution 放入 spawned task,避免调用者取消 Future 时过早释放。PreparedFork 最终持有 opaque reservation。

引用要求 plain 物理表示

resolve_rollout_lineage_for_reference 对每个 segment 获取 writer guard,并调用 materialize_rollout_for_reference。HistoryPosition 的 byte offset 必须指向可稳定 seek 的 plain 文件。

Latest 使用 projection checkpoint

先 materialize source,再从 thread_history projection_state 取得 next_ordinal/next_byte_offset,形成当前完整 durable 前缀。缺 state DB 或 projection state 都无法准备引用式 Fork。

Turn 边界来自结构化投影

ThroughTurn 找逻辑可见 Turn,拒绝 inProgress,使用 end ordinal+1/end byte offset;BeforeTurn 找物理 source Turn,要求存在持久化 start boundary,使用 start ordinal/start offset。

空 segment 边界会正规化到祖先

若 position.end_ordinal_exclusive 等于该 segment.start_ordinal,说明 child 不继承此 segment 的本地行,history_base 回退为上一个 segment 的 end,避免制造零长度指针层。

Python 风格伪代码

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

async def prepare_reference_fork(store, source_id, boundary):
    reservation = await store.reserve_lifecycle(source_id)
    await store.persist_thread(source_id)
    lineage = await store.resolve_plain_lineage(source_id)
    require(store.state_db is not None)

    for segment_needed_by(boundary, lineage):
        async with store.writer_lock(segment.thread_id):
            await store.materialize_to_sqlite(segment)

    latest = await store.projection_position(source_id)
    match boundary:
        case Latest(): position = latest
        case ThroughTurn(turn_id):
            turn = await find_visible_turn(lineage, turn_id)
            require(turn.status != 'inProgress')
            position = after_turn(turn)
        case BeforeTurn(turn_id):
            turn = await find_source_turn(lineage, turn_id)
            require(turn.has_persisted_start_boundary)
            position = before_turn(turn)

    history_base = normalize_empty_segment(position, lineage)
    model_context = await load_for_fork(lineage, history_base)
    return PreparedFork(source_id, history_base, model_context, reservation)

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

失败、取消与恢复

Reference-backed Fork 的冻结前缀的失败路径图,区分无 State DB、ThroughTurn 指向 inProgress、BeforeTurn 缺 start position、准备期间删除 source
图 8.29-2:同一操作在不同故障点留下的状态不同,恢复责任必须回到拥有该状态的组件。
故障点已留下的状态可观察结果恢复责任
无 State DB无法按 Turn 查询稳定位置Unsupported prepare_fork退回 copied fork
ThroughTurn 指向 inProgress没有完整 end boundaryInvalidRequest等待终态或用更旧 Turn
BeforeTurn 缺 start position旧投影无法精确截断InvalidRequest不猜 byte offset
准备期间删除 source若无 reservation 会悬空生命周期操作等待child durable 后释放 PreparedFork

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

必须保持的不变量

  • history_base 不超过任何 segment frozen end
  • 引用位置同时包含 ordinal 与 byte offset
  • PreparedFork 存活期间 source 不可被归档/删除越过
  • child 本地 Rollout 只保存继承边界后的增量

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

设计取舍

引用式 Fork 几乎不复制大历史,却需要 State DB、lineage、物理表示稳定和引用生命周期协调。

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

Mini Codex 复刻

PreparedFork 必须是资源对象而非普通 tuple;持有 source lease,只有 child meta 已 fsync 后 close lease。

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

源码导航

相邻测试也很重要:

本节结论

Reference-backed Fork 不复制祖先字节,而是把 ThroughTurn/BeforeTurn/Latest 解析成 HistoryPosition,并在 child SessionMeta.history_base 中冻结该物理前缀。

阅读导航

上一节:8.28 · 下一节:8.30

评论


← 返回文章列表