别压缩压缩过的东西:CliffCompaction 如何让编码 Agent 在百万 token 中不迷路
你让一个 AI 编码 agent 去优化一个 CUDA kernel。它开始读代码、跑测试、看报错、改代码、再跑测试——每一步都在往上下文窗口里塞东西。50 步之后,上下文涨到 20 万 token。100 步之后,40 万。200 步之后,模型的最大上下文窗口已经被撑爆了——98% 的运行在到达步数预算之前就因为上…
别压缩压缩过的东西:CliffCompaction 如何让编码 Agent 在百万 token 中不迷路
你让一个 AI 编码 agent 去优化一个 CUDA kernel。它开始读代码、跑测试、看报错、改代码、再跑测试——每一步都在往上下文窗口里塞东西。50 步之后,上下文涨到 20 万 token。100 步之后,40 万。200 步之后,模型的最大上下文窗口已经被撑爆了——98% 的运行在到达步数预算之前就因为上下文溢出而提前终止。
这不是一个假想的场景。这是 KernelBench Level 3 上的真实数据:没有上下文管理的 agent,中位数只能跑到 99 步就被迫停工,最终 kernel 加速比只有 1.30×。
问题的核心很简单:agent 干活需要记忆,但记忆会膨胀,膨胀到溢出就得压缩,而压缩会丢信息。
传话游戏与上下文漂移
最直觉的压缩方案是"总结"——让一个 LLM 把旧对话读一遍,写个摘要,替换掉原文。Claude Code 的 auto-compaction 就是这么做的。但这里有个致命问题,论文称之为"summary of summaries"问题。
想象你在玩传话游戏:A 告诉 B 一段话,B 转述给 C,C 再转述给 D。每经过一次转述,细节就丢失一点,错误就累积一点。经过五六轮,最后一个人听到的故事和最初的故事可能已经面目全非。
LLM 总结也是一样。第一次总结可能丢了某个关键变量名,第二次总结可能把"函数 A 调用函数 B"压缩成了"函数 A 做了些事",第三次总结可能把一个重要的错误信息完全省略了。更糟糕的是,模型看到总结后会以为自己拥有完整信息,不会主动去回查原始文档。论文把这叫做"低压缩精度"(low compaction precision)——不是丢了多少信息的问题,而是丢的信息你不知道丢了。
CliffCompaction 的第一作者 Trang Nguyen 和 Tim Dettmers(对,就是做 QLoRA 的那个 Dettmers)在卡内基梅隆大学和博世 AI 中心合作完成了这项工作。他们提出了一个反直觉的方案:别压缩压缩过的东西。
悬崖式压缩:只截断,不重写
CliffCompaction 的算法简单得让人怀疑这是不是一篇论文而不是一个 shell 脚本:
1. 上下文涨到阈值时触发压缩(比如 128K token) 2. 压缩时只做两件事:把超过 500 字符的 tool result 直接丢掉,把 tool call 缩减为签名(函数名+参数),把 agent 的 thinking 截断到 300 字符 3. 系统提示和任务描述完整保留,最近 K 轮对话原封不动 4. 关键规则:每次压缩时,把上一次的压缩结果完全丢弃,只压缩从上次压缩到现在的"活会话"
第 4 条是灵魂。传统的总结方案是把旧总结折叠进新总结,形成嵌套结构——总结的总结的总结。CliffCompaction 不这么做。每次压缩只处理原始的活会话内容,上一次的压缩块 C_{t-1} 被直接扔掉,新的压缩块 C_t 只从 L_t(上次压缩后的新对话)生成。
这就像你每次做笔记都只记今天新学的东西,昨天的笔记撕掉。但今天的笔记是在昨天笔记的指导下做的——所以昨天的信息"渗透"进了今天的笔记里,虽然昨天的纸已经不在了。
论文把这个机制叫做"残差传播"(residual propagation):
没有任何信息被显式地向前携带,但每一轮的 agent 行为都受到之前压缩块的影响,而新的压缩块又基于这些受影响的行为了生成。信息像墨水一样渗透进纸纤维——纸被抽走了,但墨迹还在。
精度 vs 召回:一个被忽视的权衡
论文提出了一个我觉得非常漂亮的框架:压缩精度(precision)和压缩召回(recall)的权衡。
- 压缩精度:保留的信息是否忠实于原文?有没有失真?
- 压缩召回:经过多轮压缩后,还能检索到多少历史信息?
CliffCompaction 反过来:精度高——保留的都是原文的逐字截断,没有 LLM 重写,没有失真。但召回率低——两轮压缩之后,最早的信息就完全不在上下文里了。
这个框架的洞察在于:对于编码 agent 来说,精度比召回更重要。 因为编码任务有执行反馈——如果 agent 忘了某个文件的内容,它可以重新读一遍。但如果它记住了错误的信息(比如总结把一个 bug 的根因描述错了),它会基于错误信息做决策,而且不会主动去验证。
这让我想起数据库设计里的 CAP 定理——一致性、可用性、分区容错,三者不可兼得。CliffCompaction 选择了精度而牺牲召回,就像 CAP 里选择 CP 而非 AP。选择本身不稀奇,稀奇的是明确说出这个权衡的存在,并且用实验证明在编码场景下精度是正确的选择。
悬崖形状与 KV-Cache 的经济学
"CliffCompaction"这个名字来自上下文长度的变化曲线:上下文自然增长,到达阈值时突然跌落,再增长,再跌落——形成一系列悬崖。
这个形状不是审美选择,而是为了 KV-Cache 的经济学。
在 LLM 推理的后端,每次请求都要把历史上下文重新加载到 KV-Cache 里。对于长上下文的 agent,cached input token 的成本是最大的开销——因为每次生成都要重新读一遍所有历史 token。API 定价上,uncached input token(需要重新 prefill)比 cached token 贵 5-6 倍。
如果你像总结方案那样频繁修改上下文,每次修改都会使整个 KV-Cache 失效,需要全部重新 prefill。这就是为什么"省钱"的总结方案在实际部署中可能反而更贵。
CliffCompaction 的悬崖形状意味着:在两次压缩之间,上下文完全不变,KV-Cache 保持有效。 只有在压缩的那一刻才需要重新 prefill,而且压缩后的上下文比原来小得多,re-prefill 的成本也低。整个设计的核心是:让缓存失效的次数最少,让每次失效时的 re-prefill 最便宜。
数据说话
Terminal-Bench 2.0
Kimi K2.6 + CliffCompaction(16K 阈值)达到 61.42% 的通过率,超过不压缩的完整上下文基线(59.16%)。同时成本从 $0.40 降到 $0.19——省了一半钱,性能还涨了 2 个百分点。
在 Claude Code 上用 GLM 5.3 Flash 测试,CliffCompaction 在 45K 平均峰值上下文下达到 76.69%,超过 Claude Code 原生 auto-compaction 的 70.97% 和默认 200K 配置的 73.03%。
测试时扩展的经济性
这是最让人兴奋的结果。测试时扩展(test-time scaling)——跑多次 rollout 然后选最好的——在理论上能提升性能,但成本高得离谱。不压缩的 Kimi K2.6 跑 3 次,花 $91.65,性能提升 4.8 个百分点。而 GPT 5.3 Codex 跑 1 次只要 $64.63,性能还高 5.5 个百分点——朴素扩展完全不划算。
CliffCompaction 翻转了这个不等式。压缩后的 Kimi K2.6 跑 3 次,只花 $58.01,性能提升 10.5 个百分点——比一次 GPT 5.3 Codex 还便宜,性能却匹配 Opus 4.7(69.7% vs 69.4%)。
论文还提出了一个叫 Soft Group Verification (SGV) 的选择器:用 LightGBM 对每条 rollout 的特征(步数、工具调用数、代码行重叠度等)打分,选最优解。有趣的是,给 Kimi 用的选择器是用 GLM 的 rollout 训练的,反之亦然——跨模型迁移有效,说明选择信号是任务本身的特征,不是模型的特征。
KernelBench:持续学习的新纪录
在 KernelBench Level 3 上,单条轨迹可以超过 100 万 token——是 SWE-bench 中位轨迹长度的 15-70 倍。
不压缩的 Kimi K2.7 在 256K 上下文窗口下只能跑 99 步就溢出,kernel 加速比 1.30×。CliffCompaction(128K 阈值)让它跑到 200 步,加速比 2.23×。跑到 400 步,加速比 3.58×——超过专门为 kernel 优化设计的 AdaExplore(1.78×)和 CUDA-Agent(1.80×)。
更值得注意的是覆盖面:400 步时,92% 的 kernel 达到 1.2× 以上,86% 达到 2× 以上。而 AdaExplore 只有 36% 和 22%。一个通用的压缩技术打败了专门设计的搜索算法——因为它能让 agent 持续工作更久。
工程洞察
1. Token 都花在哪了? 论文做了一个很有价值的 token 分析:在 Terminal-Bench 上,tool result 占 56%,tool call 占 28%,两者合计 84%。所以 CliffCompaction 主要压缩这两类——长 tool result 直接丢(可以重新调用),tool call 缩减为签名(可以重新执行)。agent 的 thinking 只占小部分,截断到 300 字符即可。
2. 为什么不压缩 thinking? 因为 thinking 是 agent 自己的推理过程,压缩它会丢失推理链。而 tool result 是外部信息,丢了可以重新获取。这个区分很重要:可恢复的信息可以激进压缩,不可恢复的信息必须保留。
3. Scaffold 无关性。 CliffCompaction 被实现为一个 API proxy,对 Claude Code 这种闭源 agent 也能用。它不需要知道 agent 的工具 schema,只做通用的"长内容截断+签名保留"。这意味着它是一个drop-in 方案——不需要改 agent 代码,挂在 API 代理层就行。
4. 8K 是压力测试,不是工作点。 在 8K 阈值下性能明显下降,但下降是渐进的而非断崖式的。论文建议 16K-32K 是实际工作的甜蜜点。
我的思考
这篇论文让我想到一个更深层的问题:在 AI agent 的设计中,什么信息应该被显式存储,什么信息应该被隐式传播?
CliffCompaction 的回答是:显式存储只保留最近的高精度信息,远期信息靠 agent 行为的"残差"隐式传播。 这和人类记忆的机制惊人地相似——你记不住上周三午饭吃了什么(显式记忆丢失),但那顿饭的营养已经变成了你身体的一部分(隐式传播)。
另一个值得关注的点是:简单方法打败复杂方法。CliffCompaction 没有训练任何模型,没有调用任何辅助 LLM,没有维护任何外部记忆库。它就是几条规则:超长内容丢掉,工具调用留签名,不压缩压缩过的东西。但它打败了需要训练的 AdaExplore、需要 LLM 调用的总结方案、需要外部基础设施的 RAG 方案。
这让我想起 Occam's Razor——如无必要,勿增实体。在 agent 工程领域,我们可能过度工程化了。与其构建复杂的记忆系统,不如先问一个简单的问题:agent 真正需要记住什么? CliffCompaction 的答案是:只需要记住最近的、原汁原味的、可恢复的上下文。其他的,让它通过行为渗透。
最后,这项工作对测试时扩展的影响可能比对上下文管理本身更大。测试时扩展一直被批评为"学术玩具"——成本太高,性价比太低。CliffCompaction 把朴素扩展的 3× 成本降到 1.9×,让"三个小模型 rollout 打败一个大模型"从理论可能变成工程现实。如果这个趋势持续,我们可能正在见证一个范式转移:不是更大的模型,而是更多次的小模型 rollout,才是 scaling 的下一个维度。
论文:CliffCompaction: Cost-Efficient Compaction for Long-Horizon Coding Agents 作者:Trang Nguyen, Eulrang Cho, Bingqing Chen, Tim Dettmers (CMU + Bosch Center for AI) arXiv:2609.26779 代码:github.com/nguyenvuthientrang/cliffcompaction