拆解四款 Code Agent 的上下文压缩机制——OpenCode、Codex、Claude Code、Pi 各自怎么做,以及它们殊途同归的设计共识。
先搞清楚:这里的「压缩」不是 zip
如果把 AI 编程助手(Code Agent)想成一个帮你写代码的同事,它面前只有一块有限大小的白板。
你让它查文件、跑命令、读日志、改代码——这些全会写到白板上。写满了,新的就放不下了。轻一点答非所问,重一点直接报错停工。
这时候就需要「上下文压缩」。但这里的压缩不是 zip 那种无损打包,而是把旧内容重新整理成一份更短、更适合继续干活的版本——本质是有损的任务交接。
白板上到底装了什么?
很多人以为上下文只有「聊天记录」。远不止如此:
- 用户和 AI 的对话
- 读取过的文件内容
- 命令行输出和执行日志
- 工具调用的参数和返回结果
- 图片、PDF 等附件
- 项目规则文件(
AGENTS.md、CLAUDE.md) - MCP 工具描述、skill 描述、auto memory
真正把上下文撑爆的,往往不是你说的那句话,而是 AI 干活过程中产生的大量中间产物——尤其是动辄几百行的日志和工具输出。
压缩的正确心智模型:四层记忆
要理解上下文压缩,最好不要把它想成「一刀切的摘要」,而是想成一套分层记忆管理:
| 层次 | 内容 | 处理方式 |
|---|---|---|
| 固定层 | 团队规范、个人偏好、项目规则 | 放在文件里,不进聊天记录 |
| 热数据层 | 最近几轮对话、刚改的文件 | 保留原文 |
| 温数据层 | 更早的工作历史 | 压缩成结构化交接单 |
| 冷数据层 | 大文件全文、历史日志、记忆库 | 不进当前窗口,按需检索 |
所有主流 Code Agent 的上下文管理,本质上都在做同一件事:把对的内容放到对的层次上。
什么时候触发压缩?
不能等白板满了才动手——那时候已经来不及了。
预防性触发:每轮对话结束后,系统估算当前上下文占了多少空间。接近上限就在下一轮开始前主动压缩。就像开车看油表,剩四分之一就找加油站。
故障性触发:如果模型因上下文太长直接报错,系统也会紧急启动压缩。这是兜底机制——不理想,但至少能让工作继续。
手动触发:用户主动执行 /compact 命令(Codex 和 Claude Code 都支持),适合你觉得对话已经太长、想清理一下的场景。
压缩前后到底发生了什么?
压缩不是逐条「缩短」旧消息,而是把一整段历史折叠成一个检查点。
压缩前,消息流大概长这样:
U1 用户:「帮我修一下支付页面的 bug」
A1 Agent:分析代码,调用 read_file、grep_search 等工具
U2 用户:「后端先别改,保持兼容」
A2 Agent:继续排查,执行 bash 跑测试,输出大量日志
U3 用户:「看起来是前端传参问题,帮我改 PaymentForm.tsx」
A3 Agent:修改文件,跑测试验证
(中间夹杂大量文件内容、终端日志、工具输出……上下文快满了)
压缩后,后续模型实际看到的只有这些:
U4 [compaction task] "What did we do so far?"
A4 [summary checkpoint]
## Goal
修复支付页面提交报错
## Constraints
后端先不改,保持兼容
## Accomplished
定位到 PaymentForm.tsx 传参为空,已修改前端逻辑
## Relevant Files
src/PaymentForm.tsx, src/api/submitOrder.ts
## Next Steps
补充单元测试,确认异常是否消失
U5 [replay] 用户:「看起来是前端传参问题,帮我改 PaymentForm.tsx」
→ Agent 从这里继续工作
U1-A3 的原始消息仍在数据库里,但已不再进入后续 prompt。A4 这条 summary 就是新的「检查点」,U5 把最后一条真实用户请求重放回来,确保 Agent 不会丢掉当前意图。
这是整个机制最关键的设计——用检查点划定新边界,而不是改写历史。
四家产品怎么做?一个一个拆
OpenCode:流水线最清晰
OpenCode 把压缩做成了一条完整流水线,每个环节的职责非常明确。
第一步:先清理噪音。 不是一上来就摘要,而是先对旧工具输出做 prune——保护最近两轮 turn 和最近 40,000 token 的工具结果,更老的降级为一行占位文本 [Old tool result content cleared]。数据还在,但不再吃上下文空间。
第二步:生成交接单。 用专门的 compaction agent(可以自定义模型和提示词)把旧历史压成结构化摘要,格式包括:Goal / Instructions / Discoveries / Accomplished / Relevant files。
第三步:自动续跑。 压缩完不停下来。如果是因为 overflow 触发,系统会找到最后一条真实用户请求重新「重放」(replay),让工作无缝接续。如果找不到可重放的请求,就插入一条「继续下一步」的合成指令。
亮点:压缩是消息流里的一种正式任务类型,和普通 prompt 共用一条主循环,不需要额外的后台状态机。支持插件钩子自定义。
Codex:两条压缩路径并存
OpenAI 的 Codex 比较特殊——它同时存在「本地可读摘要」和「服务端 opaque 压缩」两种机制。
本地侧:compaction prompt 写得很直接——要求生成给「另一个 LLM 接着干活」的 handoff summary,包括当前进度、关键决策、重要约束、剩余步骤。同时有一个关键常量 COMPACT_USER_MESSAGE_MAX_TOKENS = 20,000:从后往前收集最近用户消息保留原文,更早的部分才走摘要。
服务端侧:OpenAI API 提供了 context_management + compact_threshold 选项,触发后响应里会出现一个加密的 compaction item——不是给人读的,而是模型内部的状态压缩。还有单独的 /responses/compact 端点,返回「canonical next context window」,官方明确说应原样使用,别自己乱裁。
特别细节:Codex 区分了「手动/预轮压缩」和「中途压缩」的摘要放置位置——中途压缩时会把摘要插到最后一条真实用户消息之前,而不是末尾。这不是细枝末节——摘要在 prompt 里的位置会直接影响模型续跑的稳定性。
Claude Code:与其压缩,不如少装
Claude Code 最值得学的不是「怎么压缩」,而是从架构层面减少主窗口的负担。
Subagent 隔离:阅读大文件、全仓搜索这类任务交给子 Agent 在独立上下文里完成,只有摘要和少量 metadata 返回主线程。主对话从头到尾不会被这些过程污染。
Memory 机制:学到的信息存进 auto memory,需要时再取——本质是 JIT(Just-In-Time)上下文检索,而不是提前全塞进去。默认每次只加载前 200 行或 25KB。
按需加载规则:父目录的 CLAUDE.md 启动时加载,子目录的 CLAUDE.md 在读到相关文件时才按需加载。
当然,Claude Code 也有 /compact 命令和 API 级别的 compaction(compact_20260112,默认 150,000 input tokens 触发)。但它的核心哲学很明确:与其事后压缩,不如一开始就别把不该进来的东西装进来。
Pi:最适合抄的参考实现
Pi 是一个轻量级的开源 terminal coding harness,没有 OpenCode 或 Codex 那么高知名度,但压缩机制做得非常干净透明,适合二次开发或作为学习参考。
触发公式直白:contextTokens > contextWindow - reserveTokens,默认预留 16,384 token,保留最近 20,000 token 原文。
显式状态迁移:压缩后追加一条 CompactionEntry,用 firstKeptEntryId 标记保留边界——不是偷偷改写对话,而是留下明确的锚点。下次重载只加载「摘要 + 从锚点开始的原始消息」。
工具输出特别处理:序列化摘要输入时,工具结果截断到 2,000 字符,超出部分只标记截掉了多少。
分支摘要:Pi 通过 /tree 在不同对话分支间跳转时,会把离开的分支总结到目标分支里。还提供 session_before_compact 扩展事件,允许外部扩展自定义摘要逻辑。
横向对比
| OpenCode | Codex | Claude Code | Pi | |
|---|---|---|---|---|
| 核心策略 | 先 prune 噪音,再写交接单,自动续跑 | 保近期原文,摘要老历史,双路径压缩 | 少装东西 + subagent 隔离 | 透明可插拔,显式锚点 |
| 规则外置 | instruction files | AGENTS.md | CLAUDE.md + auto memory | 配置文件 |
| 近期保留 | 最后一条真实请求 replay | 最近 ~20k token 用户消息 | subagent 保持主线程干净 | 最近 ~20k token 原文 |
| 工具输出 | prune 降级为占位文本 | 服务端裁剪 | 不进主窗口(subagent 处理) | 截断到 2,000 字符 |
| 摘要格式 | Goal/Instructions/Discoveries/Accomplished | Handoff note 含进度+约束+下步 | 结构化 summary | Goal/Constraints/Progress/Decisions/Next |
| 压缩后续跑 | 自动 replay 或合成 continue | 手动/自动均可 | /compact 后手动继续 | 自动 |
| 服务端压缩 | 无 | 有(加密 opaque item) | 有(compaction block) | 无 |
| 开源程度 | 完全开源 | 完全开源 | 有公开仓库和文档 | 完全开源 |
方向完全一致:不让 AI 背着全部历史硬跑,而是帮它重新整理工作记忆。
容易踩的坑
1. 摘要太泛。 如果压缩结果只剩「我们讨论了 bug 修复」,几乎等于没说。好的摘要必须包含具体目标、关键决定、文件路径、下一步动作。
2. 丢掉最新意图。 最容易丢的不是旧历史,而是「用户刚刚到底要你做什么」。所以 OpenCode 有 replay、Codex 保留最近用户消息、Pi 有锚点——都是在保护当前意图。
3. 稳定规则靠摘要保留。 团队规范这种东西,如果只写在聊天里,一压缩就可能走样。必须外置到独立文件。
4. 不先处理工具输出。 大量上下文不是被「思考」撑爆的,而是被工具回显撑爆的。OpenCode 和 Pi 都把工具输出当首要治理对象。
5. 同一线程压缩太多次。 多轮压缩后准确性会衰减。Codex 源码里直接建议:长线程不如开新线程。
6. 手动乱裁 API 压缩结果。 OpenAI 的 compaction item 是加密的,Anthropic 的 compaction block 之前内容会被忽略——既然官方已经做了状态迁移,就别自己二次破坏。
一句话总结
Code Agent 的上下文压缩,不是文本压缩算法,而是一套工作记忆管理机制:
长期规则外置,近期细节保热,旧历史写成交接单,大噪音优先裁掉,并行任务拆到独立上下文里。
OpenCode 把这条流水线做得最清晰;Codex 同时展示了本地摘要和服务端状态压缩两条路线;Claude Code 说与其事后压缩不如少装东西;Pi 把机制做成了最适合抄的参考实现。
名字叫「压缩」,但和 zip 无关。它真正做的事是——让 AI 知道什么该记住、什么该忘掉、什么该放在别的地方。
写于 2026 年 3 月 28 日