雨天小六

读懂 Codex(8.26):Local/Remote Compact 在恢复语义上的差异

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

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

Local Compact 与 Remote Compact 产生摘要的路径不同,但最终都必须收敛到同一个 replacement checkpoint;差异集中在输入裁剪、输出过滤、重试和上下文注入位置。

本节解决的不是“把对象存一下”这种抽象问题,而是把问题限定在:对比两种 compactor 的历史构造与故障路径,强调共同安装边界。输入:当前 Context History、压缩原因/阶段、模型 Provider 能力与 InitialContextInjection;状态所有者:compact.rs / compact_remote.rs,最终由 Session 安装;成功结果:清洗后的 replacement history 和统一 CompactedHistoryMetadata。先把这些边界钉住,后面的顺序、失败和恢复才不会混成一句“持久化失败后重试”。

状态边界

问题本节答案
输入当前 Context History、压缩原因/阶段、模型 Provider 能力与 InitialContextInjection
状态所有者compact.rs / compact_remote.rs,最终由 Session 安装
成功产物清洗后的 replacement history 和统一 CompactedHistoryMetadata
研究范围对比两种 compactor 的历史构造与故障路径,强调共同安装边界

正常路径:先看顺序点

Local/Remote Compact 在恢复语义上的差异的正常路径时序图,展示按 Provider 选择实现、构造/发送压缩输入、获得 summary 或 compacted transcript、过滤并插入必要初始上下文、调用同一 replace_compacted_history
图 8.26-1:按 Provider 选择实现 → 构造/发送压缩输入 → 获得 summary 或 compacted transcript → 过滤并插入必要初始上下文 → 调用同一 replace_compacted_history。这张图标出本节的实际顺序点与状态所有者。

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

  1. 按 Provider 选择实现。
  2. 构造/发送压缩输入。
  3. 获得 summary 或 compacted transcript。
  4. 过滤并插入必要初始上下文。
  5. 调用同一 replace_compacted_history。

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

源码机制拆解

Local 路径把压缩当模型 Turn

它把 summarization prompt 加入 history,通过普通 Responses 流 drain 到 completed,从最后 assistant message 取摘要,再用保留的 user messages + SUMMARY_PREFIX 构造 replacement。

Local 对 context overflow 逐项裁剪

压缩请求自己也可能超窗。若输入多于一项,就从最旧项开始删除,同时 remove_first_item 会连带移除 Call/Output 配对;普通流错误按 provider retry/backoff。

Remote 路径接收结构化 transcript

supports_remote_compaction 的 Provider 调 compact endpoint,结果可包含多条 message。安装前丢弃 stale developer message、非真实 user wrapper、Reasoning/Tool Call 等不在 compact retention shape 的项。

Remote 可以按错误类型做 model fallback

自动压缩可携带 fallback StepContext;只有 should_retry_with_current_model 的错误才用当前模型再试,并记录 fallback telemetry。原始错误与 fallback 结果按实现规则选择返回。

两种路径共享 InitialContextInjection

Standalone/pre-turn 使用 DoNotInject,让下次正常 Turn 重建完整上下文;mid-turn 使用 BeforeLastUserMessage,把当前初始上下文插到最后真实 user/summary 之前,并建立 reference/world-state baseline。

Python 风格伪代码

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

async def compact(session, step, mode, injection):
    await run_pre_compact_hooks()
    try:
        if mode == LOCAL:
            working = session.history.clone()
            while True:
                try:
                    summary = await responses_summarize(working)
                    replacement = build_local_history(user_messages(working), summary)
                    break
                except ContextWindowExceeded:
                    working.remove_oldest_with_call_pair()
        else:
            transcript = await remote_compact_endpoint(prompt_history(session))
            replacement = [i for i in transcript if keep_remote_compacted_item(i)]

        initial, world = await build_compaction_initial_context(injection)
        replacement = insert_before_last_real_user_or_summary(replacement, initial)
        reference = step.turn_context if injection.is_mid_turn else None
        await session.install_checkpoint(replacement, reference, world,
                                         session.advance_window())
    finally:
        await run_post_compact_hooks_if_success()

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

失败、取消与恢复

Local/Remote Compact 在恢复语义上的差异的失败路径图,区分Local 压缩输入仍超窗、Remote 返回旧 developer 指令、Remote 主模型可回退错误、Pre/Post Hook 阻断
图 8.26-2:同一操作在不同故障点留下的状态不同,恢复责任必须回到拥有该状态的组件。
故障点已留下的状态可观察结果恢复责任
Local 压缩输入仍超窗尚未生成 replacement删除最旧完整 item/pair 后重试只剩一项仍超窗则失败
Remote 返回旧 developer 指令可能重复或过期过滤掉重新注入当前 canonical context
Remote 主模型可回退错误首 attempt 失败用 fallback StepContext 重试记录模型切换结果
Pre/Post Hook 阻断checkpoint 尚未或已经生成边界不同返回 TurnAborted只有成功安装后运行 post hook

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

必须保持的不变量

  • 两种实现最终都调用统一 checkpoint installer
  • Remote 输出不能成为旧 developer 指令权威
  • Mid-turn replacement 中初始上下文位于最后真实用户消息之前
  • 压缩失败不替换当前 history

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

设计取舍

Local 路径可在普通 Responses 能力上工作、逻辑可控;Remote 路径可由专用模型优化压缩质量。统一 checkpoint 格式隔离了生成策略差异。

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

Mini Codex 复刻

定义 Compactor Protocol 返回 candidate history;Local/Remote adapter 各自清洗,CheckpointInstaller 负责所有持久化和 baseline 不变量。

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

源码导航

相邻测试也很重要:

本节结论

Local Compact 与 Remote Compact 产生摘要的路径不同,但最终都必须收敛到同一个 replacement checkpoint;差异集中在输入裁剪、输出过滤、重试和上下文注入位置。

阅读导航

上一节:8.25 · 下一节:8.27

评论


← 返回文章列表