Codex Harness 源码课 / 中文
LESSON 07 / 12 45 分钟

上下文压缩:触发、保留与窗口重建

比较本地摘要、Remote V2 和 TokenBudget,读预算公式、保留白名单和失败处理。

公开源码快照 · 2026-10-13 · f82e365922

学完你能做到

本课目录 · 7 节

1. 先把三件事分开

在本版本中,、摘要和窗口重置都有可能降低活跃输入,但它们的机制不同。单项工具输出发生在入库;整个窗口的 compaction 在特定阶段运行;结果装回后,还要重新建立环境与指令基线。不能把这些全部描述为“自动总结旧消息”。

机制谁决定删减保留方式
单项客户端容量算法确定性保留首尾,或将指定输出改成标记。
本地摘要分支客户端规则 + 一次请求生成交接摘要,客户端按预算选择用户原文,再注入当前环境。
客户端准备/保留规则 + 服务端结果客户端发送触发项,校验一个 结果;服务端内部不可见。
窗口重置客户端新窗口逻辑不生成摘要,重建初始,并按开关保留特定客户端项。
达到压缩条件后,先看 TokenBudget,再按 provider 能力选择远程 V2 或本地摘要;三条路径重新安装窗口。
达到压缩条件后,先看 TokenBudget,再按 provider 能力选择远程 V2 或本地摘要;三条路径重新安装窗口。 · 教学示意,省略次要分支;不是完整调用图。

2. 触发条件:不是固定的“还剩 10%”

context_window_token_status 把预算拆成活跃总量、自动计量范围、完整窗口上限和结束阈值。活跃总量主要由最近一次服务端 usage 加上之后新增项的本地估算构成;服务器是否已经计入过去的 reasoning,影响客户端是否补估。它不是每次把整段历史交给精确 tokenizer 重算。

活跃使用量的计算 ↗codex-rs/core/src/context_manager/history.rs:935–959

变量 / 配置本版本的计算含义
A:活跃总量get_total_token_usage()用于判断完整窗口是否达到上限。
S:自动计量Total 模式:A;BodyAfterPrefix 模式:max(A − P, 0)P 是已知 基线;没有时以当前 A 作为基线。
L:计量额度从本模式的信息或配置解析它不是所有共享的常数。
H:可用完整窗口resolved_context_window × effective_context_window_percent / 100独立于计量模式的完整窗口上限。
F:fallback 余量来自 token_budget.fallback_buffer_tokens()有对应 fallback 机制时才保留这个余量。
token_limit_reachedS ≥ L + F OR A ≥ H(只比较有值的限额)即使只对正文计量,也不能越过完整窗口 H。

预算、余量与阶段阈值的完整公式 ↗codex-rs/core/src/session/context_window.rs:59–130

3. 三个时点,加上模型切换

新输入前的容量检查与 TODO ↗codex-rs/core/src/session/turn.rs:181–188

回合中继续及窗口条件 ↗codex-rs/core/src/session/turn.rs:609–649

回合结束阶段 ↗codex-rs/core/src/session/turn.rs:720–744

兼容 hash 和较小窗口模型切换 ↗codex-rs/core/src/session/turn.rs:1336–1432

4. 本地摘要:20k 用户原文预算,加一份交接摘要

“本地”是客户端组织普通请求,不是离线算法。它克隆历史,加入 compact_prompt(未配置时采用仓库内交接摘要模板),调用,再从返回结果构建新历史。模板要求保存进展、决策、约束、未完成事项和关键资料;这一要求不能保证每个细节无损保留。

摘要提示的选择 ↗codex-rs/core/src/compact.rs:113–150

交接摘要模板 ↗codex-rs/prompts/templates/compact/prompt.md:1–9

客户端先提取真实用户消息,排除旧 summary。build_compacted_history_with_limit 从最新用户消息往前取,预算为 COMPACT_USER_MESSAGE_MAX_TOKENS=20,000 个近似 。能完整容纳且全为文本的消息保留原文本项;越过边界时重建文本 fallback,首尾到剩余预算,并标明来源不完整;最后反转,恢复时间顺序,再追加摘要。媒体不会在文本 fallback 路径被原样克隆。

用户原文选择入口 ↗codex-rs/core/src/compact.rs:573–611

真实源码 · Apache-2.0 · 未改写
    if max_tokens > 0 {
        let mut remaining = max_tokens;
        for message in user_messages.iter().rev() {
            if remaining == 0 {
                break;
            }
            let tokens = approx_token_count(&message.message);
            let ResponseItem::Message {
                id,
                content,
                internal_chat_message_metadata_passthrough,

从最近消息开始消耗预算。选择优先级在这段代码里是时间顺序,不是相关性评分。

查看原文及相邻代码 ↗

边界消息、媒体和摘要项的重建 ↗codex-rs/core/src/compact.rs:718–781

20k 只约束这一组选中的用户原文,不包含摘要、重新注入的指令和其他请求开销。旧链与助手过程通常不作为这条本地分支的原文集合保留;它们依赖摘要延续信息。若生成摘要的请求本身超窗,代码会从旧历史前端删项,成对移除关联结果,然后重试;一般流错误才按退避预算处理。

超窗与一般请求失败的不同处理 ↗codex-rs/core/src/compact.rs:332–371

删除最老项时维护调用配对 ↗codex-rs/core/src/context_manager/history.rs:683–695

5. Remote V2:先救输入容量,再选原文保留形状

远程请求前,trim_function_call_history_to_fit_context_window 从历史尾部往前处理连续可改写的输出,把过大的结果正文替换成固定说明,同时保留调用关联和结果状态。遇到用户/开发者消息等不可改写边界即停止;启用保留客户端输出时,这种输出也构成边界。这不是任意扫描并删掉最大的输出,也不是删最早消息。

远程请求前的尾部输出改写 ↗codex-rs/core/src/compact_remote_history.rs:76–146

然后发送 CompactionTrigger,收集流式结果。collect_compaction_output 要求响应有 completed,并且恰好一个 输出;可以同时到达其他输出项,但缺少或多个项都是错误。客户端不能从这个接口证明服务端采用了怎样的摘要、检索或编码算法。

触发项和请求字段 ↗codex-rs/core/src/compact_remote_v2_attempt.rs:81–113

完成事件与压缩项校验 ↗codex-rs/core/src/compact_remote_v2.rs:435–497

原文类型Remote V2 安装新历史时是否保留附加条件
真实用户消息、HookPrompt进入保留候选按 parse_turn_item 判断,不是所有 role=user 文本。
客户端 developer 消息功能开启才候选要求 client_authored ;正文角色不能替代来源标记。
客户端具名独立输出功能开启才候选要求 client_authored、FunctionCallOutput、=None、name 有值;不是所有普通工具结果。
代理之间的消息部分候选排除后代进度与 FINAL_ANSWER;单项估算 ≤10k。
普通助手消息、普通调用和对应结果不由该白名单原样保留相关信息能否在 中延续,不能由客户端判断。

保留候选白名单 ↗codex-rs/core/src/compact_remote_v2.rs:499–593

被特殊保留的输出身份 ↗codex-rs/core/src/compact_retained_tool_outputs.rs:1–20

候选进入 64,000 保留预算。实现按来源项和附带 notice 分组,从最新组向前扫描;边界文本可,特殊保留输出必须整项保留或停止,不截半条。是否计入图像容量由 RetainedImageBudget 分支决定,不能把文字额度当成媒体容量保证。结果恢复顺序后追加 ,再注入当前状态。64k 同样不是最终请求总上限。

分组预算与整项输出边界 ↗codex-rs/core/src/compact_remote_v2.rs:618–722

6. TokenBudget:没有摘要,换一个窗口

run_auto_compact 首先检查 。若开启,会跳过 与本地摘要,调用 start_new_context_window。后者重建完整初始,只按开关保留特定客户端 developer/独立输出,使用同一 64k 保留预算;普通对话不会在这段函数中自动变成一份摘要。

压缩实现的选择顺序 ↗codex-rs/core/src/session/turn.rs:1461–1513

相同生命周期下跳过摘要 ↗codex-rs/core/src/compact_token_budget.rs:15–69

新窗口实际装入什么 ↗codex-rs/core/src/session/mod.rs:4658–4718

该功能在通用 feature 表中默认 false,但默认值或满足条件的实验模式可以请求启用,管理要求又可能将它固定关闭。因此“仓库默认关闭”与“某次运行没有启用”不是同一结论。窗口间的信息恢复还可能依赖 history-notes 等扩展贡献;不能把可选桥接写成所有安装都具有的记忆系统。

通用 feature 默认值 ↗codex-rs/features/src/lib.rs:1889–1894

模型默认值的启用边界 ↗codex-rs/core/src/session/token_budget.rs:128–158

可选窗口提示与 notes 桥接 ↗codex-rs/core/src/session/mod.rs:4411–4478

7. 安装历史:新摘要不应覆盖当前权限事实

摘要或远程结果产生后,build_compaction_replacement_history 从当前 Session/ 重新生成指令和环境,而不是把旧环境原文全部当摘要的一部分。前缀放在最前;常规优先插到最后一个真实用户、相关代理消息或保留输出之前;没有这些项时放到摘要或项之前。

重建当前状态与前缀 ↗codex-rs/core/src/compact.rs:80–111

插入位置的回退规则 ↗codex-rs/core/src/compact.rs:610–681

Session 安装替换历史、新的参考和状态基线,推进窗口 ID,并重算使用量。前失败时,run_turn 先保留尚未记录的输入,再发出错误;取消、预算错误与普通重试也有不同分支。是一次有生命周期的状态转换,不是“把旧数组换成一句话”就结束。

替换历史与用量重算 ↗codex-rs/core/src/compact.rs:375–423

回合前失败先保留输入 ↗codex-rs/core/src/session/turn.rs:189–221

沿着源码读

以下定位均锁定本课程版本。先读注释与分支,再深入类型定义。

READ & VERIFY / 小练习

实现推演

设历史依次为用户 U1、工具输出 O1、助手 A1、用户 U2、具名客户端独立输出 O2。分别按本地摘要、(相关保留开关开启)和 新窗口列出原文候选。再给保留预算填入模拟值,判断边界项会被、整项留下还是丢弃。不要推测远程 里隐藏了什么。

阅读练习无需模型账号。示例命令请先确认目录与环境。

自测与边界检查

为什么 20k 或 64k 不能解释为最终输入的最大容量?

展开自测答案

这些常量仅约束原文保留集合;摘要/、重注入的指令、环境和其他请求字段另计。整个窗口还受可用上限约束,近似计数也不等于精确 tokenizer。

所有 compaction 都会调用生成摘要吗?

展开自测答案

不会。 分支直接重建窗口。非该分支再按 provider 能力选择 或本地摘要。功能默认值、激活默认值、实验配置与管理要求都可能影响实际分支。

学习记录保存在本机

搜索课程

输入关键词,搜索全部课程、词典与来源。

就地词典

在完整词典中查看 →

完整架构总图

放大后横向、纵向滚动阅读;聚焦图示区域后可用方向键滚动。

完整架构职责图,内容与正文总图相同