花 77 美元买来的教训:为什么你的「分层渐进」压缩让缓存每步都失效? 📅 2026/6/29 19:25:49 一、六大产品速览Agent 要做上下文压缩如今几乎成为所有 Agent 必做的一环。但怎么做的分歧大得离谱——六家主流产品六种完全不同的技术哲学横向对比表产品核心策略一句话概括关键特色Claude Code(Anthropic)五段流水线按成本递增排列便宜的本地操作先上LLM 摘要兜底服务端 cache_edits API缓存前缀稳定Codex CLI(OpenAI)保留近期用户消息原文其余替换为 handoff 摘要用户说的话最准确模型说的可以重写20k token 用户消息原样保留OpenCode时间戳标记隐藏 结构化摘要 回放最后一条用户消息不真删理论上可恢复数据在数据库里只是模型看不到Cline/smol 手动 Auto-Compact 自动双模式生成摘要后在同一任务内接续Focus Chain 待办列表穿越压缩存活Cursor自动摘要 提示开新对话 历史可搜索压缩后仍能回溯原始历史Dynamic Context Discovery 减少 46.9% tokenAmp(Sourcegraph)不做递归压缩用 /handoff 开新线程携带要点长对话本身就是问题换线程比压缩好线程一等公民 引用threads: map 可视化MemGPT / Letta上下文RAM历史磁盘Agent 自主换入换出操作系统级的内存调度三层架构Main Context / Recall / Archival关键洞察六家产品六种哲学。说明这件事没有显而易见的最优解每一种选择背后都是取舍。文章结构先横向看各家具体做法 → 提炼共识原则 → 介绍 MUR AI 最终落地方案MUR AI 是腾讯面向用研场景的云端多用户 Agent它比本地 CLI 工具多了好几层需要考虑的事二、第一代方案的五大痛点第一代压缩的逻辑一句话就能讲完「等上下文窗口快撑不住了把前面几十轮历史一口气扔给 LLM 生成一段摘要」。直观、好实现但体验极差痛点 1悬崖式触发症状不溢出的时候一动不动对话越来越胖、模型注意力越来越散但系统毫无反应一旦溢出就全量出手把前面几十轮一口气捏成一段摘要后果触发的那一刻质量已经塌了——模型刚因为信息过载走神紧接着又被剥夺大半上下文。这种「零或全」的模式意味着用户感知到压缩的时候往往已经是问题发生之后痛点 2全量摘要丢细节症状几十轮消息压成几百字无论 prompt 写得多好都会丢失关键信息丢失的内容包括变量名、函数签名、错误堆栈、用户的具体措辞——偏偏这些是 Agent 继续干活最需要的东西后果模型「失忆」——不是忘记整体进展而是忘记某个具体的变量名、某行关键的报错信息痛点 3Token 估算粗糙症状不少实现用text.length / 3估算 token 数实际误差中英混合场景下误差 30-50%后果以为安全实际已经溢出估算 70%实际 92%或者以为该压了实际还早根因中文字符在 BPE 里通常占 1.5-2 token英文代码有大量短 token单个字符就是一个 token混合内容的实际 token 数比字符数估算高 30-50%痛点 4不区分信息价值症状5000 行 grep 输出和 5000 token 的关键诊断被同等对待对比grep 输出裁了几乎不影响任务只是少了一些搜索结果关键诊断裁了 Agent 立刻不会干活后果在紧要信息和噪音之间系统做了一视同仁的取舍痛点 5用户内容被一刀切症状用户贴的代码代表输入意图和工具输出性质完全不同但如果一视同仁地压就会出现「用户贴的代码被压没了模型忘了要改什么」后果Agent 丢失了任务来源信息行为开始漂移总结第一代做法的根本问题它把压缩当突发事件处理而不是一种持续维护的能力三、第二代方案逐家拆解过去一年几个主流 Agent 不约而同走向「分层 渐进」但策略和哲学各不相同3.1 Claude CodeAnthropic五段流水线 结构化摘要Claude Code 把上下文管理做成了一条严格按成本递增排列的流水线共五段流水线五段阶段名称操作成本1Budget Reduction调整工具输出的截断预算零本地2Snip截短老的工具输出留「做过什么」的摘要行零本地3Microcompact对工具输出内容做局部内联压缩零本地4Context Collapse对更久远的历史做粒度更细的折叠零本地5Auto-Compact调 LLM 生成结构化摘要兜底LLM 调用前四步都是纯本地操作零 API 调用只有第五步才请求 LLM摘要本身结构化固定包含九个章节用户意图、主要请求、技术概念、文件与代码段、错误与修复、问题解决过程、用户消息、待办任务、下一步关键细节压缩时刻意保持消息序列前缀稳定让 Prompt Cache 命中率不会因为压缩而掉下来更激进的服务端路径Claude Code 内部还有两条更激进的路径思路都是「把脏活交给服务端客户端一个字节都别改」cached_microcompact把「删掉旧 tool 结果」包装成 API 层的 cache_edits 指令。客户端发出去的 prompt 原封不动服务端在已缓存的前缀上直接抠掉指定内容——字节没变缓存不失效apiMicrocompact更彻底直接调 Anthropic 的 context_management APIbeta context-management-2025-06-27Vertex / Bedrock 也支持让服务端按 input_tokens 阈值自动裁剪旧的工具调用「能让服务端做的就让服务端做本地操作永远是兜底。」3.2 Codex CLIOpenAI近期用户消息优先保护Codex 的策略相对简单直接触发时机约 95% 容量时触发操作生成一份 handoff 摘要替换掉旧历史重建后的上下文结构所有 assistant 回复和工具结果被物理删除由摘要替代近期约20k token 内的用户消息原样保留更早的用户消息则被蒸馏进摘要里——关键请求和约束会被保留下来设计哲学把压缩当作「同事间的工作交接」——进展、约束、剩余任务足够下一个模型接手继续干3.3 OpenCode可逆隐藏 回放最后一条指令OpenCode 的做法分两步很有意思第一步Prune轻量无 LLM 调用触发时机每次成功响应后自动触发操作逻辑往回遍历消息跳过最近 2 轮用户对话保护最近 40k token 的工具输出不动把更老的工具输出用时间戳标记为「已压缩」关键特性数据还在数据库里只是模型看到的变成了占位符[Old tool result content cleared]本质不真删理论上可恢复第二步Summary重量调 LLM触发时机只在 token 用量超过模型输入上限时触发操作生成一份五段式结构化摘要目标 / 指令 / 发现 / 已完成 / 相关文件然后自动回放用户最后一条消息——模型不从摘要继续而是从用户最近的指令继续一个有趣的细节「它只有一份摘要同时服务于模型和界面展示并没有做『给模型看的详细版』和『给用户看的精简版』的分离。」3.4 Cline自动 手动双模式Cline 从 v3.25 开始支持两种压缩模式/smol别名/compact手动触发生成摘要后在同一任务内接续。决策、代码变更、状态都保留在摘要里不用切新会话Auto-Compact接近上下文上限时自动触发行为和/smol一致特色功能如果开启了 Focus Chainv3.25 默认开启待办列表会穿越压缩存活下来作为进度锚点。这意味着即使历史被压缩Agent 仍然知道自己接下来要做什么。3.5 Cursor压缩 可回溯基础能力上下文超出模型窗口时自动压缩旧消息同时提示用户「开一个带摘要的新对话」2026 年新增——Dynamic Context Discovery把聊天历史变成可搜索的文件即使压缩后 Agent 也能回头检索原始细节效果数据A/B 测试里减少了46.9%的总 token 消耗已知问题社区反馈压缩后模型有时会「忘掉」刚才的编辑Cursor 团队确认这是高优 bug 在修3.6 AmpSourcegraph不压缩换线程Amp 采取了最激进的异见立场核心论点递归摘要会导致性能逐步衰减他们引用了 OpenAI 的一份内部研究所以干脆不做压缩替代方案/handoff——把当前线程的要点打包进一个新线程用户可以在交接前审查和编辑带过去的内容线程是 Amp 的一等公民可以用引用其他线程用threads: map可视化线程关系理念是「一系列有焦点的短步骤比一个逐渐退化的长对话好」妥协2026 年的 Neo CLI 更新里Amp 也加入了 90% 窗口用量时的自动上下文管理——算是对纯手动路线的一个妥协。3.7 MemGPT / Letta上下文当 RAM学术派的代表直接按操作系统的内存层次来建模层级类比容量访问方式Main ContextRAM模型上下文窗口大小始终在 prompt 里Recall Memory交换分区完整对话历史conversation_searchArchival Memory磁盘无限向量存储archival_memory_search关键区别换入换出由Agent 自己决定通过函数调用不是被动截断。Agent 主动判断什么时候需要从 Recall 或 Archival 里检索什么信息Letta是 MemGPT 的生产化框架最新版本 v0.16.72026 年 3 月GitHub 22.5k star还在积极维护代价架构复杂度高、需要外部向量存储、有检索延迟适用场景需要跨会话长期记忆但对单会话内的压缩来说有点重四、实施陷阱滑窗式 Stub 替换 每步缓存失效问题描述第二代「分层渐进」如果实施不对会掉进一个非常隐蔽的坑。想象一种实现「保留最近 N 条 tool 结果更老的替换成 stub」听起来很合理。但如果这个判断在 step-loop 里每一步都重算那么每完成一个 step新增 2 条消息就有 1 条原本被保留的旧 tool 结果滑出窗口、被替换成 stub。它的字节变了从这个位置往后的整段 prompt 前缀对 Prompt Cache 就失效了需要重新写入真实案例数据一个 4 轮、177 step、59 分钟的会话烧了$77.3其中83%$64.8全是 cache_writecache_write 的单价是 cache_read 的12.5 倍正确做法stub 决策必须单调推进——只能大跳不能滑窗一个 part 一旦被标成 stub后续所有 turn、所有 step 里都保持 stub 不变绝不因为「又老了一步」而反复触发实现方式方案 A把决策按 part ID 持久化Redis 或内存映射下次直接复用不重算方案 B交给服务端的cache_edits/context_managementAPI——客户端字节零变化缓存天然稳定Claude Code 走的路五、行业六大共识从各家方案的共同选择中文章提炼了六条几乎人人认同的原则共识 1分层渐进不一刀切定义多个水位线越接近上限手段越激进。系统永远在小幅维护避免悬崖式塌方。不再是「满到 100% 时一脚踩死」而是「到了 60% 开始修剪到了 80% 开始大修到了 95% 才叫 LLM 救命」共识 2成本严格递增便宜的先做字符串截断、placeholder 替换贵的最后做LLM 摘要。能用零成本释放的空间不花钱买。这是 Claude Code 五段流水线的核心精神也是 MUR AI 四级水位线的基础原则共识 3增量摘要优于全量摘要老做法每次重新摘要全部历史新做法保留一份活的摘要每次只把新增部分合并进去三大好处单次输入更短、更便宜、也更准模型不用处理几百条消息同一段历史不会被反复重写避免「摘要的摘要」导致语义漂移合并时模型可以主动取舍例「这个文件改过了旧描述更新一下」共识 4用真实 Token别估算LLM API 每次都返回usage.totalTokens——免费、精确、唯一可信text.length / 3只在内部排序时凑合用「先裁哪个工具输出」需要的是相对大小不是绝对值触发判断必须用真实值共识 5用户消息有特权用户的指令、问题、代码——这些是任务来源Codex做到一字不动OpenCode做到压缩后回放最后一条其他方案至少保证用户纯文本不裁共识 6保护近端无论怎么压最近几轮不能动。模型短期连贯性几乎全靠这几轮维持。常见做法是定义保护区——比如最近 8000 token 内的所有消息任何级别都不参与压缩共识 7单调边界绝不滑窗stub 决策一旦做出对应位置的字节就必须从此固定。下一 turn、下一 step 再看到同一位置永远是同一个 stub绝不因为「又老了一步」而重新触发替换。这是第 4 节惨痛教训的直接产出六、MUR AI 四级水位线方案设计理念「想象一台电脑的内存压力监视器。」四个 Tier 不是互斥而是累积——Tier 3 触发时会先做完 Tier 1 和 Tier 2 再做摘要。这意味着即使最坏情况需要送给 LLM 的内容量也已经被前两步免费砍掉了一大块。水位线总览级别触发条件操作性质LLM 成本核心动作Tier 0 60% 窗口用量什么都不做零—Tier 1 Snip60-80%预防性维护零截短老工具输出、截短用户代码块Tier 2 Prune80-95%深度释压零旧输出 → 占位符、旧 assistant → 截断Tier 3 Summarize≥ 95%LLM 摘要兜底有调用增量摘要 结构化输出Tier 0什么都不做 60%上下文宽裕模型注意力没散最好的优化是不优化。不必要的预防性操作本身也会引入干扰Tier 1Snip — 便宜的整理60-80%到了 60% 开始预防性维护。没有 LLM 调用纯字符串处理操作内容截短老的工具输出一次 grep 返回 5000 token保留前几行 工具名 「还有 X 条结果被省略」剩下丢掉截短用户消息里的代码块200 行代码保留文件名注释 前几行 总行数标注保护规则保护区内的工具输出和代码块不动某些工具享有豁免比如 Skill、Task 这种返回结构化关键信息的用户的纯文本指令永远不动只压缩 markdown 代码块「这一级成本是零但能挡住相当一部分增长。」Tier 2Prune — 更狠的释压80-95%预防性维护不够了需要更激进的手段依然零 LLM 成本Tier 1 已经截短的工具输出进一步替换成占位符[Content compacted to save space]裁掉 assistant 旧文本——保留前两句 [truncated]截断阈值整体下调能压的全压依然不变的零 LLM 成本、不动保护区、不动用户纯文本Tier 3Summarize — 兜底≥ 95%只有 Tier 1 Tier 2 都救不回来时才触发 LLM 摘要增量摘要流程找出「上次摘要之后 ~ 保护区之前」的消息作为deltaLLM 输入上次摘要 delta→ 生成合并摘要替换旧摘要删除 delta 消息保护区不动第一次触发时「上次摘要」为空相当于做一次普通摘要之后每次都是追加合并避免反复重写历史导致语义漂移结构化输出摘要 prompt 要求 LLM 输出四段结构化内容章节内容进展已完成的工作、当前状态文件涉及的文件清单、修改状态待办剩余任务、未解决的问题上下文用户偏好、已知错误、约束条件七、云端多用户额外三层设计四级水位线解决的是「上下文里压什么、压多狠」。但 MUR AI 跑在云端、服务多用户这意味着必须处理几件 CLI 工具可以无视的事用户关掉浏览器再回来压缩状态不能丢Pod 重启、流量漂移跨进程的压缩决策必须一致工具完整日志要支持事后审计和前端回取不能为了省 context 就把日志丢了sandbox 里的工具输出动辄几十 MB完整性和模型注意力不能二选一「这些靠水位线解决不了。」7.1 存储分离完整日志落盘对话里只留截断版每次工具调用bash、read、grep都可能吐出几万 token。直接塞进对话历史不行全丢掉又损失调试和审计能力实现方案engine 层调用persistTruncatedOutput把完整内容写到沙箱的_internal/truncated-outputs/{callId}.log沙箱写失败就降级到 COS 直传截断版的 metadata 带上fullLogPath模型看到的是「前几行内容 [截断] 完整日志路径」——它知道完整内容在哪但没花 token 去读前端展示时按需调 sandbox-file API 现取现读完全绕开 context 约束设计本质「把『模型的工作记忆』和『用户的审计需求』解耦了。」CLI 工具不用管这个——它们没有前端工具输出要么留在 context 里要么就没了7.2 工具差异化四个梯度梯度类别包含工具处理方式完全保护PROTECTED_TOOLSSkill、Task任何 Tier 都不动微压缩豁免被动压缩跳过Task、AskUserQuestion小幅压缩也跳过白名单可压压缩主力bash、read、grep、websearch…无状态读取类承受压缩参考建议非压缩范围其他—差异化存储预算工具单次输出落盘上限Read30KBBash50KBWebSearch15KB这组数值的考量Bash 经常吐大段构建日志Read 相对可控WebSearch 通常只要几条结果就够「一段 grep 输出和一次 Skill 调用的信息密度不在一个量级用统一阈值处理就是粗暴。」特殊保护AskUserQuestion 的输出是用户的回答删了等于把用户回话抹掉所以即使被动小幅压缩也跳过7.3 跨轮缓存ReplacementCache问题场景用户聊到第 20 轮系统在第 8 轮、第 14 轮分别做过 snip截短了某些 grep 输出。然后实例重启了或者下一个请求被路由到另一个 Pod。第 21 轮触发新一轮压缩——如果什么都不记得新进程会从头重新判断每个 part 该怎么压结果可能跟之前不一样两个严重后果Prompt Cache 全废消息序列前缀变了缓存命中率掉到零每一轮都按新 prompt 计费模型困惑同一段历史在不同轮次里「长得不一样」模型可能重新触发已经处理过的工具调用甚至开始鬼打墙解决方案把每一轮的截断决策按 part ID 存进Rediskey 形如msgOptCache:{sessionId}TTL 30 分钟下一轮无论是同进程还是另一个 Pod先查缓存已经决定过怎么压的 part 直接复用之前的结果没决定过的才走新策略从缓存读出的数据先过isValidReplacementEntry()校验损坏的直接丢弃重算效果同一个 part 在整个会话里始终长一样消息前缀稳定Prompt Cache 友好跨实例、跨重启无感有数据校验兜底不会因缓存损坏而崩溃7.4 多用户隔离最后一层最朴素但绝对不能省。所有压缩状态按(userId, sessionId)二元组隔离数据库写入强制带双条件WHERE userId ? AND sessionId ?杜绝越权读写COS 路径形如user_sessions/{userId}/sessions/{sessionId}/_internal/...天然按用户分区SSE 压缩事件按 sessionId 严格过滤订阅一个用户收不到另一个用户的压缩通知「压错给谁」比「压错什么」严重得多。这一层没有为了性能而省的空间四层云端特化设计的关系层次解决的问题CLI 工具需要吗存储分离审计需求 vs 上下文限制不需要工具差异化不同工具信息密度不同部分需要跨轮缓存实例重启后一致性不需要多用户隔离租户间安全不需要「它们是单机工具重启意味着开新会话压缩状态丢了就丢了。但云端 Agent 不一样用户合上电脑去吃饭、明天回来期待的是接着上次继续而不是『对不起请重新介绍一下需求』。」八、关键设计决策8.1 为什么阈值是 60 / 80 / 95阈值含义60%预防性维护的甜点——再低会频繁触发没意义再高模型注意力已经开始下降80%危险线离溢出还有缓冲但已经不能等95%最后防线再不调 LLM 就要爆了三个值都是可配置的常量接入远程配置后能热更新。出问题时把任意一个调到 1.0 就能禁用对应 Tier——这是生产环境必备的「保险丝」8.2 为什么先 Snip 再 Prune一个关键区别操作模型还能知道什么Snip「我之前 grep 过这个文件」——保留了工具名和部分元信息Prune只剩一个占位符[Content compacted to save space]——连执行的什么工具都看不到了Snip 是轻量记号Prune 是彻底擦除——能用前者解决的就别上后者8.3 为什么要增量摘要打个比方做法类比全量摘要每周把过去三个月的工作重新写一份周报增量摘要维护一份持续更新的项目状态每周只追加和修订变化的部分增量摘要的隐藏好处同一个文件被多次提及时最新状态覆盖旧状态全量摘要里这个文件可能被描述好几次而且互相矛盾8.4 为什么用真实 Token作者团队一开始也用text.length / 3估算直到踩了坑「估算值显示 70%实际 LLM 返回的 usage 已经 92%——再来一轮直接溢出。」触发判断必须用 API 真实值内部排序「先裁哪个工具输出」用估算就够了需要的是相对大小不是绝对值30% 误差不影响排序8.5 为什么保留 compactionProtected 标记预留扩展。某些 Part 可能很重要但看起来很普通——用户上传的设计文档、关键错误堆栈、标记为「记住这个」的指令。给它们打标记任何 Tier 都跳过「能力先建好需要时直接用。」九、红线清单「压缩系统最大的事故不是压不够而是压错东西。」以下内容在任何 Tier 下都绝对不能动红线内容原因保护区内的所有消息模型短期连贯性的命脉用户消息的纯文本部分用户意图就是任务来源PROTECTED_TOOLS 的输出Skill / Task高度结构化的关键信息或会话级状态MICRO_COMPACTION_EXEMPT 的工具输出Task / AskUserQuestion保住对话流的关键节点带 compactionProtected 标记的 Part业务侧明确指定的「必须保留」严格执行原则这些规则在每一级压缩里严格执行。不存在「为了救场破例」的情况「如果保护区都救不下来那就让上下文真的爆出来——比错误压缩造成模型行为漂移要好。」十、可观测性设计「压缩发生在背后没有可观测性的话调起来全靠玄学。」SSE 事件包含的信息指标含义当前触发的 tier0 / 1 / 2 / 3当时的 token 使用率来自 LLM 真实 usageSnip 截了几个 partTier 1 的处理量Prune 替换了几个 partTier 2 的处理量Summarize 是否真的调用了Tier 3 是否触发预估节省了多少 token压缩效果量化命中 ReplacementCache 的 part 数跨轮一致性收益前端展示前端渲染一个压缩面板让用户看到类似「这一轮触发了 Tier 1节省了 3000 token其中 6 个 part 直接复用了上一轮决策。」持续优化这些数据接入 trace 系统后可以基于真实生产数据调水位线不再拍脑袋。比如如果 Tier 1 触发过于频繁但每次节省太少调高阈值如果 Tier 3 触发频率超出预期说明前两级不够激进如果 ReplacementCache 命中率低缩短 TTL 或检查缓存策略十一、刻意未做的事为了避免过度工程有几条路团队暂时没走但保留了可能性1. 主动 cache-aware 调度Claude Code 的做法是什么压缩时主动调整顺序来最大化 Prompt Cache 命中现状目前用 ReplacementCache 实现了被动一致性为什么不做还没做按 cache 边界主动选择压谁触发条件等 cache 命中率成为瓶颈再上2. 可逆隐藏OpenCode 的做法是什么用时间戳标记而非真删现状目前_internal落盘已经满足审计需求为什么不做等用户需要「回滚到某次压缩之前」时再上3. 回放最后一条用户消息OpenCode 的做法是什么摘要后重新追加用户最近指令为什么不做Tier 3 触发频率本来就低优先级不高4. 用户消息一字不动Codex 的做法是什么用户消息完全不动包括代码块现状已经不动用户纯文本但还会截断用户贴的代码块为什么不做是否进一步走极端看后续生产数据5. 分层长期记忆MemGPT / Letta / Mem0是什么跨会话记忆系统为什么不做跨会话记忆是另一个产品方向不是这一轮要解决的问题十二、核心哲学Context 压缩的目标从来不是省 Token省钱是顺带的。它要解决的问题是保护模型的注意力Context Rot 现象200K 的上下文窗口听起来很大但研究反复表明上下文塞到 70% 以上模型的中段失忆和指令漂移就会明显恶化它不是真的「忘了」——是注意力被稀释、信号被噪声淹没。这就是Context Rot压缩系统 信号工程师一个合格的压缩系统应该把无关紧要的工具输出降为占位符让模型不用扫过它们把老的 assistant 文本裁短让最近的对话不被淹没把历史合并成结构化摘要让模型用事实思考而不是用文本回忆终极问题「这一轮对话里模型应该把注意力放在什么上面」2026 年做 Agent 工程这个问题绕不开