专题篇章 · 拆开 OpenAI Codex

满窗之后,砍哪一段留哪一段

三个时机共用一个分发器,三种实现由开关选中。同一份摘要在中途必须停在历史最后一项。

本页解决的问题

先给结论

「满窗之后,砍哪一段留哪一段」要解决的关键问题是什么?

三个时机共用一个分发器,三种实现由开关选中。同一份摘要在中途必须停在历史最后一项。

判断标准

跟着交接处走,不要只看 Demo。 系统是否可靠,往往取决于模型、工具、状态、权限和人的交接处。把每次交接都当成可以观察、测试和恢复的地方。

下一步

为一个自动化步骤写清输入、负责人、审批和恢复动作。

常见误区

一次运行成功了,却说不清发生了什么,也无法安全重放。

课程目标读完能说清三件事。第一,上下文满了,Codex 先问时机再问实现,九种组合共用两套入口。第二,服务端回来的 transcript 默认不可信,进门先丢掉指令包装和工具回放。第三,同一份摘要在采样前和中途要摆到不同位置,空窗开关打开则回忆不带走。
先玩一遍 · 同一段历史,三条管道各砍各的
窗口到顶:切换时机和实现,看各自砍掉哪一段、保住哪一段
时机
采样前和手动压完,下一轮才整包重注。中途压完还要继续采样,摘要必须停在队尾。
实现
默认 OpenAI 会话走远端。提供方不支持就走本地再采样。空窗跳过摘要,回忆不带走。
中途 × 远端 0砍掉 0保住 3版本
保住 砍掉 新注入的环境
滤网 摘要在最后 下一轮重注 空窗
等待开始。点播放,或先换一格再走一遍。
九宫格 · 点任一格,看这一格独有的砍法
采样前 中途 手动 远端 本地 空窗
逻辑轨迹 · 动画每一步对应源码里的哪一段
  1. 看 token_limit_reached,或用户提交 Compactcontext_window.rs L77
  2. 采样前走 run_pre_sampling_compactturn.rs L1012
  3. 中途走 should_roll_over,还要有 follow-upturn.rs L458
  4. 手动拉起 CompactTask,打断当前 turntasks/compact.rs L19
  5. TokenBudget 开则换空窗,远端和本地都被跳过turn.rs L1189
  6. 否则按远端 v2 或本地分发turn.rs L1201
  7. 远端结果过 should_keep 滤网compact_remote.rs L370
  8. 按 InitialContextInjection 决定插不插compact.rs L68
  9. replace_annotated 才把 history_version 加一history.rs L298
点播放,看同一段混杂历史在当前这一格里怎么被重排。
远端服务端回来的包装不可信。developer、工具回放先丢掉,再按时机决定环境插不插。中途必须把摘要留在最后。
本地先追加合成提示和助手输出,版本号还不变。成功之后整表换成截断后的用户原话加摘要前缀。
空窗跳过模型和服务端。新窗口里只剩当前环境,没有摘要,回忆不带走。
教学示意:色块数量与角色为课程化设定,用来展示三条管道各自砍什么、留什么。逻辑轨迹右侧行号对应 openai/codex 仓库 commit 4f39251a01。
思路一 · 时机和实现拆开
它解决什么问题

你让 coding agent 在同一个线程里改了三十轮。前二十轮还记得工作区在哪、哪几个文件不能动。到第三十轮,它突然问你「当前目录是什么」。再过几轮,已经批准过的 npm test 又被问了一遍。

用量顶到窗口边上之后,系统必须压历史。如果把什么时候压、怎么压写进同一段判断,换提供方或加一种不叫模型的空窗,判定入口都要跟着改。中途压完还要继续采样,发消息前压完则下一轮才会重注,这两件事对摘要位置的要求也不一样。

思路是什么

Codex 把时机写成 CompactionPhase:发消息前的 PreTurn、工具跑完还要续跑的 MidTurn、用户手动的 StandaloneTurn。实现写成 CompactionImplementation:本地再采一次样、旧的 /responses/compact、新的 compaction_trigger。分析事件另有 Trigger 和 Reason 两套标签。自动路径共用 run_auto_compact,手动路径共用 CompactTask。九宫格是乘法表,源码里没有九套并列函数。

三个自动时机都先问同一个函数。token_limit_reached 在缓冲后的 compact 限额或满窗任一触发时为真。中途还要多一道闸:后面还要继续,并且模型刚请求了新窗口或 token 已经到顶。只满不续,这一轮自然结束,下一轮用户消息到来时走预采样。

出处:codex-rs/core/src/session/context_window.rs 第 74 至 91 行;codex-rs/core/src/session/turn.rs 第 458 至 483 行

分发器第一句看 TokenBudget。开了就换空窗,远端和本地都被跳过。关了再按提供方能力选:认 compaction_trigger 且特性开,走远端 v2;认 V2 但特性关,走旧远端接口;Unsupported 走本地。TokenBudget 默认关,RemoteCompactionV2 默认开。默认 OpenAI 会话走远端 v2。

出处:codex-rs/core/src/session/turn.rs 第 1178 至 1201 行;codex-rs/features/src/lib.rs 第 1428 至 1433 行、第 1542 至 1547 行

用量到顶或 /compact 选相位 同一套分发器 空窗 TokenBudget 第一句 if,跳过摘要 远端 v2 或旧接口 回来要过滤网 本地再采样 提供方 Unsupported replace_compacted_history 三条管道的安装点相同,差异在安装之前的产物
教学化结构图:先选相位,再进同一套分发器,最后都在同一个安装点换窗。
为什么长期成立

什么时候该换窗,和窗里装什么,是两件独立的事。换语言重写,仍要先选相位再选管道。提供方只有一个时,也可以先把判定和安装拆开。

思路二 · 远端先过滤,摘要位置看相位
它解决什么问题

服务端带回的 transcript 可能夹着过期 developer 指令。不滤就和本地按当前 world state 新渲染的环境叠在一起,效果等价于有人改了旧行。中途压完还要在同一轮继续采样,模型被训练成摘要是历史上最后一项。上下文如果插到摘要后面,训练约束就破了。

思路是什么

滤网是完整的穷尽 match。丢掉 developer、非用户内容的 user 包装、工具调用和压缩触发项。留下真实用户消息、持久化 hook prompt、assistant、压缩项。v2 复用同一函数。

出处:codex-rs/core/src/compact_remote.rs 第 370 至 397 行

然后按 InitialContextInjection 决定插不插。预采样和手动用 DoNotInject:替换历史里没有初始上下文,并清掉 reference_context_item,下一轮普通 turn 会走完整重注。中途用 BeforeLastUserMessage:当前环境和权限插到最后一条真实用户消息之上,摘要仍在队尾。插入函数还有兜底:没有真实用户就插在摘要前,再没有就插在最后一条 compaction 项前。

出处:codex-rs/core/src/compact.rs 第 59 至 74 行

采样前 / 手动 · DoNotInject 用户原话 摘要 环境此刻不进替换表 下一轮普通 turn 整包重注 中途 · BeforeLastUserMessage 当前环境 最后一条用户 摘要必须在最后 同一轮接着采样,训练约束把摘要钉在队尾
教学化对照:同一份摘要,两种相位,两种安置。

三种实现最后都进 replace_compacted_history。live history 整表替换。history_version 只在这时加一,追加不碰版本号。Guardian 复用 transcript 时会核对 parent_history_version,版本变了就不能复用旧的审查前缀。

出处:codex-rs/core/src/context_manager/history.rs 第 298 至 302 行

远端回来的包装先丢掉。中途摘要停在队尾。
为什么长期成立

远端结果只要不是本地函数吐出来的,进门先丢掉指令包装。插入点挡的是训练约束和重注时机,这两件事本来就分开。版本号只在重写时前进,因为追加每轮都发生。

思路三 · 空窗也是压缩,只是不叫模型
它解决什么问题

有时用户只想换一扇干净的窗,不要摘要。空窗如果走另一套生命周期,hook 和 ContextCompaction 条目会看不见这件事。这个开关默认关,避免用户在没意识到时丢掉整段对话。

思路是什么

TokenBudget 跳过模型和服务端摘要,安装一扇新窗口,摘要字段是空字符串。模型在新窗口里看不到旧对话,只看到此刻的环境和权限。这和 new_context 工具那句合同一致:换窗,不摘要。它仍然走压缩生命周期,pre-compact hook 若停下,窗口还没换。

出处:codex-rs/core/src/compact_token_budget.rs 第 21 至 25 行

本地路径自己碰上满窗,不递归调用自动压缩。它删最老的一条再打一次。只剩一条还超,就标满窗并返回错误。远端失败也不会改走本地:普通满窗触发的预压缩和中途压缩都把 fallback 传成 None,第一次远端失败直接返回。超时不在可重试名单里。

出处:codex-rs/core/src/compact_model_fallback.rs 第 8 至 20 行

为什么长期成立

压缩是一次换窗的生命周期,产物可以是摘要,也可以是空房间。递归要自己挡住:手动任务不进 turn 循环,本地超窗靠修剪,中途路径靠压成功就会低于限额。假设不成立时,循环可以再次进入。

横向对比 · 同一道题的另一种答法

DSH:不叫模型的那一刀可以先落地

DSH 的压缩家族在 packages/compaction/。压力触发时先可选地 prune,再量一次。prune 之后如果已经回到阈值以下,摘要不跑。两条路径都在本地,没有 Codex 那种 compact 客户端。

溢出恢复把 replaceGeneration 当重试许可。prune 先落地、随后摘要抛错,只要 generation 前进了,就允许从新表层再试。generation 只在 replace 计划提交时加一,append 不加。Codex 用 history_version 回答同一问题,远端要么整窗安装,要么完全不装。

两侧均已核对源码 · 2026-08-22 · DSH · Compaction

Claude Code:递归靠标签拦住,失败三次就停手

默认自动路径仍是再叫一次模型写摘要。自动判定先挡递归:session_memorycompact 这两种 querySource 直接返回 false,注释写明它们是 fork 出来的 agent,再触发会死锁。连续失败 3 次之后电路熔断,注释记录过单会话连续失败上千次的事故。

Codex 的 Compact 任务不进 turn 循环,这种标签可以不存在。换来的是中途路径没有对等的连续失败计数器。作者把赌注写在注释里:压缩只要把用量压到限额以下,就不必担心死循环,于是计数器被省掉了。

两侧均已核对源码 · 2026-08-22
课堂练习
01

三张替换表各留下什么

上面那一段混杂历史,分别走中途远端、走空窗、走采样前远端。写出三张替换表:各砍掉哪几块、保住哪几块、摘要在不在最后、初始上下文此时在不在表里。

进阶一问:远端第一次失败时,为什么普通满窗路径不会改走本地,换模型预压缩却可以再打一次远端。

Takeaway:时机和实现拆开,九种组合共用两套入口。远端先进滤网,中途摘要必须停在队尾。空窗是压缩的一种产物,回忆不带走。

「先玩一遍 · 同一段历史,三条管道各砍各的」的能力藏在每次交接里

「你让 coding agent 在同一个线程里改了三十轮。前二十轮还记得工作区在哪、哪几个文件不能动。到第三十轮,它突然问你「当前目录是什么」。再过几轮,已经批准过的 npm test 又被问了一遍」说明,Agent 的表现不只由模型决定。模型、上下文、工具、状态、权限和人之间的每次交接,都会改变任务能否继续以及出了问题能否恢复。

先写清状态,再增加能力

从「用量顶到窗口边上之后,系统必须压历史。如果把什么时候压、怎么压写进同一段判断,换提供方或加一种不叫模型的空窗,判定入口都要跟着改。中途压完还要继续采样,发消息前压完则下一轮才会重注,这两件事对摘要位置的要求也不一样」出发,可以把流程拆成起始状态、下一步动作、工具返回、状态更新和停止条件。这样调试时找的是第一处丢失信息或权限的位置,而不是笼统地说“模型变笨了”。

  • 看 token_limit_reached,或用户提交 Compact context_window.rs L77
  • 采样前走 run_pre_sampling_compact turn.rs L1012
  • 中途走 should_roll_over,还要有 follow-up turn.rs L458

成功路径不能代表系统可靠

用「进阶一问:远端第一次失败时,为什么普通满窗路径不会改走本地,换模型预压缩却可以再打一次远端」重放一次成功和一次失败,记录每一轮真正传入的上下文、工具结果和负责人;只要第二个人能复述这条链,系统才有可维护性。

从「先玩一遍 · 同一段历史,三条管道各砍各的」走到「思路一 · 时机和实现拆开」

「先玩一遍 · 同一段历史,三条管道各砍各的」先把问题落在「窗口到顶:切换时机和实现,看各自砍掉哪一段、保住哪一段 播放 单步 重置 时机 采样前 中途 手动 采样前和手动压完,下一轮才整包重注。中途压完还要继续采样,摘要必须停在队尾。 实现 远端 本地 空窗 默认 OpenAI 会话走远端。提供方不支持就走本地再采样。空窗跳过摘要,回忆不带走。 中途 × 远端 0 砍掉 0 保住 3 版本 保住 砍掉 新注入的环境 滤网 摘要在最后 下一轮重注 空窗 等待开始。点播放,或先…」上;到了「思路一 · 时机和实现拆开」,讨论继续推进到「你让 coding agent 在同一个线程里改了三十轮。前二十轮还记得工作区在哪、哪几个文件不能动。到第三十轮,它突然问你「当前目录是什么」。再过几轮,已经批准过的 npm test 又被问了一遍」。两段连起来,重点就不只是记住一个结论,而是看清它成立所依赖的条件。

把这条判断带到下一个场景

分析 Agent 时,沿着状态、动作、工具结果和下一步的顺序走一遍;每次交接都要能说明信息从哪里来、由谁确认、失败时停在哪里。

  • 「先玩一遍 · 同一段历史,三条管道各砍各的」:窗口到顶:切换时机和实现,看各自砍掉哪一段、保住哪一段 播放 单步 重置 时机 采样前 中途 手动 采样前和手动压完,下一轮才整包重注。中途压完还要继续采样,摘要必须停在队尾。 实现 远端 本地 空窗 默认 OpenAI 会话走远端。提供方不支持就走本地再采样。空窗跳过摘要,回忆不带走。 中途 × 远端 0 砍掉 0 保住 3 版本 保住 砍掉 新注入的环境 滤网 摘要在最后 下一轮重注 空窗 等待开始。点播放,或先…
  • 「思路一 · 时机和实现拆开」:你让 coding agent 在同一个线程里改了三十轮。前二十轮还记得工作区在哪、哪几个文件不能动。到第三十轮,它突然问你「当前目录是什么」。再过几轮,已经批准过的 npm test 又被问了一遍
  • 「最后的要点」:TokenBudget 开则换空窗,远端和本地都被跳过 turn.rs L1189

最后的「最后的要点」把讨论落到「TokenBudget 开则换空窗,远端和本地都被跳过 turn.rs L1189」。回看这条线索时,最值得保留的是:当输入、规模或风险改变,哪些判断需要重新做一遍。

标记为已学完 阅读进度会自动记录
← 上一篇下一篇 →

继续阅读

同一条线上的下一篇。

文章讨论

读到这里,留下一个判断。

把刚想明白的地方、还没想通的问题,留给下一位一起学习的人。

正在讨论 满窗之后,砍哪一段留哪一段 拆开 OpenAI Codex
3条讨论文章讨论 · 与共学社区同步
在共学社区查看
AM
Asha Morgan内容编辑
观点实践记录

我把这篇文章里的一个判断改写成了今天可以验证的小实验。比记住结论更有用的是,知道下一步要观察什么。

文章讨论7 有帮助
LH
Lin Harper独立开发者
观点观点

读完以后我先回头找它成立的条件,而不是直接把方法搬进项目。这个顺序让后面的取舍清楚很多。

文章讨论5 有帮助
KM
Kiki Moore产品运营
问题问题

如果把这个判断放到真实工作里,最先需要补的约束是什么?我想知道从阅读到第一次实践之间,哪一步最值得先做。

文章讨论4 有帮助