Codex Context Compaction 真相:Agent 为什么压缩后还能接着干活?

从 Codex Remote Compact 看长程 Agent 如何保存现场、重建规则并继续执行。

原文链接AI小老六

如果你长时间用过代码 Agent,大概率遇到过这种场景:前面半小时还在很认真地查仓库、跑测试、解冲突,下一轮突然像换了个人,只记得"正在处理一个项目",但忘了分支名、PR 编号、刚才失败的是哪个测试。

这类崩坏通常不发生在第一轮。它发生在上下文被压缩之后。

所以过去很多人的习惯很保守:上下文快满之前,先让 Agent 写一份交接文档;重要任务做到一半,尽量不要自动 compact;能新开会话就新开会话。听起来麻烦,但至少可控。

最近 Codex 的风向不太一样。越来越多用户开始把上下文窗口这件事交给系统自己处理。它自动压缩,继续跑,再压缩,再继续跑。最让人意外的不是它"还能回答",而是它在复杂任务里还能记住不少低层事实:分支、stash、merge commit、构建结果、用户最后一句确认。

这背后不是某个提示词突然写得更聪明了。更准确地说,Codex 把 compact 从"一段摘要"做成了一次状态迁移。

图:长程 Agent 真正需要保住的是现场碎片,而不只是文章主线。

窗口不见了,不代表窗口不重要

Codex App 曾经把 context window 的使用比例放在主界面上。后来这个指示器被挪到了二级入口,只能通过 /status/compact 之类的命令查看。这个改动让不少重度用户很不爽。

原因很好理解。对短问答来说,上下文窗口只是一个技术参数;对长程 coding 来说,它更像油表。你不一定每秒盯着它,但你需要知道车还能跑多远。

Codex 团队当时给出的方向是 "​vibe contexting​":用户不再需要主动管理上下文。

这句话如果放在一年前,很像产品经理的漂亮话。因为压缩一直是 Agent 链路里最脆弱的地方。它不是把文本缩短那么简单,而是在决定哪些事实还能活到下一轮,哪些事实永远丢掉。

Codex 敢把窗口感知降级,真正依赖的是下面几层机制:入口截流、远程压缩、系统脚手架重建,以及服务端和模型的配合。

真正进入 compact 之前,很多内容已经被处理掉了

很多人以为上下文管理就是"满了以后压一下"。Codex 不是这么干的。

在 full compact 之前,它已经先把一批容易撑爆窗口的内容压住了:

位置 Codex 怎么处理 这意味着什么
工具输出 工具结果进入历史时就做 middle truncation,保留头尾 中间细节可能直接消失
终端输出 PTY 原始输出只留头尾,总量有限 大日志不会完整进上下文
Hook 结果 太大的 hook 输出写到临时文件,只给预览和路径 模型需要时可以再读,但不会默认吃满窗口
历史消息 清理不成对的 tool call/output、孤儿输出和不支持的图片 保证请求结构干净
系统上下文 只追加变化部分,不每轮重复塞满配置 降低固定上下文的增长速度

这里有个细节挺重要:很多截断发生在内容进入历史的那一刻。也就是说,它们发生在 prompt cache 形成之前。

这不是洁癖,是成本问题。模型服务端的前缀缓存依赖逐 token 一致。如果一条旧工具结果已经进入历史,后面再回头改它,改动点之后的缓存可能全废。对十几万 token 的长程会话来说,一次 cache miss 可能就是一轮账单和延迟的数量级变化。

Codex 的选择比较硬:先在入口控制住体积。代价也很明确,被截掉的内容未必能回来。

Local Compact​:一份交接文档能救简单任务,救不了复杂现场

Codex compact 的第一条路叫 ​Local Compact​。它的做法并不神秘:让当前模型给后续模型写一份 handoff summary。

流程大概是这样:

图:Local Compact 以明文摘要替换旧历史

这套方案非常像人类工程师写交接文档。写得好,后面的人能接上;写漏了,后面的人只能猜。

Local Compact 最大的问题就在这里:压缩后的 replacement history 里,很多原始材料不会再出现。assistant 的中间判断、tool call 的原始结果、reasoning、web 结果,大多只能靠 summary 间接留下来。

简单任务还能扛住。比如开一个 GitHub issue、补一条评论、确认几个 label,summary 抓住主线就行。

复杂任务不一样。长程 coding 里真正致命的常常是小事实:

  • 当前在哪个分支;
  • 哪个 stash 是临时绕过签名用的;
  • 哪个测试 315/315 通过;
  • 哪个构建失败是 x86_64 slice 缺失;
  • 用户最后是否已经验收。

这些东西如果没被 summary 写进去,下一轮模型不会"自然想起来"。它只能重新查,或者老老实实说不知道。

Remote Compact​:客户端看不懂,但效果明显不一样

第二条路是 ​Remote Compact​。这里开始变得有意思。

Codex 会根据 provider 判断能不能走远程压缩。OpenAI 原生 provider 和 Azure Responses provider 支持;常见第三方 provider、中转站、本地模型入口默认不支持。分流逻辑大致是:

图:不同 provider 下的远程压缩路径

Remote V1 走专用 /v1/responses/compact endpoint。Remote V2 则把压缩触发器塞进普通 Responses stream:输入末尾多一个 CompactionTrigger,服务端返回一条特殊的 compaction item。

两条远程路径最后都会产生类似的东西:encrypted_content

客户端不会拿到一段可读摘要。它拿到的是一块 opaque state,一段加密内容。后续请求继续带上它,由服务端解密并组装给模型。

图:encrypted_content 在服务端完成状态迁移

这也是 Remote CompactLocal Compact 的本质区别:Local 留给客户端一份"文本交接";Remote 留给服务端一份"状态块"。

图:Remote Compact 的关键变化,是把压缩从文本摘要推进到服务端状态承载。

实验最刺眼的地方:差距不是 5 分,而是一个等级

如果只看机制,很容易陷入猜测。真正有说服力的是同一批长程任务分别走 Local、Remote V1、Remote V2 后的恢复效果。

一类简单任务里,Remote 比 Local 稳定一些,但差距还不算夸张:

测试内容 Local Remote
主线和决策链恢复 89.7 96.6
反事实判断 88 100
低层事实定位 84.0 92.3
禁用工具后的续跑 86 92
允许工具后的最小验证 88 95
平均 87.1 95.2

真正拉开差距的是复杂 coding 任务。比如持续处理一个 Multi-Mac PR:merge upstream、解冲突、stash 管理、构建验证、真机测试、用户验收,全都混在同一条会话里。

压缩路径 客观评分 关键事实命中
Local Compact 约 28/100 5/24
Remote V1 约 94/100 23/24
Remote V2 约 92/100 23/24

Local 的失败很克制,但也很致命。它没有大规模编造,而是大量 unknown。它知道自己不知道。可对执行任务来说,这几乎等价于失忆。

Remote V1/V2 则能恢复仓库、分支、merge commit、PR #137、stash 内容、测试通过数量、真机型号和用户确认。这里面不少事实都不是"文章主旨",而是现场碎片。长程任务能不能接上,往往就靠这些碎片。

所以 Remote Compact 的优势不像是 prompt 稍微好一点。它更像保留了 Local summary 留不住的状态。

服务端黑盒:提示词之外还有多少操作空间

有人用 prompt injection 研究过 Remote Compact 的服务端流程。能看到的大致是这样:服务端有一个 compactor LLM,它读入系统提示和会话历史,写出一份明文 summary;随后服务端把这份 summary 加密成 blob。下一轮请求时,服务端再解密 blob,拼上 handoff prompt,交给模型继续执行。

麻烦也在这里。泄露出来的 compaction prompt,和 Local Compact 开源出来的那段提示词非常接近。既然提示词差不多,为什么复杂任务里一个接近失忆,一个还能恢复大部分现场?

我更倾向于把 prompt injection 看到的东西理解成"表层文本",不是完整机制。服务端真正能动手脚的地方,比那几行 prompt 多得多。

图:Remote Compact 可能包含隐藏信号和上下文重组

第一种可能,是服务端给 compactor 的输入里夹了额外信号。比如 tool call 的结构化摘要、关键实体列表、conversation 的元数据标注。它们不一定会以自然语言形式出现在 prompt 里,所以即使用注入方式套出了提示词,也看不到这些中间信号。

第二种可能,是加密 blob 里不只有一段普通 summary。表面实验看到的是 handoff 文本,但服务端在加密前完全可能追加后处理结果:工具输出摘要、关键决策节点、甚至某种更适合恢复任务状态的结构化表示。客户端只拿到 encrypted_content,没法判断里面到底装了什么。

第三种可能,是 Remote 的压缩器本来就不是用户当前正在用的模型。Local Compact 只能调用当前配置模型来写交接文档;Remote Compact 的 compactor 由服务端选择,它可能针对压缩任务做过优化,更擅长从一堆工具调用和对话碎片里抓住任务状态。这个猜测没法从客户端证实,但能解释为什么同一套提示词会拉开这么大差距。

第四种可能,发生在解密之后。能看到 summary 被放在 handoff prompt 后面,但服务端组装最终上下文时是否还有检索、重排、裁剪或额外注入,外部很难观察。换句话说,压缩不是一次"模型写摘要"就结束了,后面还有 context assembly 这一步。

所以 Remote Compact 的强点未必是"提示词更好"。更可能是服务端掌握了从压缩、加密、保存、解密到重新组装上下文的整条链路。客户端只能看到 encrypted blob 进来、模型状态恢复出去,中间发生了什么基本不可见。

这也是它最让人不踏实的地方:效果确实强,但不可审计。你不知道它保住了哪些事实,也不知道哪些事实已经在黑盒里丢了。

只靠压缩还不够,系统规则必须重新搭起来

长程 Agent 压缩后容易出问题,还有一个原因:它不只会忘任务,也会忘规则。

比如项目里的 AGENTS.md 怎么要求,当前 sandbox 策略是什么,哪些工具可用,哪些 skill 已安装,工作目录和 git 状态是什么。这些东西如果也靠 summary 传递,压几次以后迟早变形。

Codex 的做法是:系统约束不交给 summary。

compact 完成后,Codex 会清掉一个 baseline 标记。下一轮开始时,它发现 baseline 不存在,就重新构造 initial context。这个脚手架会把开发规则、权限策略、可用工具、MCP、插件、环境信息、协作模式等重新注入。

图:任务状态与系统脚手架在下一轮重新合流

这点很关键。summary 只需要记住"任务发生了什么"。至于"当前系统要求模型怎么工作",下一轮重新搭。

换句话说,Codex 的恢复不是一条腿走路:

  • compact 负责迁移任务状态;
  • prompt scaffold 负责恢复执行环境和规则。

很多人只盯着 summary,就会低估第二层机制的作用。

Claude Code 的思路不同:它更像在维护一张视图

拿 Claude Code 对比,会更容易看出 Codex 的设计取舍。

Codex 像 checkpoint/rebase:压缩后生成一段 replacement history,把旧历史换成新基线。

Claude Code 更像数据库 view:原始记录尽量 append-only,模型看到的是预算、外置、折叠、缓存编辑之后的投影视图。

维度 Codex Claude Code
大工具输出 入口截断,简单直接 外置到 sidecar 文件,保留读取路径
full compact 前的减压 主要靠入口控制和历史清洗 tool budget、snip、micro compact、context collapse 多层处理
compact prompt 极简 handoff 100+ 行审计清单式 prompt
服务端协作方式 返回 opaque encrypted state cache_editscontext_management 等声明式能力
客户端透明度 低,服务端空间大 高一些,但客户端逻辑复杂

两边没有绝对优劣。Codex 把更多事情委托给服务端,效果可以很强,但黑盒感重。Claude Code 把更多控制留在客户端,工程复杂,调试空间也更大。

我更愿意把它们看成两种架构性选择,而不是两种摘要写法。

Prompt Cache 是这套设计里最现实的约束

为什么 Codex 不在历史中间做细粒度修改?为什么 Claude Code 那么在意外置决策"冻结"?绕不开 prompt cache。

长程会话里,单次请求十几万 token 并不稀奇。上一轮已经出现过的前缀,如果这一轮逐 token 一致,就可以命中缓存。命中的 token 便宜得多,也快得多。

但缓存很脆。你改了历史中间某个旧工具结果,从那里往后的 token 都可能失去命中。

所以两个系统都在避免"中间切一刀":

设计动作 背后的 cache 考虑
Codex 在内容入史前截断 还没形成缓存,不破坏旧 prefix
Codex full compact 时替换整段 history 不做局部手术,直接建立新基线
Claude Code 冻结外置决策 保证后续 wire format 不变
Claude Code 用 cache_edits 让服务端动缓存层,不改客户端消息内容

这也是上下文压缩最容易被忽略的地方。不是"文本越短越好"。信息保留、窗口大小、缓存命中率三者之间一直在互相拉扯。

更大的变化:模型和 Harness 正在绑在一起

Remote Compact 不是一个孤立功能。它其实暴露了 Agent 竞争的下一层:模型和 Harness 正在变成一套东西。

过去大家更关心"哪个模型更强"。现在越来越多体验差异来自模型外面的那层系统:工具协议、​上下文压缩​、缓存策略、权限模型、IDE 集成、历史状态恢复。

OpenAI 有 Codex 的 encrypted_contentCompactionTrigger。Anthropic 有 Claude Code 的 cache_editscontext_management。这些能力并不是裸模型 API 的简单包装,而是服务端、客户端、训练数据一起配合出来的。

第三方 Agent 当然还能做得很好,但它要追的东西变多了:协议细节、服务端 beta 能力、模型后训练习惯、真实用户长程数据。任何一个环节慢半拍,体验都可能差一点。

这也是为什么 "​Model + Harness = Agent​" 会越来越像行业共识。只提供模型,不做 Harness,就等于把最终用户体验交给别人,也失去了最有价值的交互数据。

结语:上下文压缩已经不是摘要问题

Codex 多次 compact 后还能继续跑,原因大概可以拆成三层:

  1. 入口层先控制工具输出和历史膨胀,避免窗口太快被噪声吃掉。
  2. Remote Compact 把任务状态交给服务端处理,用 opaque state 承载比明文 summary 更强的恢复能力。
  3. Prompt scaffold 在压缩后重建系统规则,让 summary 不必背负所有环境约束。

这套机制当然还有黑盒问题。encrypted_content 里到底有什么?服务端 compactor 是什么模型?解密后怎样重组上下文?外部只能猜个轮廓。

但实验结果已经足够说明一点:长程 Agent 的能力,很多时候不只取决于模型会不会推理,还取决于它怎么保存现场、怎么遗忘、怎么在遗忘后重新接上。

所谓 "​vibe contexting​",表面上是用户不用管上下文了。底下其实是一堆很不 vibe 的工程。

推荐阅读

Dynamic Workflows 深度解析:Claude Code 为什么把多 Agent 编排写进可执行代码

Claude Opus 4.8 Agent 交付力拆解:为什么它更像工程负责人?

为什么 AI Coding 难进生产环境?深入了解 Everything-Claude-Code!

平台智能化到了分水岭:为什么配置代码化才是 AI Coding 的下一代接口

Agent Runtime 九个关键设计:状态外化、上下文压缩与多智能体协同