具体问题与边界
三种历史变换都在“减少或复制历史”,但它们的输入、身份、耐久记录和恢复语义有何不同?
Compact 生成 summary+suffix replacement;Rollback 截断最近 N 个 user turn;Fork 把有效历史写入新 thread_id。三者只能在 Session idle 时修改当前历史。 下面同时给出状态所有者、顺序、伪代码、故障残留和不可外推边界。
状态所有权
| 对象 | 所有者 | 生命周期 | 持久化 |
|---|---|---|---|
| canonical history | HistoryManager | Session 生命周期 | Rollout 记录 |
| compact replacement | HistoryManager.compact | checkpoint 之后生效 | 是 |
| rollback replacement | HistoryManager.rollback | marker/checkpoint 后生效 | 是 |
| fork snapshot | write_fork | 新 Thread 初始历史 | 新 JSONL |
| thread identity | RuntimeManager/target_thread_id | Thread 生命周期 | session_meta |
正常路径
- Compact 根据最近 user message 的索引保留 suffix,并在前面插入 Developer summary。调用者提供摘要,Mini 不额外调用模型。
- Rollback 按 user message 边界找到倒数第 N 个 Turn,保留其前缀;N 必须为正且不能超过已有 user turn 数。
- 两者先替换内存 History,再追加含 replacement_history 的 compacted/rolled_back 记录并 flush。Resume 遇到记录就替换此前有效历史。
- Fork 不修改父 History;它要求目标文件不存在,创建新 session_meta/new thread_id,再记录 forked_from_snapshot 和复制的有效 Item。
- 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、取消、外部副作用和结果反馈的实际顺序。
失败、取消与恢复
| 故障点 | 残留/风险 | 处理 |
|---|---|---|
| 活动 Turn 中变换 | 采样与历史替换并发 | 拒绝操作 |
| Rollback N 非法 | 无确定边界 | ValueError,不改 History |
| checkpoint flush 失败 | 内存可能先于耐久日志 | 错误上报;不能声称跨崩溃完成 |
| Fork 目标已存在 | 可能混入另一 Thread | FileExistsError |
| 复制 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_historytest_rollback_checkpoint_replays_replacement_history
官方源码导航
- codex-rs/core/src/compact.rs:Local/Remote compaction 入口与 replacement history
- codex-rs/core/src/session/handlers.rs:Rollback marker 与热应用
- codex-rs/core/src/thread_manager.rs:Thread fork 创建
- codex-rs/thread-store/src/local/paginated_fork.rs:reference-backed fork 准备与 durable reference
- codex-rs/core/src/session/rollout_reconstruction.rs:Compact/Rollback 恢复语义
Mini Codex 对照
src/mini_codex/context/history.py:compact/rollback 算法src/mini_codex/persistence/rollout.py:compacted/rolled_back/write_forksrc/mini_codex/persistence/resume.py:replacement replaysrc/mini_codex/runtime/session.py:idle gate
本节结论
历史变换必须在明确边界产生 replacement;Fork 必须获得新身份且不能继续读取父 Thread 的未来追加。
评论
登录后即可评论