雨天小六

读懂 Codex(9.16):Compact、Rollback 和 Fork

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

#Codex#Agent Runtime#Python#软件架构

具体问题与边界

三种历史变换都在“减少或复制历史”,但它们的输入、身份、耐久记录和恢复语义有何不同?

Compact 生成 summary+suffix replacement;Rollback 截断最近 N 个 user turn;Fork 把有效历史写入新 thread_id。三者只能在 Session idle 时修改当前历史。 下面同时给出状态所有者、顺序、伪代码、故障残留和不可外推边界。

状态所有权

对象所有者生命周期持久化
canonical historyHistoryManagerSession 生命周期Rollout 记录
compact replacementHistoryManager.compactcheckpoint 之后生效
rollback replacementHistoryManager.rollbackmarker/checkpoint 后生效
fork snapshotwrite_fork新 Thread 初始历史新 JSONL
thread identityRuntimeManager/target_thread_idThread 生命周期session_meta

正常路径

Compact、Rollback 和 Fork正常路径图
图 9.16-1:三种历史变换都在“减少或复制历史”,但它们的输入、身份、耐久记录和恢复语义有何不同?
  1. Compact 根据最近 user message 的索引保留 suffix,并在前面插入 Developer summary。调用者提供摘要,Mini 不额外调用模型。
  2. Rollback 按 user message 边界找到倒数第 N 个 Turn,保留其前缀;N 必须为正且不能超过已有 user turn 数。
  3. 两者先替换内存 History,再追加含 replacement_history 的 compacted/rolled_back 记录并 flush。Resume 遇到记录就替换此前有效历史。
  4. Fork 不修改父 History;它要求目标文件不存在,创建新 session_meta/new thread_id,再记录 forked_from_snapshot 和复制的有效 Item。
  5. Session 拒绝在活动 Turn 中 compact/rollback,避免采样或工具链同时持有旧 prompt view。

Python 风格伪代码

async def compact(summary, keep_last):
    require(session.idle)
    replacement = history.compact(summary, keep_last)
    await rollout.append({
        "type": "compacted",
        "replacement_history": encode(replacement),
    })
    await rollout.flush()

async def rollback(turns):
    require(session.idle)
    replacement = history.rollback(turns)
    await rollout.append({
        "type": "rolled_back",
        "turns": turns,
        "replacement_history": encode(replacement),
    })
    await rollout.flush()

async def fork(parent_history, new_path, new_thread_id):
    require(not new_path.exists())
    child = JsonlRollout(new_path, new_thread_id)
    await child.initialize()
    await child.append_items(parent_history)
    await child.flush()
    return child

伪代码没有复制 Rust 语法;它保留了状态修改、await、取消、外部副作用和结果反馈的实际顺序。

失败、取消与恢复

Compact、Rollback 和 Fork失败路径图
图 9.16-2:失败不是一个 exception 方框,而是各状态所有者留下的可观察组合。
故障点残留/风险处理
活动 Turn 中变换采样与历史替换并发拒绝操作
Rollback N 非法无确定边界ValueError,不改 History
checkpoint flush 失败内存可能先于耐久日志错误上报;不能声称跨崩溃完成
Fork 目标已存在可能混入另一 ThreadFileExistsError
复制 Fork 后父继续写Child 不应看见未来父项snapshot 复制天然冻结

不变量

历史变换必须在明确边界产生 replacement;Fork 必须获得新身份且不能继续读取父 Thread 的未来追加。

设计取舍与不能外推的结论

Mini 用完整 replacement_history 简化恢复,空间效率低但语义清晰。官方还有自动摘要、WorldState 基线、legacy marker、copied/reference-backed fork、lineage 删除约束。

测试与复现

cd examples/mini-codex
uv run pytest -q -k 'test_compaction_replay_and_offline_fork_use_effective_history or test_rollback_checkpoint_replays_replacement_history'
uv run mypy src
uv run python benchmarks/runtime_baseline.py
  • test_compaction_replay_and_offline_fork_use_effective_history
  • test_rollback_checkpoint_replays_replacement_history

官方源码导航

Mini Codex 对照

  • src/mini_codex/context/history.py:compact/rollback 算法
  • src/mini_codex/persistence/rollout.py:compacted/rolled_back/write_fork
  • src/mini_codex/persistence/resume.py:replacement replay
  • src/mini_codex/runtime/session.py:idle gate

本节结论

历史变换必须在明确边界产生 replacement;Fork 必须获得新身份且不能继续读取父 Thread 的未来追加。

阅读导航

上一节:9.15 · 下一节:9.17

评论


← 返回文章列表