雨天小六

读懂 Codex(8.9):Ordinal、Byte Offset 与稳定历史位置

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

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

Paginated 历史用逻辑 ordinal 与物理 byte offset 的组合定位前缀:前者约束顺序,后者冻结具体文件边界,二者共同支撑分页、Fork 和增量投影。

本节解决的不是“把对象存一下”这种抽象问题,而是把问题限定在:说明 HistoryPosition 的生成、恢复和校验,不展开 lineage 拼接。输入:History Mode、history_base、当前文件尾和下一条记录;状态所有者:OrdinalState、ProjectionState 与 HistoryPosition;成功结果:end_ordinal_exclusive + end_byte_offset 的稳定边界。先把这些边界钉住,后面的顺序、失败和恢复才不会混成一句“持久化失败后重试”。

状态边界

问题本节答案
输入History Mode、history_base、当前文件尾和下一条记录
状态所有者OrdinalState、ProjectionState 与 HistoryPosition
成功产物end_ordinal_exclusive + end_byte_offset 的稳定边界
研究范围说明 HistoryPosition 的生成、恢复和校验,不展开 lineage 拼接

正常路径:先看顺序点

Ordinal、Byte Offset 与稳定历史位置的正常路径时序图,展示从 mode/base 或尾行恢复 next ordinal、为新行附 ordinal、写成功后记录字节尾、SQLite 原子推进 next position、Reader 用 exclusive boundary 截断
图 8.9-1:从 mode/base 或尾行恢复 next ordinal → 为新行附 ordinal → 写成功后记录字节尾 → SQLite 原子推进 next position → Reader 用 exclusive boundary 截断。这张图标出本节的实际顺序点与状态所有者。

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

  1. 从 mode/base 或尾行恢复 next ordinal。
  2. 为新行附 ordinal。
  3. 写成功后记录字节尾。
  4. SQLite 原子推进 next position。
  5. Reader 用 exclusive boundary 截断。

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

源码机制拆解

Legacy 不写 ordinal

OrdinalState 在 Legacy 模式返回 None,保持旧日志线格式兼容。Paginated 从 history_base.end_ordinal_exclusive 或 0 推导下一序号,并为每条行记录写 Some(ordinal)。

Exclusive end 减少边界歧义

HistoryPosition 的 end_ordinal_exclusive 表示边界之后的第一个序号;Fork ThroughTurn 把该 Turn 的末 ordinal 加一,BeforeTurn 直接使用 start ordinal。

Byte offset 冻结物理前缀

同一个源文件以后仍可追加,因此只存 ordinal 不足以保护反向扫描边界。end_byte_offset 让 Reader 从固定字节末端开始,明确排除后来追加的内容。

恢复必须检查最后有效行

Resume 从 canonical meta 得到 mode,再反向找最后一条有效记录。Paginated 的最终有效记录必须携带 ordinal,否则无法安全确定下一个值。

投影同时检查两种连续性

SQLite apply_projection 在事务里核对 expected byte offset 与 expected ordinal。任何一个不吻合都说明另一个 writer、截断或陈旧 projector 介入,不能盲目覆盖进度。

Python 风格伪代码

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

def initial_ordinal(mode, history_base):
    if mode == LEGACY:
        return None
    return history_base.end_ordinal_exclusive if history_base else 0

async def append_paginated(state, item):
    ordinal = state.next_ordinal
    start = await state.file.position()
    await state.file.write_line(encode(item, ordinal=ordinal))
    end = await state.file.position()
    state.next_ordinal = checked_add(ordinal, 1)
    return HistoryPosition(
        thread_id=state.thread_id,
        end_ordinal_exclusive=state.next_ordinal,
        end_byte_offset=end,
    )

async def apply_projection(db, expected, rows, new_position):
    async with db.begin_immediate() as tx:
        assert await tx.position() == expected
        assert rows_have_contiguous_ordinals(rows, expected.next_ordinal)
        await tx.apply(rows, new_position)

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

失败、取消与恢复

Ordinal、Byte Offset 与稳定历史位置的失败路径图,区分ordinal 溢出、尾行缺 ordinal、byte offset 超出文件、投影 expected position 不符
图 8.9-2:同一操作在不同故障点留下的状态不同,恢复责任必须回到拥有该状态的组件。
故障点已留下的状态可观察结果恢复责任
ordinal 溢出无法表示下一位置追加返回错误checked_add 禁止回绕
尾行缺 ordinalPaginated 文件不连续Resume 无法安全续写拒绝打开而非猜号
byte offset 超出文件引用指向不存在字节Lineage/Fork 无效边界校验直接报错
投影 expected position 不符并发或陈旧 projector事务不应用 changes重新读取当前位置后重算

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

必须保持的不变量

  • Paginated 每个有效行都有单调 ordinal
  • ordinal 只在行写成功后推进
  • HistoryPosition 的 byte offset 不得超过物理文件
  • SQLite next position 不能领先已经读取的完整 JSONL 前缀

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

设计取舍

双位置增加元数据和校验逻辑,但把逻辑分页与物理冻结同时表达,避免仅凭行号或仅凭字节带来的歧义。

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

Mini Codex 复刻

每次追加返回 Position;用 exclusive ordinal 和 byte offset 实现 freeze_prefix,并测试追加后旧 Position 仍读取同一前缀。

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

源码导航

相邻测试也很重要:

本节结论

Paginated 历史用逻辑 ordinal 与物理 byte offset 的组合定位前缀:前者约束顺序,后者冻结具体文件边界,二者共同支撑分页、Fork 和增量投影。

阅读导航

上一节:8.8 · 下一节:8.10

评论


← 返回文章列表