Skip to content

一文看懂 AI Agent 的"上下文压缩":从 0 到 1 学透

本文基于 mervynyang《Claude Code、Codex、OpenCode 们到底怎么压上下文?分析完 6 家,我们做了第 7 个》一文重新整理而来,目标是把原文里偏工程化、偏术语化的部分,用"小白也能懂"的方式讲清楚 —— 配生活化类比 + 8 张原理图。


写在前面:这篇文章帮你解决什么?

如果你接触过 ChatGPT、Cursor、Claude Code、Codex CLI 等任何"AI 编程助手 / Agent",可能都遇到过下面这些尴尬时刻:

  • 聊到第 30 轮,AI 突然"忘"了你最初让它做什么
  • 让它继续修 bug,它却跑去重新分析整个项目
  • 同一段代码反复让它读,它的回答前后矛盾
  • 用着用着系统弹窗:"对话过长,请开启新会话"

这些不是模型变笨了,而是 "上下文(Context)" 这个东西在背后出了问题。本篇文章会带你彻底搞懂:

  1. 上下文到底是什么,为什么必须压缩
  2. 业内 6 家头部产品(Claude Code、Codex、OpenCode、Cline、Cursor、Amp、MemGPT)的设计哲学差异
  3. 一套"四级水位线"压缩方案的工程实现细节
  4. 云端多用户场景下额外要做的 4 层特化设计

读完之后,你会明白:好的 Agent 工程师,本质上是"模型注意力的工程师"


一、最基础的概念:什么是"上下文(Context)"?

1.1 大模型其实没有"记忆"

很多人以为 ChatGPT 能记住和你的全部对话,是因为它"记忆力很好"。

真相残酷得多:大模型本身没有任何长期记忆。它每一次回答,都是把"过去发生的所有事情"重新打包成一段长字符串,再喂给模型一遍。

打个比方:你和一个有失忆症的朋友聊天 —— 每次他来之前,你都得给他一份"我们过去聊过什么"的备忘录。这份备忘录,就是上下文

1.2 上下文里都装了些什么?

一份典型的上下文长这样(按从上到下的顺序):

层级内容例子
System Prompt(系统指令)"你是编程助手,遵守 XX 规范..."
工具定义(Tools Schema)告诉模型有哪些工具可用:read / bash / grep
历史对话 + 工具调用结果前面所有轮次的 user / assistant / tool 消息
用户最新一条消息"再帮我改一下这段代码"
大模型生成的最新回复"好的,我来分析..."

下一轮调用时,⑤ 会变成"历史"和 ③ 合并 —— 越聊越长,永远在膨胀。

图1:什么是 AI Agent 的上下文

1.3 上下文有上限:200K tokens 听起来很大,其实很危险

主流大模型的上下文窗口大约是 128K ~ 200K tokens(约 10~15 万汉字)。

听起来"够用一辈子"对吧?但研究反复证明 —— 当上下文塞到 70%+ 之后,模型的表现就开始急剧下降。这个现象有个专门的名字叫 Context Rot(上下文腐烂)

  • 中段失忆:模型记得开头、记得最后几条,但中间发生过什么忘了
  • 指令漂移:忘记你最初要它做什么,开始按自己的"直觉"乱跑
  • 答非所问:你问 A,它去答 B,因为关键信号被噪声淹没

💡 生活类比:你的办公桌空着的时候,找一份文件 1 秒钟。等桌面堆满 100 份文件之后,找同一份文件可能要翻 5 分钟,而且你大概率会拿错。模型也一样,不是"记不住",是"找不到"

1.4 所以"上下文压缩"到底要解决什么?

一句话:在塞满之前主动整理桌面,让模型始终能集中注意力

⚠️ 注意:压缩的目标不是"省钱"(虽然顺带能省),而是保护模型的注意力。这个区别非常重要,文末第 12 节会再回到这点。


二、第一代压缩方案的"翻车现场"——5 大痛点

要理解后面六家产品为什么各种花式设计,先得看看最朴素的方案有哪些坑。

2.1 朴素策略 V1:撑不住才动手

逻辑朴素到一句话能讲完:

text
每轮结束后看一眼:上下文是不是要爆了?
├─ 没爆 → 什么都不做
└─ 爆了 → 把前面所有消息一次性压成一段摘要,替换掉

听起来很合理对吧? 但用过的人都知道,它问题大得离谱。下面是 5 个典型痛点:

图2:第一代压缩的 5 大翻车现场

2.2 痛点 1:悬崖式触发 —— 等地板湿透才拿盆

问题:不溢出的时候系统一动不动,对话越来越胖、模型注意力越来越涣散,但系统毫无反应。一旦溢出,它全量出手 —— 一次性把前面几十轮全部捏成一段摘要。

为什么糟糕:触发的那一刻,质量已经塌了。模型刚刚因为信息过载开始"走神",紧接着又被剥夺了大半上下文。

💡 生活类比:就像水龙头一直流,盆子满了才想起拿盆 —— 等你动手时,地板已经湿透。

2.3 痛点 2:全量摘要丢失大量细节

问题:把前面几十轮对话一次性压成一段几百字的摘要,无论 prompt 写得多好,都会丢失大量细节:变量名、函数签名、错误堆栈、用户的具体措辞……这些恰恰是 Agent 接下来要继续工作时最需要的东西。

💡 生活类比:把过去三个月的工作日报,压成 200 字的"工作总结" —— 你给老板看可以,但你自己接下来要工作时,关键数字、人名、客户偏好都没了,等于从零开始。

2.4 痛点 3:Token 估算粗糙 —— 用目测称体重

问题:很多实现用 text.length / 3 或类似的字符数除法来估算 token。中英混合场景下,误差可以达到 30~50% —— 它以为还很安全,实际已经溢出;它以为该压缩了,实际还远没到。

为什么这么不准

  • 中文字符在 BPE 分词里通常占 1.5~2 token / 字
  • 英文代码大量短 token
  • 混合内容的实际 token 数可能比字符估算高 30~50%

💡 生活类比:用"目测"称体重 —— 你以为 100 斤,体重秤说 130 斤。如果医生靠这个开血压药,会出大事。

2.5 痛点 4:不区分信息价值

问题:一个"大就裁掉、小就留下"的策略,会把 5000 行 grep 输出和一段 5000 token 的关键诊断同等对待 —— 但显然,前者裁了不影响任务,后者裁了 Agent 立刻就不会干活了。

💡 生活类比:大扫除时把厚厚的一摞书扔了,把薄薄一张房产证留下来 —— 标准是"厚薄",不是"重要不重要"。

2.6 痛点 5:用户内容也被一刀切

问题:用户贴进来的大段代码,按理说和工具输出不一样 —— 它代表用户的"输入意图"。但如果系统把它和工具输出一视同仁,就会出现"用户贴的代码被压没了,模型反而忘了我要它改什么"的滑稽场面。

💡 生活类比:客户给你的合同被秘书当废纸扔了 —— 你不知道客户到底要谈什么,整个项目就废了。

2.7 总结:根本病灶

这一代做法的本质问题是:它把"压缩"当成一个突发事件,而不是一种持续维护的能力


三、百家争鸣:六家头部产品的"设计哲学"

意识到上面的问题之后,过去一年里几家主流 Agent 系统不约而同地走向了"分层 + 渐进"的方向,但每家味道很不一样。

图3:六家头部 Agent 的设计哲学对比

3.1 Claude Code(Anthropic 官方):精益生产流水线

把上下文管理变成一条严格按成本排序的流水线

步骤做什么成本
1. Budget Reduction调整内部预算分配≈ 0
2. Snip把老的工具输出截短,留下"做过什么"的摘要行CPU 微秒级
3. Microcompact局部小压缩CPU 毫秒级
4. Context Collapse把更久远的历史折叠CPU 毫秒级
5. Auto-Compact兜底 —— 调 LLM 生成结构化摘要$$$ + 秒级延迟

便宜的本地操作先做,真正昂贵的 LLM 调用是最后一步。摘要本身也是结构化的 —— 固定包含九个章节:用户意图、主要请求、技术概念、文件、错误、问题解决、用户消息、待办任务、下一步。

它还有一个聪明的细节:Cache 友好。压缩时尽量保持消息序列前缀稳定,让 Prompt Cache 命中率别因为压缩反而掉下来。

💡 设计哲学:就像汽车工厂的流水线,每道工序按"成本递增"排好 —— 能用扳手解决就别上焊枪,能焊接就别整体重铸。

3.2 Codex CLI(OpenAI):用户消息神圣不可侵犯

策略简单得多,但有一个鲜明的原则:

  • 触发后做一次"handoff 摘要",包含进展、约束、剩余任务
  • 所有用户消息原样保留,一字不改
  • assistant 回复和工具结果被物理删除,由摘要替代

💡 设计哲学:"用户说过的话最准确,模型自己说过的可以重写。" 类似销售保留客户原话录音,自己的话术可以反复打磨。

3.3 OpenCode:留底党 —— 可逆隐藏 + 回放最后一句

两步设计很有意思:

  1. Prune:用时间戳"标记隐藏",而不是真的删除 —— 数据还在,理论上可恢复
  2. Summary:生成五段式摘要,并且自动回放用户最后一条消息 —— 这样模型不会从摘要文本继续,而是从你最近的指令继续

它还把"给模型看的详细摘要"和"给用户看的两句话摘要"分开做。

💡 设计哲学:邮箱"已删除"文件夹的逻辑 —— 不是真删,30 天后才清,万一要后悔还能找回来。

3.4 Cline / Cursor:半自动 —— 决策权还给用户

更克制:

  • Cline 的 /smol 命令生成摘要后,把当前会话的上下文清空,用摘要作为新起点 —— 你不需要切到新会话
  • Cursor 在你接近上限时主动提示你开新对话,并自动生成本次会话的摘要作为新会话的起点

它们其实是把压缩"半自动化" —— 决策权部分交给用户

💡 设计哲学:浏览器"标签页太多了"的提示框 —— 系统不擅自帮你关,让你自己决定哪些不要。

3.5 Amp(Sourcegraph):禁欲系 —— 完全不自动压

走另一个极端:不自动压缩。理念是"长对话本身就是问题",因此鼓励用户主动开短对话、用 handoff 把要点带到新线程。

💡 设计哲学:与其修复堵塞的下水道,不如别往里面倒油 —— 治标不如治本。

3.6 MemGPT:学术派 —— 把上下文当 OS 的 RAM

把上下文窗口直接类比成操作系统:

  • Main Context = RAM:放最近、最热的消息
  • External Context = 硬盘:放历史归档和召回记忆
  • 系统决定什么时候把数据"换入换出"

这套思路启发了 Letta、Mem0 等"长期记忆"产品。卖点是 80~90% 的 token 节省 —— 但代价是架构复杂度、需要外部存储、检索延迟。

💡 设计哲学:电脑的虚拟内存机制 —— RAM 满了就换页到 SSD,需要时再换回来。

3.7 小结:为什么没有"标准答案"

同一件事,六家头部产品给出了六种完全不同的设计哲学。这意味着这个问题没有"显而易见的最优解" —— 每一种选择都是一组取舍

  • 是优先保护用户输入,还是优先保护模型连贯性?
  • 是自动激进压缩,还是把决策权还给用户?
  • 是维护单会话注意力,还是建立跨会话长期记忆?

不同的产品场景(CLI vs 云端、单用户 vs 多用户、短任务 vs 长协作)会让这些权衡指向不同方向。


四、6 条业内共识("通用配方")

虽然六家做法迥异,但有 6 条原则几乎是共识

共识 1:分层渐进,而不是一刀切

不再"溢出才动手",而是定义多个水位线,越接近上限手段越激进。这样系统永远在"小幅维护",避免悬崖式塌方。

共识 2:成本严格递增

便宜的(字符串截断、placeholder 替换)先做,贵的(调 LLM 摘要)最后做。能用 0 成本释放的空间,绝不去花钱买

图8:成本严格递增的流水线

共识 3:增量摘要 > 全量摘要

老的做法是"每次重新摘要全部历史"。新的做法是保留一份"活的摘要",每次只把新增部分合并进去。这有几个好处:

  • 单次摘要的输入更短,更便宜也更准
  • 同一段历史不会被反复"重写" —— 避免"摘要的摘要的摘要"导致语义漂移
  • 模型可以在合并时主动取舍:"新信息说这个文件改过了,旧摘要里的描述我就更新一下"

共识 4:用真实 token,不要估算

LLM API 的 response 里都会返回 usage.totalTokens —— 直接用它text.length / 3 这种估算只在内部排序("先裁哪个工具输出")时凑合用一下就行,触发判断必须用真实值

共识 5:用户消息有"特权"

用户的指令、问题、提供的代码 —— 这些是 Agent 的任务来源,压缩时应当享有最高保护:

  • Codex 直接做到"用户消息一字不动"
  • OpenCode 做到"压缩后回放最后一条用户消息"
  • 其他方案至少保证用户的纯文本不被裁掉

共识 6:保护"近端"

无论怎么压,最近的几轮永远不能动。模型短期内的连贯性几乎完全靠这几轮维持。常见做法是定义一个"保护区" —— 比如最近 8000 token 内的所有消息,在任何 tier 都不参与压缩。


五、终极方案:四级水位线模型(核心干货)

把上面这些原则落地,本文作者最终选择了一套 四级水位线模型。理解它最简单的方式是想象一台电脑的"内存压力监视器":

图4:四级水位线模型

5.1 触发判断公式

text
ratio = 当前 prompt token / 上下文上限

ratio < 0.60         → Tier 0:什么都不做
0.60 ≤ ratio < 0.80  → Tier 1:Snip      (截短老工具输出 + 用户代码块)
0.80 ≤ ratio < 0.95  → Tier 2:Prune     (替换为 placeholder + 裁掉 assistant 旧文本)
ratio ≥ 0.95         → Tier 3:Summarize (增量 LLM 摘要)

⚠️ 关键性质:四个 Tier 的关系不是互斥而是累积 —— Tier 3 触发时,会先做完 Tier 1 和 Tier 2 的所有工作再做摘要。这意味着即使在最坏情况下,需要送给 LLM 摘要的内容量也已经被前两步免费砍下来一大块。

5.2 Tier 0:什么都不做(< 60%)

上下文还很宽裕,模型注意力也还没散,最好的优化是不优化

💡 生活类比:办公桌还很整洁,没必要放下手头工作去搞大扫除。

5.3 Tier 1:Snip — 整理桌面(60% ~ 80%)

到了 60%,开始预防性维护。这一级没有 LLM 调用,纯粹是字符串处理

操作例子
截短老的工具输出一次 grep 返回了 5000 token 的匹配?保留前几行 + 工具名 + 一句"还有 X 条结果被省略",剩下的丢掉
截短用户消息里的代码块用户贴的 200 行代码,保留文件名注释 + 前几行 + 总行数标注

注意几个细节:

  • 保护区不动 —— 最近 N token 内的工具输出和代码块原样保留
  • 某些工具享有豁免权 —— 比如 Skill、Task 这种返回结构化关键信息的工具
  • 用户的纯文本指令永远不动 —— 只压缩 markdown 代码块

这一级的 ROI 极高:成本是 0,但能挡住相当一部分增长

💡 生活类比:把翻过的旧报纸折好叠到角落,桌面立刻清爽 —— 但如果要找,还在那里。

5.4 Tier 2:Prune — 更激进的释压(80% ~ 95%)

到了 80%,预防性维护不够了,需要更狠一点:

操作例子
工具输出替换为占位符Tier 1 已截短的工具输出 → 替换成 [Content compacted to save space]
裁掉 assistant 旧文本保留前两句 + [truncated]
整体阈值下调能压的全压

依然是 0 LLM 成本,依然不动保护区,依然不动用户纯文本。

💡 生活类比:把旧报纸直接装进纸箱搬到储藏室 —— 桌上只留个标签写着"2024 年 3 月报纸合集",不再占地方。

5.5 Tier 3:Summarize — 最后的兜底(≥ 95%)

只有当 Tier 1 + Tier 2 都救不回来时,才会触发 LLM 摘要。而且我们做的是增量摘要

text
1. 找出"上次摘要之后 ~ 保护区之前"的消息作为 delta
2. LLM 输入:上次摘要 + delta → 生成合并摘要
3. 替换旧摘要 part,删除 delta 消息
4. 保护区一律不动

第一次触发时,"上次摘要"为空,相当于普通摘要;之后每次都是追加合并。这避免了"反复重写历史"导致的语义漂移。

摘要 prompt 仍然采用结构化输出,但简化为四段

  • 进展
  • 文件
  • 待办
  • 上下文(用户偏好、错误、约束)

💡 生活类比:请秘书写工作交接备忘录 —— 贵但靠谱,只在迫不得已时才用。


六、为什么是"增量摘要"而不是"全量摘要"?

这是第二代方案 vs 第一代方案最关键的一个分水岭,单独拎出来讲。

图5:增量摘要 vs 全量摘要

6.1 一个生活化的对照

想象一下你给同事写交接文档:

做法全量摘要增量摘要
每周做什么把过去三个月的工作重新写一份周报维护一份持续更新的项目状态,只追加和修订有变化的部分
每次输入量越来越长(要重读三个月内容)始终很短(上次状态 + 本周新事)
准确性同一个事件被反复"翻译",慢慢失真旧描述不被修改,语义稳定
新旧冲突同一个文件可能在不同周报里被描述成不同样子自然覆盖:"这个文件改了,旧描述自动更新"
成本每周都贵每周都便宜

哪种更准、更便宜、信息保真度更高?显然是后者。

6.2 增量摘要的隐藏好处

当同一个文件被多次提及时,最新状态会覆盖旧状态 —— 而全量摘要里这个文件可能被描述好几次互相矛盾。

💡 类比:维基百科的页面 —— 一个事件随时间发展,老编辑的内容会被新编辑覆盖,最终呈现的是当前最准确的版本,而不是各时期版本的简单拼接。

6.3 实现细节:三个步骤

text
Step 1: 找 delta —— 上次摘要 part 之后、保护区之前的所有消息
Step 2: LLM 输入 = "上次摘要" + "delta" → 输出"合并后的新摘要"
Step 3: 用新摘要 part 替换旧摘要 part,并删除被合并掉的 delta 消息

保护区全程不动


七、云端多用户 Agent —— 在水位线之上再叠 4 层

前面四级水位线讲的是**"上下文里压什么、压多狠"。但如果你做的是云端多用户 Agent**(比如本文作者的 MUR AI),相比 Claude Code、Codex、Cline 这类跑在用户本机的 CLI 工具,还有几件事是 CLI 可以"装看不见"、云端必须替用户兜住的:

  • 用户关掉浏览器再回来,压缩状态不能丢
  • 实例横向扩展、Pod 重启、流量漂移,跨进程的压缩决策必须保持一致
  • 工具的完整日志要支持事后审计、前端回取、问题排查 —— 不能"为了护住 context 就把日志丢了"
  • 一个 sandbox 里跑用户代码,工具输出动辄几十 MB —— 完整性和模型注意力不能二选一

所以在四级水位线之上,需要再叠加 4 层云端特化设计

图6:云端 Agent 三层特化设计

7.1 存储分离:完整日志落盘 + 对话里只留截断版

问题:每一次工具调用 —— bash、read、grep —— 都可能吐出几万 token 的输出。直接塞进对话历史显然不行;但全丢掉又损失了调试和审计能力。

做法:把"模型工作记忆"和"用户审计需求"两件事解耦

  1. 工具被截断时,engine 层调用 persistTruncatedOutput 把完整内容写到沙箱里的 _internal/truncated-outputs/{callId}.log;如果沙箱写失败,自动降级到 COS 直传
  2. 截断版的 metadata 带上 fullLogPath,模型在对话里看到的是"前几行内容 + [截断] + 完整日志路径" —— 它知道完整内容在哪,但没有花 token 去读
  3. 前端展示时按需调 sandbox-file API 现取现读,把完整日志展开给用户看 —— 这条路完全绕开了 context 约束

💡 生活类比:开会议时只发"纪要 + 录音文件链接",不读全部录音。模型保住注意力,用户保住完整性,没人需要妥协。

7.2 工具差异化:不是所有工具都该被一视同仁

把工具划成 4 个梯度

梯度工具待遇
完全保护Skill, Task任何 Tier 都不动。Skill 关联到知识库绑定,Task 是会话级元任务 —— 它们的输出有状态意义,删了 Agent 就糊涂了
微压缩豁免Task, AskUserQuestion在被动的小幅压缩里也跳过 —— AskUserQuestion 的输出是用户的回答,删了等于把用户回话抹掉
白名单内可压bash, read, grep, websearch这些是"无状态读取类"工具,是被压缩的主力
差异化存储预算单次输出落盘上限按工具区分Read 30KB, Bash 50KB, WebSearch 15KB

这反映了不同工具的输出特性:Bash 经常吐大段构建/测试日志,Read 大小相对可控,WebSearch 通常只要几条结果就够。

💡 生活类比:医院按科室分诊,急诊和体检不能一个标准。同样是"采血",捐血和糖尿病检查的处理方式完全不同。

7.3 跨轮缓存(ReplacementCache):让"上一轮怎么压"在重启后还算数

这一层是云端特有的

设想这样的场景:用户和 Agent 聊到第 20 轮,系统在第 8 轮、第 14 轮分别做过 snip。然后实例重启了,或者下一个请求被路由到了另一个 Pod。第 21 轮触发新一轮压缩 —— 如果什么都不做,新进程会从头重新判断每个 part 该怎么压,结果可能是和之前不一样的截断长度、不一样的占位符文本。

这会带来两个问题:

  1. Prompt Cache 全废:消息序列前缀变了,缓存命中率掉到 0,每一轮都得按新 prompt 重新计费
  2. 模型懵掉:同一段历史在不同轮次里"长得不一样",模型可能重新触发已经处理过的工具调用,甚至开始"鬼打墙"

解法叫 ReplacementCache —— 把每一轮的截断决策按 part ID 存进 Redis:

text
key = msgOptCache:{sessionId}
TTL = 30 分钟
value = 各 part 的截断后内容

下一轮 —— 无论是同进程还是另一个 Pod —— 先查这个缓存,凡是已经决定过怎么压的 part 直接复用之前的结果,没决定过的才走新一轮策略。

效果:

  • 同一个 part 在整个会话里始终长成同一个样子 → 消息前缀稳定,Prompt Cache 友好
  • 跨实例、跨重启无感 → Pod 漂移、灰度发布、流量切换都不会让压缩"失忆"
  • 失败安全 → 从缓存读出的数据先过 isValidReplacementEntry() 校验,损坏的直接丢弃重算

💡 生活类比:会议纪要要存档,下次会前先翻昨天的记录而不是重写一份 —— 既省时间,又保证前后一致。

7.4 多用户隔离:每条压缩状态都带"身份"

最后一层最朴素,但绝对不能省。所有压缩相关的状态都按 (userId, sessionId) 二元组隔离:

sql
-- 数据库写入强制带双条件
WHERE userId = ? AND sessionId = ?
text
-- COS 快照路径形如
user_sessions/{userId}/sessions/{sessionId}/_internal/...
天然按用户分区,连扫描都不可能扫到别人的
text
-- SSE 压缩事件按 sessionId 严格过滤订阅
一个用户绝对收不到另一个用户的压缩通知

💡 关键原则:云端多租户场景下,"压错给谁"比"压错什么"严重得多 —— 这一层没有任何"为了性能而省"的空间。

💡 生活类比:医院档案系统不能混淆病人 —— 哪怕检查项目都对,把 A 的 CT 报告塞给 B,就是医疗事故。


八、5 类内容 —— 任何 Tier 都不能动

把红线列清楚很重要 —— 压缩系统最大的事故不是"压不够",而是"压错东西"

图7:5 类内容任何 Tier 都不能动

#内容原因生活类比
1保护区内的所有消息(最近 N token)模型短期连贯性的命脉你看小说时刚翻过的那几页 — 撕掉就读不下去了
2用户消息的纯文本部分用户的意图就是 Agent 的任务来源客户给你的需求文档 — 内部资料怎么改都行,但客户原话不能动
3PROTECTED_TOOLS 的输出(Skill / Task)输出本身就是高度结构化的关键信息客户签字的合同 — 不管多占地方都得保留原件
4MICRO_COMPACTION_EXEMPT 工具(Task / AskUserQuestion)微压缩里也跳过,保住对话流的关联会议中别人对你提问的回答 — 没记清就再问一遍,不能假装没说过
5compactionProtected 标记的 Part业务侧明确指定的"必须保留"贴了红色"勿动"标签的文件 — 不论谁来打扫都要绕开

这些规则在每一级压缩里都被严格执行 —— 不存在"为了救场不得不破例"的情况。如果保护区都救不下来,那就让上下文真的爆出来,比错误压缩造成模型行为漂移要好


九、关键设计的"为什么"

设计过程中有几个看似细节但实际很关键的选择,单独讲讲。

9.1 为什么阈值是 60 / 80 / 95?

阈值含义
60%预防性维护的甜点 —— 再低的话频繁触发无意义,再高的话模型注意力已经开始下降
80%"危险线" —— 离溢出还有缓冲,但已经不能等
95%"拒绝退却" —— 再不调 LLM 就要爆了

这三个值都是可配置的常量,未来接入远程配置后还能热更新。出问题时把任意一个调到 1.0 就能禁用对应 Tier。

9.2 为什么先 Snip 再 Prune?

因为 Snip 保留更多元信息

  • 一个被 Snip 的工具结果还能让模型看到"哦我之前 grep 过这个文件"
  • 被 Prune 替换成占位符后,模型连这个都看不到了

所以 Snip 是"轻量记号",Prune 是"彻底擦除" —— 能用 Snip 解决的就别上 Prune。

9.3 为什么用真实 token 而不是估算?

真实案例:估算值显示 70%,实际 LLM 返回 usage 里已经 92% 了 —— 再来一轮直接溢出。

原因:中文字符在 BPE 里通常占 1.5~2 token,而英文代码大量短 token,混合内容的实际 token 数可能比字符估算高 30~50%。

LLM API 每次都会返回精确的 usage.totalTokens —— 这是免费的、精确的、唯一可信的数字,没理由不用。

内部排序 —— 比如"先裁哪个工具输出" —— 继续用估算就够了。这里需要的是相对大小,不是绝对值,估算的 30% 误差不影响排序结果。

9.4 为什么保留"compactionProtected"标记?

预留扩展。某些 Part 可能很重要但看起来很普通 —— 比如:

  • 用户上传的设计文档
  • 关键错误堆栈
  • 明确标记为"记住这个"的指令

给它们打一个标记,任何 Tier 都跳过。这个能力先建好,业务上有需要时直接用。


十、可观测性:让压缩"被看见"

压缩是一个发生在背后的复杂过程,如果没有可观测性,调起来全靠玄学

在 SSE 事件里加详细信息:

  • 当前触发的 tier(0 / 1 / 2 / 3)
  • 当时的 token 使用率(来自 LLM 真实 usage
  • Snip 截了几个 part
  • Prune 替换了几个 part
  • Summarize 是否真的调用了
  • 预估节省了多少 token
  • 命中 ReplacementCache 的 part 数(衡量跨轮一致性收益)

前端可以据此渲染一个"压缩面板":让用户看到"这一轮触发了 Tier 1,节省了 3000 token,其中 6 个 part 直接复用了上一轮决策"。

同时这些数据接入 trace 系统 —— 后续可以基于真实生产数据调整水位线,不再靠拍脑袋。

💡 生活类比:装修房子如果不装电表水表,你永远不知道哪个空调在偷电。可观测性 = 给 Agent 装上电表水表。


十一、本质思考:Context 压缩到底为了什么?

聊了这么多技术细节,最后想跳出来说一点感性的东西。

11.1 真正的目标不是省钱

Context 压缩的目标,从来都不是"省 token"

省钱是顺带的。它真正要解决的问题是:保护模型的注意力

200K 上下文窗口听起来很大,但研究反复表明:当上下文塞到 70%+,模型的"中段失忆"和"指令漂移"就会显著恶化 —— 它不是真的"忘了",而是注意力被稀释、信号被噪声淹没

这就是所谓的 Context Rot

11.2 一个合格的压缩系统 = 主动的信号工程师

所以一个合格的压缩系统,应该是一个主动的信号工程师

  • 把无关紧要的工具输出降为占位符,是为了让模型不用扫过它们
  • 把老的 assistant 文本裁短,是为了让最近的对话不被淹没
  • 把历史合并成结构化摘要,是为了让模型用"事实"思考而不是用"文本回忆"

我们花这么多力气做分级、做增量、做保护区,本质上都是在回答一个问题:

"在这一轮对话里,模型应该把注意力放在什么上面?"

而这,恰恰是所有 Agent 工程师 2026 年都在反复琢磨的核心命题。

11.3 给小白的最后总结

如果你只能记住三句话,请记住:

  1. 上下文 = AI 的工作记忆。每次调用都要重新打包过去所有内容,没有压缩就一定会爆
  2. 压缩是一种持续维护,不是突发事件。分级 + 渐进 + 增量 + 保护用户内容,这是业内共识。
  3. 压缩的目的是保护模型的注意力,省钱只是顺带。一个塞了 200K 但全是噪声的窗口,远不如一个塞了 50K 但全是关键信息的窗口好用。

附录:参考资料

  • Anthropic — Effective context engineering for AI agents
  • Anthropic — Compaction (Claude API)
  • Justin3go — Shedding Heavy Memories: Context Compaction in Codex, Claude Code, and OpenCode
  • badlogic — Context Compaction Research: Claude Code, Codex CLI, OpenCode, Amp
  • Letta — Agent Memory: How to Build Agents that Learn and Remember
  • Mem0 — LLM Chat History Summarization Guide
  • Microsoft — Compaction (Agent Framework)
  • MemGPT — Engineering Semantic Memory through Adaptive Retention
  • Cline / Cursor / Amp — 各家官方文档
  • 原文:mervynyang《Claude Code、Codex、OpenCode 们到底怎么压上下文?分析完 6 家,我们做了第 7 个》(KM 平台,2026-05-04)