Code Agent 的上下文压缩:不是 zip,而是工作记忆管理

1 阅读10分钟

拆解四款 Code Agent 的上下文压缩机制——OpenCode、Codex、Claude Code、Pi 各自怎么做,以及它们殊途同归的设计共识。

先搞清楚:这里的「压缩」不是 zip

如果把 AI 编程助手(Code Agent)想成一个帮你写代码的同事,它面前只有一块有限大小的白板。

你让它查文件、跑命令、读日志、改代码——这些全会写到白板上。写满了,新的就放不下了。轻一点答非所问,重一点直接报错停工。

这时候就需要「上下文压缩」。但这里的压缩不是 zip 那种无损打包,而是把旧内容重新整理成一份更短、更适合继续干活的版本——本质是有损的任务交接


白板上到底装了什么?

很多人以为上下文只有「聊天记录」。远不止如此:

  • 用户和 AI 的对话
  • 读取过的文件内容
  • 命令行输出和执行日志
  • 工具调用的参数和返回结果
  • 图片、PDF 等附件
  • 项目规则文件(AGENTS.mdCLAUDE.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 扩展事件,允许外部扩展自定义摘要逻辑。


横向对比

OpenCodeCodexClaude CodePi
核心策略先 prune 噪音,再写交接单,自动续跑保近期原文,摘要老历史,双路径压缩少装东西 + subagent 隔离透明可插拔,显式锚点
规则外置instruction filesAGENTS.mdCLAUDE.md + auto memory配置文件
近期保留最后一条真实请求 replay最近 ~20k token 用户消息subagent 保持主线程干净最近 ~20k token 原文
工具输出prune 降级为占位文本服务端裁剪不进主窗口(subagent 处理)截断到 2,000 字符
摘要格式Goal/Instructions/Discoveries/AccomplishedHandoff note 含进度+约束+下步结构化 summaryGoal/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 日