我把 OpenAI 的 Compaction 文档和 openai/codex 的几次改动对了一遍。最早的 Codex 会请模型写一份接手摘要,今天的 Remote v2 已经换成加密 checkpoint。两代实现都在处理同一个麻烦。对话越来越长,模型的上下文快要装不下时,任务怎样接着做。
源码里,服务端先把旧状态压成 checkpoint。客户端随后重建模型下一轮要看到的历史,放回近期用户原话和当前会话的规范上下文。文件、Git diff 和终端进程仍留在工作区,下一轮模型从这里继续执行。
这篇文章引用 2026 年 8 月可查到的 OpenAI 文档。客户端细节定在 openai/codex 2026 年 7 月 19 日的提交 0fb559f。固定源码版本很重要,因为 provider 路由还在变化。服务端 checkpoint 经过加密,公开材料只能说明它的协议和用途,无法还原里面的压缩算法。
Compact 只照看当前任务
Compact 的作用范围是当前任务。它尽量留下目标、进度和继续推理所需的状态,让同一个任务越过上下文边界。Memory 面向以后的任务,用来保存可复用的偏好与经验。规格、代码、测试结果和已经确认的结论,仍应落在仓库文件或其他可审阅产物里。
OpenAI 的可靠 Agent 示例把 Compaction 和 Memory 分开处理,前者维持当前工作,后者帮助未来工作。经过审阅的产物留作事实来源。
压缩发生时,Codex 线程还在,工作目录和 rollout 也在。客户端替换的是模型的活跃历史。这个边界解释了任务为何能继续,也说明了 Compact 的可靠性上限。假如一条关键约束只在很早的对话里出现过,之后没有写进文件,checkpoint 又漏掉了它,模型便没有可靠的恢复来源。
所以长任务仍要留下可验证的外部状态。计划与决定写进文档或代码,需要核对命令结果时再执行一次。Compact 负责接力,项目产物负责留证。
Compact 什么时候发生
公开的 Responses API 提供两种入口。应用可以设置 context_management.compact_threshold,让服务端在 response stream 内自动加入压缩结果;也可以显式调用 POST /responses/compact,拿回下一轮可直接使用的窗口。Codex 客户端在 Agent Harness 里另有一层触发逻辑,用户还可以输入 /compact 主动建一个 checkpoint。
在本文查看的源码快照里,客户端要先能解析 context_window。模型元数据没有单独阈值时,它以窗口的 90% 推导自动压缩上限;显式阈值也会被限制在这个上限以内。ModelInfo::auto_compact_token_limit()里能看到这段计算。90% 属于这个版本的客户端实现,官方 API 没有把它承诺成所有模型、所有客户端都固定遵守的比例。
Codex 会在下一次请求前检查一次。历史已经越线时,它先 compact,再把请求交给模型。这个 pre-turn 检查可以避免把明显超限的历史原样送出去。
一轮执行中间也可能触发检查。模型刚调用完工具,接下来还要继续推理,而累计 token 已经超过阈值,Codex 会在同一个执行循环里换窗,然后继续采样。run_turn()和 run_pre_sampling_compact()分别覆盖了 mid-turn 与 pre-turn 路径。这样一来,context overflow 不会被误当成任务已经完成。
模型切换也会影响这件事。新的模型拥有不同的 comp_hash,或者可用窗口变小,旧 checkpoint 可能不再合适,客户端会据此安排新的压缩。另一个细节是 token 可以按整段活跃上下文计算,也可以只算固定前缀之后增加的部分。后者能防止一个较大的保留前缀刚放进新窗口,阈值马上又被碰到。
Remote v2 发了什么
Remote Compaction v2 走普通的 Responses stream。一次请求大致经过下面这几步。
当前历史
↓
加入当前指令、工具定义和 compaction_trigger
↓
Responses 服务返回一个加密 Compaction item
↓
客户端安装新的历史
发送之前,客户端复制活跃历史,并在必要时缩短过大的工具输出。它把当前基础指令和工具定义一起放进请求,最后追加一个瞬态 compaction_trigger。请求构造代码显示,这个 trigger 只负责启动压缩,不会留在后续对话里。
这条流与公开的 POST /responses/compact 接口各自独立。独立接口接收完整窗口,返回可供下一次请求使用的标准窗口。Codex Remote v2 则在普通 Responses 请求中使用 trigger 和返回项完成交换。把两者混为同一个调用,很容易在阅读日志时找错入口。
客户端对返回值很严格。当前实现只接受一个 Compaction item,缺少或重复都会让本次压缩报错。stream 在 response.completed 之前关闭时,结果同样作废。Remote v2 的响应校验把 checkpoint 设成了一条清楚的协议边界。
OpenAI 的 Compaction 文档把这个 item 描述为 encrypted、opaque 的压缩结果。它用较少 token 携带后续工作需要的关键状态和推理,客户端不读取其中的语义,只按类型接收并带进新的上下文窗口。官方使用 loss-aware compression 这个说法,表示压缩会优先照顾任务相关信息。早期细节仍然可能丢失。
公开资料到这里就停住了。我们能检查请求里放了什么,也能检查返回 item 的数量和后续 history。服务端如何挑选事实、给各类信息分配多少权重,目前没有可验证答案。内部是否使用固定摘要模板也没有公开。
客户端还要重建一次历史
服务端返回 checkpoint 以后,Codex 没有直接把它当成全部历史。客户端会回到原始输入,从中找出真实用户消息和 Hook prompt,再按最近消息优先的顺序装入新窗口。本文源码快照给这部分原文留了 64,000 token 预算。边界上的消息太长时,只保留能放下的部分,随后才把加密 checkpoint 接在末尾。build_v2_compacted_history()实现了这套规则。
这里的 64k 只管保留的原始消息。checkpoint 大小和整扇上下文的上限由其他逻辑决定。这个预算也解释了用户原话为何值得单独保留。目标、路径或验收条件一旦改写,细小偏差就会直接影响后面的工具操作。
接着,客户端会过滤旧 developer message、session wrapper、reasoning、tool call 和 tool output。should_keep_compacted_history_item()列出了筛选规则。当前会话的规范上下文会重新注入,其中包含此刻有效的指令和环境状态。旧包装不会借着 checkpoint 一直留在模型眼前。
pre-turn 和 mid-turn 的摆放方式有一点差别。mid-turn 还要在同一轮继续采样,checkpoint 需要待在历史尾部,当前上下文会先插到最后一条真实用户消息之前。历史里没有真实用户消息时,位置退到摘要或 Compaction item 前。pre-turn 和手动 /compact 在替换历史时不立即注入,下一次正常请求再完整放回。
重建结束后,replace_compacted_history() 会替换活跃历史,把新历史写进 rollout,然后重算 token 用量。mid-turn 若生成了新的 world-state 基线,它也会一并持久化。pre-turn 和手动压缩此时没有这份 baseline,要等下一次正常请求重注入。对应源码记录了这些分支。
没有 Remote Compaction 时怎样接上
当 provider 不支持 remote compaction,或能力路由没有选中远端路径时,Codex 才走由客户端编排的本地摘要路径。客户端给用于本次 compact 的模型一段 checkpoint prompt,请它写出一份交接文本,内容覆盖当前进度、关键决定、限制条件和剩余工作。最终那条 assistant message 会成为人类可读的摘要。
客户端同时保留近期的真实用户消息,这个源码版本给本地路径的原文预算是 20,000 token。旧的压缩摘要会被清掉,新摘要以 user role 消息放到历史末尾。本地历史构造和 20k 保留逻辑都可以直接读到。
compact_prompt 只影响这条本地路径。Remote v2 的加密内容由服务端生成,自定义 prompt 无法改变它。这里说的本地 fallback 也属于 provider routing 的兼容选择。Remote v2 请求运行失败时,当前代码不会自动改走自然语言摘要。
支持范围还在往前走。7 月的固定源码只为 OpenAI 和 Azure provider 启用远端路径。Codex CLI 更新记录显示,0.147.0 在 2026 年 8 月又加入了 Amazon Bedrock remote conversation compaction。源码快照适合解释机制,最新支持列表仍要看当时的文档与发布记录。
一次换窗会丢掉什么
Remote v2 会保留近期用户原话,早期 assistant 推理和工具输出不会原样进入 replacement history。它们的延续主要依赖 checkpoint,必要证据则要回到文件、日志或可重复命令里找。压缩带来的第一类风险就藏在这里。一个中间结论没有进入 checkpoint,也没有外部记录,后续模型很难凭空恢复。
多次压缩还会积累偏差。第一次漏掉一条弱约束,下一次 checkpoint 只能基于已经缺了一块的活跃状态继续压缩。本地路径完成后,客户端还会向用户发出 warning。长线程和多次 compaction 可能降低模型准确性,线程最好保持小而聚焦。
成本曲线也会短暂变化。Prompt Caching 文档说明,cache 命中依赖请求前缀。Compact 改写了较早的上下文,换窗后的第一次请求可能复用更少的旧 cache。输入规模下降仍可能节省费用。能否抵消 cache reuse 下降和 compaction 本身的成本,需要把后续输入与延迟放在一张账上实测。
如果要测这套机制,我会从一段可验证的长任务开始,在压缩前留下明确约束和一个尚未完成的步骤。触发 Compact 以后,看 Agent 能否继续完成任务,并用仓库状态或测试结果核对。再把同一任务延长到多次换窗,记录约束遗失、重复工具调用和人工纠错。这样的结果比单独报告压缩率更接近用户实际感受到的可靠性。
这套机制怎样走到今天
2025 年 9 月的 PR #3446建立了早期框架。模型先生成可读摘要,客户端再用它组成下一段历史。一个月后的 PR #6027开始单独保留近期用户消息,让原始意图和 handoff summary 各自承担一部分接力工作。
2025 年 11 月,PR #6795引入 Remote Compaction。到了 2026 年 5 月,PR #20773把 Remote v2 放进 Responses stream,PR #22809确定 compaction_trigger 与标准加密返回项的传输约定。随后 PR #23728加入近期消息的 64k 保留预算。
2026 年 6 月,PR #27573让支持的 provider 默认使用 Remote v2。对于走 Remote v2 的 provider,客户端已经不再依赖一份固定格式的可读摘要。它只验证协议产物,再按确定规则安装新历史。服务端获得了继续改进压缩策略的空间,开发者能直接观察到的内容也少了一块。