Codex 如何用不同方式解决上下文压缩问题
我逆向分析了 Codex 与 Claude Code 处理上下文溢出的差异,答案涉及 AES 加密、会话交接模式以及 KV 缓存技巧,整个架构远比想象中复杂。
如果你用 Claude Code 认真做过开发,一定见过这个场面:终端里出现”Compacting conversation…”,从那一刻起,感觉哪里不对劲了。模型开始忘记十分钟前讨论过的内容,响应延迟攀升,你询问刚刚一起重构的函数,它的回答却像是第一次听说一样。
问题根源很直接。Claude Code 的 200K token 上下文窗口比大多数人预想的填满速度更快——一次大型重构、几次文件读取、一些带冗长输出的工具调用,容量就耗尽了。当使用量达到阈值(大概是窗口的 75%-92%,我见过最早在 65% 时就触发),Claude Code 会对对话进行摘要压缩,丢弃原始消息,只保留摘要继续运行。没有进入摘要的信息就此消失。
我一直听说 OpenAI 的 Codex 处理这个问题的方式不同,于是花时间研究了所有能找到的公开分析。最有意思的成果来自 Krafton 首席 AI 官 Kangwook Lee,他用提示词注入的方式逆向工程了 Codex 实际的内部流水线。
压缩损失了什么,为什么重要
摘要化本质上是有损压缩。Claude Code 在压缩时,会在后台对完整对话进行摘要,生成一个压缩块,然后丢弃它之前的所有内容。CLAUDE.md 文件因为从磁盘重新读取所以得以保留,但任何只存在于对话中的内容,除非摘要器捕获了它,否则就消失了。
工具调用结果是损失最惨重的地方。当你让 Claude Code 读取一个文件,完整的文件内容会进入上下文;当你运行一条命令,完整的输出也会进入。这些工具调用结果往往是对话中信息密度最高的部分,而它们在压缩时恰恰被最彻底地抹平。一个 500 行的文件读取操作,变成了”读取了配置文件并记录了数据库设置”这样的一句话。具体的值、讨论过的边界情况、引用过的行号,全部消失了。
我亲历过几十次这种情况。压缩之后,我问”我们刚才看的那个辅助函数返回值类型是什么?“,得到的是一个言之凿凿但错误的回答。模型并不是在通常意义上产生幻觉,它只是在一份确实不包含我所询问内容的摘要上工作。
在一个漫长的会话中,经过 9 次或更多次压缩之后,问题会叠加恶化。每次摘要都会对前一次摘要再次压缩。早期会话中的决策依据彻底消蚀。到会话进行 10 小时后,模型已经完全不记得你当初为什么选择方案 A 而不是方案 B,即便你们当时花了二十分钟讨论过权衡取舍。
Codex 加密压缩流水线的内部机制
Kangwook Lee 的分析很巧妙。他使用两次链式提示词注入,提取了 Codex 压缩系统的内部行为。
第一次注入的目标是压缩器 LLM 本身。当 Codex 触发压缩时,并不是在本地进行摘要,而是将对话发送给 OpenAI 服务器上的一个独立 LLM 来生成摘要。Lee 的注入欺骗这个压缩器,使其将自身的系统提示词包含在摘要输出中。服务器随后用 AES 加密这个摘要(此时其中已包含泄露的提示词),并以不透明的数据块形式返回。
第二次注入利用的是解密步骤。将加密后的数据块加上一条精心构造的用户消息传回 Responses API,服务器会解密该数据块并组装模型的上下文。由于第一次注入已经将压缩器的系统提示词嵌入摘要中,解密后的上下文就揭示了整个流水线的工作方式。
他的发现是:调用 Codex 的 compact() API 时,一个独立的 LLM 对对话进行摘要,结果以 AES 加密的形式返回。在下一轮对话中,服务器解密这个数据块,在前面加上一段交接提示词(“这是上次对话的摘要”),然后将整体喂给模型。加密密钥存在于 OpenAI 的服务器上,客户端从不接触明文摘要。
压缩提示词本身与开源 Codex CLI 针对非 Codex 模型的压缩模板几乎完全相同,提示词工程上并无秘密可言。有意思的是架构设计:摘要的服务端加密、服务端解密与注入,以及一个客户端无法检查或修改、只能传递的不透明数据块。
为什么要加密?Lee 的分析没有给出确定性的答案。一种理论认为加密数据块中包含的不只是文字摘要,还有工具调用恢复数据、内部状态标记或 OpenAI 不希望暴露的结构化元数据。另一种可能性则更简单:加密数据块防止用户篡改摘要来操控模型行为。我认为第二种解释更有可能,但两者都未经证实。
OpenAI 还通过 Responses API 在服务端支持这一功能。设置 compact_threshold 值,当 token 数超过阈值时,服务器会在线执行压缩操作。压缩条目会在响应中流式返回,你将其追加到后续请求中。最近一次压缩条目之前的内容可以安全丢弃。
相比之下,Claude Code 的处理方式是:压缩块是人类可读的,你可以检查它,也可以通过 instructions 参数或在 CLAUDE.md 中添加自定义压缩指令来定制压缩行为。更透明,但信息损失的本质问题相同。
会话交接模式
抛开压缩机制不谈,更有意思的问题是:当你需要开启新会话但不想丢失上下文时,该怎么办?在这里,我看到了一个开发者的自动化方案,彻底改变了我对这个问题的思路。
这个模式是这样运作的:在压缩触发之前,一个 pre-compact hook 会封锁所有写入工具,防止模型在处于部分感知状态时修改代码。这是一个我多次遇到的故障模式:压缩在重构中途触发,模型忘记了哪些文件已经修改过,写入了相互冲突的编辑。
封锁写入之后,系统从 JSONL 会话日志中只提取用户消息和思考块,其余所有内容(工具调用、文件内容、模型回复)全部丢弃。这将日志压缩到原始大小的约 2%。
然后三个子 Agent 并行运行,各自在原始未压缩的 JSONL 日志中搜索提取步骤遗漏的信息,寻找的是:讨论过但没有出现在用户消息中的架构决策、只出现在工具输出中的错误模式、被放弃方案背后的原因。这些 Agent 将发现汇编成一个 resume-prompt.md 文件,其中包含会话摘要、差距分析结果以及修改过的文件列表。
一个 VS Code 文件监视器检测到新生成的 resume-prompt.md,自动打开一个以它作为初始上下文的新会话。新会话一开始就对上个会话的进展有清晰、完整的认知。
据报告,构建效率提升了 10 倍。这个数字难以独立核实,但架构逻辑是说得通的。你不再面对一个越来越降质的单一摘要,而是用一个经过差距检查、精心整理的交接文档开启全新的上下文窗口。
我自己也尝试实现了一个简化版本。差距分析步骤是价值最集中的地方。没有它,你只是用不同格式做了压缩已经在做的事;有了它,你在主动恢复摘要丢失的信息。我的版本用单个子 Agent 代替了三个,结果比直接压缩明显更好,但可能不如完整的三 Agent 方案彻底。
KV 缓存:被忽视的隐性成本杠杆
这里有一个大多数讨论完全忽略的性能维度。KV 缓存(注意力计算中的键值对)在提示词前缀相同时可以跨请求复用。两个共享相同开头 token 的请求,对这部分 token 的计算可以跳过。
数字很能说明问题。在对比稳定和扰动系统提示词的受控测试中,稳定前缀实现了 85% 的缓存命中率,首 token 中位延迟为 953ms;扰动前缀:缓存命中率 0%,首 token 延迟 2727ms。每次请求的成本从 $0.033 降至 $0.009,仅仅靠保持提示词前缀一致,延迟就降低了 65%,成本降低了 71%。
这对会话交接模式有直接影响。如果你的 resume-prompt.md 始终以相同的结构性前缀开头(系统提示词、交接说明,然后才是可变内容),固定部分就会被缓存。新会话中的每一个后续请求都能从这个缓存中获益。如果你打乱前缀结构或在前面注入可变内容,每次请求都会从头重新计算。
我围绕这个认知设计了自己的会话文件夹结构。基于会话 ID 的归档让交接文档井然有序,而 resume prompt 的固定前缀约定意味着每个新会话的前 4-5 万个 token 都能命中 KV 缓存。用 QMD(我另外专门介绍过的工具)对会话归档进行预索引,可以在子 Agent 需要检索历史会话时加速检索步骤。
真正重要的是什么
真正的收获并不是 Codex 的方式比 Claude Code 更好或更差。两者在压缩时都会丢失信息,两者都在长会话中挣扎。架构上的差异(加密不透明数据块 vs. 人类可读的压缩块)反映的是不同的设计哲学,但根本限制是相同的:上下文窗口是有限的,摘要化是有损的。
重要的是你在这个限制之上构建了什么。会话交接模式、差距分析、基于 JSONL 的检索、KV 缓存优化,这些都是针对一个模型改进无法完全解决的问题的工程解法。50 万或 100 万 token 的上下文窗口只是推迟了问题,并不能消除它。
AI 编程工具的瓶颈不在于模型智能,而在于上下文管理。构建能可靠检索被遗忘信息的系统,比构建能更准确摘要的系统更有价值。这一点我有切身体会:一个平庸的摘要配上好的检索,每次都胜过一个优秀的摘要配上没有检索。
技术细节来源:Kangwook Lee 的分析,以及 OpenAI 和 Anthropic 的公开 API 文档。
订阅通讯
获取最新 AI 洞见。