Codex 工程实践(二):上下文如何压缩与拼接
Coding Agent 一轮又一轮地读文件、运行工具,上下文很快会超过一开始的任务描述。真正难的地方是:空间变少以后,哪些信息还能支撑下一步决策;压缩完成以后,哪些规则需要重新组装进去。
《Codex 源码分析》第 6、7 章分别从核心循环和 Prompt 组装解释了这两个问题。把它们连起来看,上下文管理包含两项工作:维护可继续执行的历史,以及为下一次模型请求准备正确的材料。
本系列日期按博客时间轴编排,资料统一于 2026-11-05 核对。参考章节基于
f31e90b79f594c606746626a56bdf1e49497b275;本文另核对 Codex 上游86ac0916597244a2e67a197822a5483924660ff1的压缩实现。示例算法用于说明设计,不是源码的逐行复刻。
一、上下文由哪些东西组成
把上下文理解成聊天记录,容易忽略真正占空间、也真正影响执行的部分。
| 内容 | 作用 | 管理方式 |
|---|---|---|
| 基础指令与运行约束 | 定义行为、执行条件 | 从可信配置和运行状态构造 |
| 项目规则与能力目录 | 说明仓库约定、可用能力 | 按路径发现,控制体积 |
| 当前任务和最新纠正 | 决定这次要做什么 | 保留明确目标与变化 |
| 工具调用及结果 | 提供执行过程和证据 | 保持关联,减少低价值输出 |
| 历史总结与近期交互 | 支持跨阶段继续工作 | 压缩旧内容,保留近期细节 |
这里还存在一种重要差别:有些内容可以从文件和运行状态重新取得;有些内容只存在于本次交互中,例如用户刚刚撤销了一个方案。后者在压缩时更容易丢失,也更需要被明确保存。
二、为什么压缩会出现在回合中途
第 6 章展示的核心循环是分层的:会话接收操作,任务驱动一个回合,一个回合里又会多次请求模型与执行工具。
1 | 接收用户任务 |
所以,“一次用户输入”并不等于“一次模型调用”。代理在执行一个复杂任务时,可能已经读入数十份材料;即使用户没有再发消息,也可能需要在回合中途压缩。
容量预算也不能只看当前文本。下一次请求还需要容纳新工具结果、补入的上下文和输出。可以用下面这个设计关系理解:
1 | 当前有效历史 + 待拼接内容 + 预留输出空间 ≤ 可用上下文预算 |
这只是预算模型。实际计量方式和触发点受运行版本、模型元信息与实现路径影响,不宜把某个教程里的固定阈值当作所有模型的标准。
三、压缩的对象是历史表示
我会把“压缩”分成三种处理,避免混淆。
输出裁剪处理体积。例如把很长的日志保留首尾,注明中段省略,并留下原始文件路径。这会丢掉内容,适合处理暂时不需要逐行阅读的输出。
语义摘要处理信息密度。它把多个步骤转成当前状态:已经尝试什么、验证了什么、哪些方案已废弃、接下来做什么。摘要同样可能丢信息,也可能写错。
历史替换则让后续请求使用新的历史表示。它必须保持消息结构和上下文边界,不能只把一段摘要随意追加到原始历史末尾。
在本次核对的上游 compact.rs中,可以看到摘要构造、新历史生成、初始上下文组装与 replace_compacted_history 等步骤。实现还处理了待接续输入和上下文插入位置。这些细节说明,压缩不仅是让模型“总结一下”,还需要由运行时安装一份可继续使用的历史。
不同版本及本地、远程压缩路径可能采用不同表示;不能假定所有压缩结果都是用户可读的一段短文。
四、一份有用的摘要要保留什么
任务摘要应该优先支持恢复执行。下面是我建议的结构:
1 | 目标:修复跨时区日期排序,公共 API 保持兼容。 |
这比“已经分析并修复了日期问题”更有用,因为它保留了约束、验证范围和未完成动作。
一个常见压缩错误是把状态写得过于肯定。“局部测试通过”被压成“测试通过”,后续代理可能跳过完整验证。另一个错误是把废弃方案留成当前决定,导致任务绕回原路。
因此,摘要中的事实应带上证据位置和状态;推测与未验证内容继续保持未验证。完整原始材料可以留在外部文件,摘要只保留检索入口。重新读取时还要检查文件是否被改过,路径本身不代表内容未变化。
五、拼接时保留角色、来源与顺序
第 7 章最有帮助的地方,是把 Prompt 展示为不同来源片段的组装结果。
参考版本中,基础指令可以来自显式配置、历史保存的信息或模型默认指令;Skill 的可用目录进入 developer 片段,被选中的 Skill 正文则包装为带名称和路径的 user 片段。这些是参考版本的具体实现,不是所有代理系统必须采用的角色分配。
可以把组装理解为:
1 | 模型请求 |
这是一张组成示意,真实序列需要遵循实现中的插入规则。核心原则是:拼接不能抹掉来源差异。用户授权、项目约定、工具返回的网页内容,各自承担不同角色。外部材料即使写着“忽略之前的要求”,也不应因此升级为运行策略。
工具调用与结果也需要保留标识关联。裁剪时留下结果却丢掉调用,或保留调用却丢掉结果,都可能让模型误解执行状态。恢复中断历史时,应明确记录未知结果,而不能凭一条调用推断操作已经成功。
六、Skill 为什么先给目录,再读取正文
安装的 Skill 越多,把全部正文放进每次请求的成本越高。参考第 7 章采用的思路是渐进加载:先展示名称、描述和路径,让代理知道有什么能力;命中任务需要的 Skill 后,再加载具体步骤。
在目录预算不足时,参考实现还区分三种情况:完整描述可以放下就保留;最小目录行可以放下时缩减描述;连最小行也放不下时只能保留部分目录并报告预算问题。
这里有两个实践启发。
一是目录应该便于选择。简短、能区分用途的描述,比把整份操作手册复制进描述更合适。二是“目录里可见”和“本轮已加载正文”必须分别观察,否则很容易把未命中、名称冲突或预算裁剪误判为模型不会使用工具。
Skill 的说明文本也不等于权限。它要求运行某个程序,仍需由工具系统按当前权限执行。
七、怎样检查压缩与拼接是否有效
如果在自己的 Harness 中实现这套机制,可以用下面的流程做设计参照:
1 | 统计历史、待注入片段与预留空间 |
检验重点不只是 token 降了多少。更关键的是:用户最新纠正是否保留,未完成动作是否仍能识别,摘要中的证据能否定位,下一步是否重复已经做过的副作用操作。
例如,在“修改文件完成、测试尚未运行”时触发一次压缩,再继续任务,观察代理是否会先运行测试,是否还记得禁止新增依赖。这类固定恢复任务,比只看摘要读起来是否流畅,更能判断压缩是否支持实际工作。
上下文越长,越需要把任务状态、来源和证据组织清楚。压缩负责让历史保持可用,拼接负责让当前规则与材料正确进入下一次请求;两者的交接处,往往就是长任务能否稳定继续的关键。
参考阅读
上一篇:个人与企业如何减少使用漂移。下一篇:工具生命周期、权限控制与沙箱。