1. 概述
Agent 的每一次模型请求都需要一份当前上下文。它由系统规则、工具定义、用户目标、模型动作、工具结果、任务状态和最近一次输入共同组成。任务继续推进时,新的消息和工具结果不断追加,原始轨迹会逐渐变长。
上下文压缩(context compaction)是将已有的历史上下文信息通过一系列压缩策略进行摘要或二次处理为后续的决策信息的过程。
压缩处理的是“发给模型的视图”,不等于直接删除任务的全部历史。我们通常同时保留三份内容:
- 事件日志:用于恢复和审计
- 原始产物:用于追溯已有的历史产物
- 上一轮的压缩结果:用于下一轮模型请求
这样可以在控制输入长度的同时保留可复查性。
同一个任务可以采用多种处理方式:短结果直接保留,重复结果做局部摘要,低价值工具输出只保留结论,探索型工作交给独立 Agent,历史达到压力阈值后再生成整体检查点。
2. 上下文压缩的必要性
上下文变长会同时影响可发送性、费用、时延和决策质量。长任务中一次文件读取、网页检索或日志分析都可能返回数万 Token ;如果每轮都把完整结果留在消息列表中,模型窗口会很快被中间材料占满。
2.1 客观条件约束
模型的上下文窗口 \(W\) 同时容纳输入和输出。Agent 还需要为本轮回答、工具参数、重试和压缩摘要预留输出空间,因此真正可用于历史消息的预算远小于模型标称窗口。可以用下面的形式表达主动压缩阈值:
其中 :
- \(T\) 是触发主动压缩的输入预算
- \(r\) 是使用比例
- \(O\) 是本轮输出预留
- \(B\) 是安全余量
不同模型和服务商的计费方式不同,实际实现还需要考虑图片、工具定义、推理 Token 和协议包装产生的额外消耗。
| 约束 | 变长来源 | 超出后的表现 | 压缩的作用 |
|---|---|---|---|
| 上下文窗口 | 历史消息、工具结果、图片、工具定义 | 请求被拒绝或只能截断输入 | 释放可用输入空间 |
| 输入费用 | 每一轮重新发送的历史 Token | 多轮任务费用线性甚至更快增长 | 减少重复发送的历史 |
| 首词时延 | 长前缀的预填充计算 | 请求开始等待时间变长 | 缩短需要重新计算的部分 |
| 有效输出预算 | 输入占用输出窗口 | 模型回答变短,工具参数可能不完整 | 为当前动作保留空间 |
| 失败恢复 | 过长请求、网络重试、模型切换 | 任务停在中间状态 | 留出可恢复的检查点 |
例如,一次 Agent 任务连续调用搜索工具。每次返回的网页正文约有 20,000 个 Token ,五轮后原始结果已经超过 100,000 个 Token 。把每个网页都完整带入下一轮,既会消耗大量输入费用,也会让后续请求没有空间生成新的工具调用。压缩可以把每份结果保存为原始文件、来源和短摘要,把模型输入中的内容降到与当前问题相关的事实集合。
压缩阈值不应只看历史消息长度。系统还需要把本轮用户输入、预计工具输出、模型最大输出和重试次数纳入预算。对于拥有多个模型路由的系统,阈值应按实际路由模型计算,不能用一个固定数字覆盖所有上下文窗口。
2.2 注意力机制
上下文压缩需要先理解注意力的计算对象。对一个注意力头,输入隐藏状态记为矩阵 \(X\),模型先计算:
其中:
- \(Q\)(Query)表示当前位置正在寻找什么
- \(K\)(Key)表示每个历史 Token 可以被什么问题匹配
- \(V\)(Value)表示匹配后取回的内容
- \(M\) 是因果掩码:当前位置 \(i\) 只能看到 \(j \le i\) 的 Token,未来位置的分数设为负无穷
对第 \(i\) 个位置而言,某个历史 Token \(j\) 得到的权重为:
输出 \(y_i\) 是所有可见 \(v_j\) 的加权和。
模型会在多层、多头中重复这个过程,逐步把分散在上下文里的信息组合到当前表示中。
虽然这个机制提供了强大的按内容检索能力,但也带来几个与长上下文直接相关的数学限制,也就是我们常说的注意力机制的数学缺陷:
第一,计算量随可见位置成平方增长。 一次全量预填充需要生成 \(n \times n\) 的注意力分数矩阵,标准实现的计算量近似为 \(O(n^2 d)\),其中 \(n\) 是输入 Token 数,\(d\) 是每个头的维度;显式保存分数矩阵时,注意力中间结果的空间也接近 \(O(n^2)\)。FlashAttention 等实现可以减少中间矩阵的存储量,但每个新位置仍需要读取它之前的大量 \(K\)、\(V\)。因此,输入从 32K 增长到 128K 时,预填充工作量不会只增加四倍,注意力部分的两两交互会增加到约十六倍量级。
第二,softmax 让有限权重在候选项之间分配。 如果多个历史 Token 与当前查询的分数接近,\(\alpha_{ij}\) 会接近均匀分布;当可见位置从 \(m\) 增加到 \(4m\),同等分数下单个事实得到的权重大致从 \(1/m\) 降到 \(1/(4m)\)。真实模型会用多头、不同层和更尖锐的分数集中权重,长上下文也不必然导致均匀分配,但重复日志、网页菜单、格式标记和多份相同结论会增加竞争项,让关键事实需要更强的匹配信号才能脱颖而出。
第三,注意力输出是加权混合。 \(Y = AV\) 产生的是多个 \(V\) 向量的组合。某个事实如果只出现一次,且没有明确的字段名、来源或任务关系,它的表示可能与周围内容混合;重复出现的结论更容易获得稳定信号,互相矛盾的结果则会让不同层的表示承担额外辨别工作。压缩摘要需要把事实、状态、来源和关系写成结构化字段,帮助后续查询产生更清晰的 \(Q\)-\(K\) 匹配。
第四,位置编码带来距离和外推约束。 以 RoPE 为例,位置 i 的查询和位置 j 的键经过旋转后,点积会携带相对位置 i-j 的相位信息。模型在训练窗口内学到的相位范围有限,输入接近或超过训练长度时,远距离位置的区分可能变得困难;不同模型的长上下文扩展方法会改善这种现象,仍然需要在实际长度上验证。消息顺序、重复插入和压缩边界会改变位置序列,因此压缩后的内容需要保持清晰的时间顺序与阶段边界。
第五,注意力只处理 Token ,不直接处理事实。 一个路径、日期或 JSON 字段可能被拆成多个 Token ;模型需要先从这些 Token 恢复实体,再把实体与任务目标关联。把长网页原文塞进上下文,增加的是 Token 和匹配机会,事实之间的关系仍然需要模型从文本中重建。压缩保留“结论—来源—状态”的紧凑表达,可以减少从 Token 恢复结构的工作。
因此,注意力机制的数学限制可以归纳为三类成本:
- 两两交互带来的计算增长
- softmax 权重竞争造成的信号稀释
- 位置和表示混合造成的远距离检索难度
如果对压缩过程进行设计,直接使用类似滑动窗口类的硬性压缩策略,就会产生几类常见问题:
| 现象 | 具体表现 | 压缩时需要保留的内容 |
|---|---|---|
| 关键事实被稀释 | 模型重新搜索已经确认过的资料 | 事实、数值、时间和来源 |
| 关系被拆散 | 工具结果仍在,但看不出它回答了哪个问题 | 调用标识、问题、结果之间的对应关系 |
| 决策理由消失 | 模型重复讨论已经否决的方案 | 决策、理由、适用条件 |
| 失败路径被遗忘 | 模型再次调用已经失败的接口 | 失败原因、重试次数、替代方案 |
| 任务状态过时 | 模型误以为文件、权限或环境仍处于旧状态 | 当前阶段、已完成项、待办和限制 |
因此,简单的滑动窗口并不适合长任务。它可以缩短输入,却可能把“为什么不再尝试某条路径”一起移除,导致 Agent 重复执行相同工具调用。
即,上下文压缩策略的核心在于:需要能够有效降低候选 Token 数量,提升关键事实的结构化程度,并保留足够的位置与关系信息。压缩应当围绕任务语义组织内容,至少保留事实与关系:事实说明发生了什么,关系说明该事实服务于哪个目标、来自哪次操作等。
2.3 压缩与 KV Cache互补
KV Cache 和上下文压缩处理的是两个层次的问题。模型在一次推理过程中会缓存已经计算出的键和值,避免生成每个新 Token 时从头计算前缀;服务商也可能跨请求复用相同的提示前缀,这通常称为 Prompt Cache。两者都依赖前缀稳定,但作用范围不同。
上下文压缩会替换历史中的一段内容,因此从替换位置开始的缓存通常需要重新计算。把压缩放在系统规则和工具定义之后,可以保留更早的稳定前缀;把动态状态追加到末尾,可以减少前缀变化对缓存的影响。
| 操作 | 对历史的影响 | 对前缀缓存的影响 | 适用位置 |
|---|---|---|---|
| 追加新的用户消息或工具结果 | 只增加末尾内容 | 已有前缀可以继续复用 | 每轮正常推进 |
| 修改系统提示词 | 改变最早的输入 | 后续缓存大范围失效 | 版本切换或规则变更 |
| 删除中间工具结果 | 从删除点开始改变序列 | 删除点之后需要重新计算,缓存编辑接口可减少重算 | 上下文压力上升 |
| 用摘要替换旧历史 | 生成新的检查点 | 替换点之后重新建立缓存 | 长任务阶段切换 |
| 子 Agent 隔离探索 | 主 Agent 只接收结论 | 主 Agent 前缀保持不变 | 大量检索、代码浏览 |
这说明压缩不应在每一轮都执行。过于频繁的摘要会增加额外模型请求,并反复重建缓存;完全不压缩又会让输入、费用和注意力压力持续上升。常见做法是先处理单条大结果,再在接近阈值时批量处理旧历史,同时保留稳定的系统前缀和工具目录。
3. 压缩策略
3.1 常用的分层压缩策略
成熟的 Agent Harness 通常按信息粒度和处理成本组织多层策略。低成本操作先减少明显冗余,只有仍然超出预算时才启动需要额外模型请求的整体摘要。
| 层级 | 处理对象 | 典型操作 | 是否需要额外模型请求 | 结果 |
|---|---|---|---|---|
| 1 | 单条超大工具结果 | 截断展示,原文写入文件 | 否 | 模型看到预览和产物路径 |
| 2 | 单条结果中的重复信息 | 提取结论、参数、来源和错误 | 可选 | 结果变短,调用关系保留 |
| 3 | 低价值旧消息 | 删除或折叠整条消息、代码块和重复日志 | 否 | 释放空间,保留边界标记 |
| 4 | 探索型中间过程 | 委托给子 Agent,只回传结论 | 需要子 Agent | 主上下文不承载大段中间材料 |
| 5 | 已完成的历史阶段 | 生成结构化会话摘要并保留最近尾部 | 是 | 形成可恢复检查点 |
| 6 | 压缩请求自身过长 | 从最早的消息组开始缩短后重试 | 通常需要 | 让压缩请求有机会完成 |
分层策略如何协同
分层压缩的关键是为每一条内容同时计算三个属性:
- 当前体积
- 再次使用的可能性
- 重新获取的成本
搜索结果通常体积大、再次使用的可能性中等、重新获取成本可能很高;重复的目录输出体积大、事实密度低、重新获取成本很低;用户给出的约束体积小、影响范围大、重新获取成本极高。这三类内容应当进入不同的处理层。
可以把选择过程写成下面的决策顺序:
- 先检查结果是否超过单条输出上限。超过时保存完整产物,消息只留下预览、统计信息和产物路径。
- 再检查结果是否已经有可靠摘要。已有摘要可以复用,避免每轮重复调用摘要模型;摘要必须带来源和适用范围。
- 再检查消息是否属于同一次 API round。选择压缩范围时保持用户消息、assistant 工具调用和对应的
tool_result完整配对。 - 再根据任务阶段决定摘要重点。搜索阶段偏向保留候选来源和未确认事实,分析阶段偏向保留证据链和决策理由,执行阶段偏向保留文件变更、测试结果和失败限制。
- 最后才选择整体摘要。整体摘要需要生成新的边界事件,并记录它覆盖了哪些原始消息和产物。
压缩范围通常有两个边界:前边界保护稳定前缀和系统规则,后边界保护最近一段可直接用于下一步的消息。中间区域可以被折叠或摘要。后边界不能只按消息条数计算,因为一条网页结果可能占用几万 Token ;通常情况下,我们会按 Token 预算选择近期尾部,并向前移动到工具调用配对完整的位置。
每次压缩应产生一个可观测记录:
{
"trigger": "token_threshold",
"selected_range": {"start": "event_018", "end": "event_067"},
"retained_tail_tokens": 12000,
"input_tokens_before": 118400,
"input_tokens_after": 42300,
"summary_request_tokens": 9400,
"artifacts": ["artifacts/search/result_07.json"],
"status": "completed"
}这类记录能把“压缩成功”拆成可检查的事实:范围是否正确、释放了多少空间、摘要请求花费多少、原始材料是否仍可访问。只记录压缩后的文本,无法定位错误来自摘要内容还是范围选择。
分层处理需要明确输入和输出边界。工具结果修剪器只改变模型看到的内容,原始输出仍保存在外部文件;会话摘要只负责建立新的上下文入口,任务状态、权限和文件快照继续由独立存储维护;子 Agent 的中间轨迹留在自己的会话里,主 Agent 接收带来源的结果。
一个可恢复的检查点可以包含以下字段:
{
"objective": "完成产品研讨会资料、PRD 和评审安排",
"completed": [
"确认三个竞品的核心能力",
"保存来源页面和原始检索结果"
],
"decisions": [
"PRD 先覆盖会议记录、摘要和待办提取"
],
"constraints": [
"每条外部事实必须带来源",
"未公开的价格和识别准确率仍需验证"
],
"pending": [
"补充用户访谈问题",
"安排评审会议"
],
"artifacts": [
"artifacts/search/competitor-a.json"
]
}其中 objective、completed、decisions、constraints 和 pending 面向下一轮决策,artifacts 负责把摘要与原始材料连接起来。摘要应当说明事实的来源和状态,不能只留下“已经处理”这类无法复核的结论。
3.2 Claude Code压缩策略
五层手段的由来
长任务触发压缩的原因有三种:单条工具结果过大,长期闲置的工具结果继续占据前缀,整体历史接近窗口上限。三种原因的处理成本差别很大,清空一条旧结果只需要改写一段消息,整体摘要需要额外一次模型调用。Claude Code 按成本把手段排成五层,请求前先做最轻的处理,仍然超出预算时才升级到下一层。
| 层 | 处理对象 | 动作 | 调用模型 | 触发条件 |
|---|---|---|---|---|
| 1 | 服务端保存的旧工具结果与思考内容 | 请求参数要求服务端清理,本地消息不变 | 否 | 输入达到 180,000 Token |
| 2 | 模型标记为无用的历史消息 | 按标识删除整条消息,保留分界消息 | 否 | 会话达到 30 条消息后模型判断 |
| 3 | 单条较大的工具结果 | 保留最近几条,其余替换为占位文本或交给服务端删除 | 否 | 可压缩结果超过 10 个,或闲置 60 分钟 |
| 4 | 已经提炼出的会话记忆 | 用记忆文件替换旧历史,跳过摘要调用 | 否 | 自动阈值到达且记忆可用 |
| 5 | 整段旧历史 | 生成结构化摘要,保留近期消息并重注附件 | 是 | 达到自动压缩阈值 |
前四层可以反复执行,成本接近零。第五层结束后需要重新建立缓存前缀,只在前面几层释放的空间不够时才使用。
阈值如何计算
有效窗口先把留给输出的空间扣掉,模型最大输出超过 20,000 Token 时按 20,000 预留。缓冲按窗口大小分三档:有效窗口小于 400K 时为 13,000,400K 到 800K 为 30,000,800K 以上为 50,000。自动压缩阈值等于有效窗口减去缓冲。
以 200K 窗口为例,输出预留 20,000,有效窗口为 180,000,缓冲取 13,000,阈值落在 167,000,约为标称窗口的 83.5%。
请求前还会估算本轮可能新增的内容。单轮工具结果按 15,000 Token 估计,叠加输出预留后得到预测阈值;同一例子中预测阈值降到 145,000,压缩在真正触顶之前就开始。警告状态与错误状态各自保留 20,000 Token 的空间;关闭自动压缩时,阻塞上限只剩有效窗口减去 3,000,避免连手动命令本身都发不出去。
触发条件与各层动作
服务端清理在输入达到 180,000 Token 时把旧工具结果与思考内容一并清掉,清理后目标保留 40,000 Token,也就是至少释放 140,000 Token。默认覆盖终端、文件匹配、内容搜索、文件读取、网页抓取和网页搜索的结果;编辑类工具的调用参数由开关控制,默认关闭;思考内容默认全部保留,闲置超过 1 小时时只保留最后一轮的思考内容。
按消息删除在会话达到 30 条消息时开始提示模型,模型主动标记或用户执行强制命令后生效。删除以整条消息为单位,被删内容的位置留下一条分界消息,后续轮次从分界消息之后取历史。
旧结果清理处理可压缩工具产生的单条结果。可压缩结果超过 10 个时,保留最近 5 个,更早的通过缓存编辑交给服务端移除;缓存已经不适用时,闲置超过 60 分钟就直接把旧结果内容替换为占位文本,这条时间触发路径默认关闭。两条路径都保留工具调用本身,模型仍能看出某条结果是哪次动作返回的。
会话记忆替代在自动阈值到达、记忆文件已经存在且两个开关同时打开时启用。保留区至少取 10,000 Token 和 5 条含文本的消息,上限 40,000 Token;切分位置向前移动,直到工具调用与其结果、同一条流式消息里的思考块保持完整。这条路径省掉一次摘要调用,摘要质量取决于之前提炼记忆的质量,压缩后如果仍然超过阈值,就退回全量摘要。
全量摘要把剩余历史交给摘要模型。摘要子代理被排除在自动压缩之外,避免摘要过程再次触发摘要;折叠类子代理同样跳过。
手动压缩可以按选中的消息向前压缩一半,也可以向后压缩一半。向后压缩保留前缀缓存,向前压缩会让缓存前缀失效。
一次全量压缩的执行过程
- 统计压缩前的用量,执行压缩前钩子,界面进入压缩状态。
- 组装摘要请求,把待压缩历史、摘要指令与必要的稳定上下文交给摘要子代理;请求前去掉图片与文档,替换为占位标记,也去掉随后会被重新注入的技能清单。
- 摘要子代理不开启思考,拒绝所有工具调用,输出上限 20,000 Token。
- 摘要请求自身超限时,按轮次从最早的一组开始删除,最多重试 3 次。
- 摘要完成后清理文件读取状态与已加载的嵌套记忆,并发生成文件、计划与技能附件。
- 按“分界消息、摘要消息、保留消息、附件、启动钩子结果”的顺序重建消息列表。
- 同一轮查询继续执行,后续轮次从最后一条分界消息之后取历史。
这条顺序保留了三个入口:摘要提供任务背景,保留消息提供最近动作,附件提供文件状态与计划内容。只生成摘要会让模型失去最近的工具结果,只保留最近消息又会让早期决策消失。
压缩后恢复的状态
最近读取的文件最多恢复 5 个,单个文件超过 5,000 Token 时只保留路径引用,总预算 50,000 Token;保留消息里已经出现过的读取结果不重复注入。
已调用的技能每个最多恢复 5,000 Token,总量最多 25,000 Token,超出时丢弃最久未使用的技能。
计划文件以完整内容注入,仍处于计划模式时附加计划模式说明。
运行中或已完成但结果尚未取回的后台子任务以任务状态附件注入,带上描述、状态、进度摘要与输出文件路径。
延迟加载的工具、子代理列表与 MCP 指令按当前状态重新生成变化片段。
项目根目录的规则文件、自动记忆与 Git 状态在压缩后重新注入,路径规则与嵌套规则等模型重新访问相关文件时再加载。
摘要消息附带完整转录的路径,模型需要精确细节时可以重新读取;保留消息的链关系一并写入,恢复会话时按链关系重接。
摘要结构与失败处理
摘要输出固定九个部分:主要请求与意图、关键技术概念、文件与代码段、错误与修复、问题解决过程、全部用户消息、待办任务、当前工作、可选的下一步。
写作顺序是先写分析草稿再输出正文,最终只保留正文,草稿被删除。指令要求只输出文本、不得调用工具,安全相关约束逐字保留,只把真正的用户消息算作用户请求,禁止把模型自己生成的引用当成用户指令。当前工作写到最细,下一步要求引用最近的原文对话,避免任务解释漂移。自动压缩时追加一句“直接从上次中断处继续,不要确认摘要,不要复述”。
失败处理分几类。摘要请求超限时按轮次删除并重试 3 次,删除到只剩一组仍失败就报告无法摘要。流式响应为空时按开关重试 1 次或 2 次,仍为空则报告压缩中断。连续失败 3 次后熔断,本会话不再自动尝试;历史记录中曾有会话连续失败上千次,熔断避免了无意义的重复请求。
服务商返回超限错误时走被动恢复,在一次请求链里只执行一次;恢复过程按轮次从最早一组删除,删除后如果首条消息变成孤立的模型消息或工具结果,就插入一条用户角色标记,让消息序列重新满足首条角色的要求。错误响应带缺口数值时按缺口累计删除,没有可解析缺口时删除约五分之一的历史。被动恢复失败后直接暴露原错误并结束,不再进入后续钩子,避免报错、钩子拦截、重试、再报错的循环。
缓存方面,保持系统提示词与工具定义稳定,工具结果以追加消息进入轨迹,压缩只替换较晚的历史。支持缓存编辑时,删除旧工具结果通过服务端完成,本地消息结构不变;缓存已经失效时直接清空结果比再生成一次摘要更省一次模型调用。
整体摘要本身需要一次额外请求,只有释放的空间足够时才值得执行。压缩前后的用量、触发原因与失败次数都会写入事件,便于比较节省量与任务成功率。
3.3 Codex 压缩策略
压缩即开启新窗口
Codex 把一次压缩处理成开启新窗口。实现分三层:历史层保存用户输入、模型响应与工具输出;环境状态层保存项目指令、工作目录、权限、模型信息、工具目录与协作模式;窗口计数器记录自动压缩次数与窗口标识。压缩时只替换历史层,环境状态层按当前会话重新生成,计数器继续递增。
这样分工之后,摘要只承担“过去发生过什么”,当前环境由各自的来源重新提供。历史里被省略的读取过程不会影响项目规则与可用工具,重新打开会话或进入新窗口时也能恢复到相同环境。
三种实现的分工
| 实现 | 生成方式 | 保留内容 | 适用条件 |
|---|---|---|---|
| 重置窗口 | 不生成摘要,不调用模型 | 只有重新生成的环境状态 | 只需要一个新窗口即可继续 |
| 本地摘要 | 当前模型生成交接材料 | 最近 20,000 Token 用户消息与摘要 | 本地模型或服务商不支持原生压缩 |
| 服务端压缩 | 服务端返回压缩项 | 64,000 Token 内的混合项与压缩项 | 服务商能够生成原生压缩 |
三种实现共享同一套生命周期:执行压缩前钩子、记录压缩事件、安装新窗口、执行压缩后钩子,并把触发原因与实现方式写入分析数据。差异只在摘要由谁生成、历史由谁保存。
触发时机
自动压缩上限取配置值与窗口 90% 的较小者,硬上限取窗口的 95%。以 200K 窗口为例,自动上限为 180,000,硬上限为 190,000,两层之间留出一段缓冲。
触发点分布在轮次的三个阶段:轮次开始前发现用量已经达到上限就压缩;轮次进行中模型还要继续调用工具或还有待处理输入时压缩并继续同一轮;轮次结束后按配置的百分比压缩,默认关闭。切换上下文窗口更小的模型时,用旧模型对旧窗口做一次补偿压缩,压缩结果随新窗口一起使用。
切换模型或服务商后重新计算预算,不沿用旧路由的阈值。
本地压缩的执行过程
- 把完整历史与压缩指令交给当前模型,复用当前路由与流式连接;已经执行的工具调用附加在提示中,避免摘要模型把已完成动作当成待执行动作。
- 摘要分四段:当前进度与关键决策;重要上下文、约束与用户偏好;剩余工作与下一步;继续所需的关键数据或引用。
- 从最新用户消息向前累计,最多保留 20,000 Token,更早的用户消息丢弃。
- 摘要放在历史最后,前面加固定前缀,说明另一个模型已经完成过工作,请在此基础上继续并避免重复。
- 重新生成窗口前缀与环境状态,插到最后一条真实用户消息之前。
- 替换历史并重新计量,下一轮请求直接使用新历史,原始工具调用仍可从持久记录中追溯。
- 压缩结束时给出一条警告:长线程与多次压缩会降低模型准确度,条件允许时新开线程。
服务端压缩的执行过程
服务端返回一个压缩项。客户端从原请求输入中筛出可以保留的内容:用户消息、钩子提示、开发者消息、单条不超过 10,000 Token 的模型消息、带标记的工具输出。保留项从最新向前累计,总预算 64,000 Token,工具输出保持完整,放不下时停止向更早扩展;图片在预算允许时保留。
拼接顺序是保留项、压缩项、重新生成的初始上下文。必须恰好收到一个压缩项,数量不符时直接失败。
环境状态的重建
环境状态包含项目说明文件、工作目录与环境信息、权限设置、当前模型、工具目录、协作模式、插件与连接器贡献的片段。
首次构造新窗口时注入完整片段,后续步骤按上一次快照只追加发生变化的片段。被压缩的历史不会覆盖这些快照,权限或工作目录变化时,变化出现在新的状态片段里,模型不会继续沿用旧状态。
失败处理
本地压缩请求自身超限时,从历史最前面删除一项后重试,同时保留较新的消息;只剩一项仍然超限就报告无法压缩。
普通流式错误按服务商的重试上限退避重试,中断、会话预算耗尽与响应不完整不重试。服务端压缩的流式重试上限取服务商上限与 2 的较小者,当前模型失败时可以切换到替换模型重试。
轮次结束后压缩失败只发一条警告,保留已经完成的回答;轮次开始前压缩失败不会向客户端重复报错,用户刚输入的内容仍然保留。
3.4 OpenCode 压缩策略
检查点承担历史
OpenCode 把每次压缩做成一份可以续写的检查点。旧消息仍然留在数据库里,模型可见的历史从最后一次完成的压缩开始,一条检查点消息成为第一条消息,新消息直接排在它后面。
检查点由两部分组成:摘要承担较早的历史,最近一段原文保持逐字可读。下一轮既能拿到任务背景,也能拿到最近的工具结果和文件变更。
工具结果的体积在进入历史之前就被限制:默认 2,000 行和 50 KiB,超限时把原文写入独立文件,模型看到前部样本和文件路径,已经截断过的结果不再重复处理。
触发条件
预留空间取“窗口的 10%”和 16,000 Token 的较大者,窗口小于 32K 时只留 10%。可用上限等于窗口减去预留,用量达到可用上限时触发自动压缩;200K 窗口的预留为 20,000,可用上限为 180,000,相当于窗口的九成。
三条触发路径分别是自动压缩、服务商拒绝请求后的恢复、手动命令。模型窗口未知时不自动触发,收到超限错误后按 200K 估算窗口做恢复。刚完成压缩的那一次请求跳过判断,等待下一轮按新检查点重新计量。是否达到上限只依据当前服务商的用量统计,其他服务商的计数不能证明当前窗口的大小。
手动压缩进入队列,即使没有达到阈值也执行,执行完之后队列里的后续输入才开始处理。
一次压缩的执行过程
- 把消息拆成较早的历史和最近 15,000 Token 的原文,切分位置尽量落在用户消息上,避免拆开模型消息里的工具调用与其结果。
- 如果全部内容都能放进保留预算,强制切出最新一轮,保证还有更老的内容可以摘要;只有一轮时全部摘要,不保留原文。
- 把最近内容序列化为文本:用户文本、技能激活内容、附件标记、模型文本、推理过程、工具调用与结果、终端输出;单条工具输出超过 1,250 字符时截断。
- 较早的历史交给摘要模型,按固定章节输出。
- 摘要写入一条检查点消息,依次是检查点开始标记、说明这是历史上下文、摘要正文、可选的最近原文、检查点结束标记。
- 自动压缩完成后重新加载上下文并继续当前步骤;手动压缩结束后处理队列。
上一份检查点的合并
已有检查点时改用更新提示:新历史优先于旧检查点,合并仍然成立的信息,去掉已经解决的事项,不复制检查点的包装标记。旧模板出现时按新结构重写,避免继续携带旧模板里的冗余细节。
读取历史以最后一条完成的压缩为起点,因此上一份检查点总是第一条消息,不会出现多份摘要并排增长的情况。
服务商原生检查点
当前模型声明原生压缩能力时,调用服务商的压缩接口,由服务商返回替换窗口或加密压缩项,本地只记录最近的用户消息。原生检查点只能在产生它的服务商路由上重放,端点不一致时报告不支持并退回文本摘要。
请求内部的收缩
估算请求超过目标规模时,把历史转成文本记录:保留上一份摘要,之后的消息按整轮交互分组,整组保留或整组丢弃,从最老的一组开始,丢弃的位置写入“省略若干轮”的标记。文本记录省略推理过程、媒体内容和指令更新,长工具输出截断。
目标规模依次缩到 70%、50%、35%。字节超限的错误先不计数,直接把请求转成纯文本重发;纯文本仍然超限才进入按比例收缩。三次收缩之后仍被拒绝就失败;无法再缩且不能丢掉最新一轮或检查点时,报告无法继续缩减。
摘要模板与失败处理
摘要固定七个章节:目标、要求、决策、工作状态、下一步、相关文件、重要上下文;工作状态下再分已完成、进行中、受阻三项。相关文件最多列 15 个,要求写路径和一句原因。
规则要求单行短条目,保留精确的路径、符号、命令、错误字符串、URL 和标识符;只保留仍未解决的用户问题;保留工作流状态,例如未提交、已提交、已推送、评审中、已合并;摘要里不提及压缩过程。
模型回复必须包含模板里的至少一个章节标题,否则追加一次提示重试;第二次仍然不符合就报告摘要不符合模板。自动压缩失败会让当前步骤以失败结束;进程中断留下的未完成压缩在下一次处理时统一标记为中断;服务商拒绝后的恢复每一步只允许一次,压缩成功后清除恢复标记。
缓存方面,稳定指令与工具定义留在历史前部,检查点与最近原文位于后部。工具结果在进入历史前截断,减少切分位置移动的频率;若先把 50 KiB 的日志写进历史再整体压缩,摘要请求会重复读取这段日志。保留预算需要按任务类型调整:代码编辑任务保留最近的文件变更与测试结果,检索任务保留最近的来源与未解决问题。
3.5 DeepSeek Harness压缩策略
日志与视图分离
DeepSeek Harness 把会话保存为追加写入的事件日志,日志记录用户输入、模型动作、工具结果与压缩事件;模型每轮看到的是一份当前视图,视图通过替换事件遮蔽已经压缩的节点。
压缩只改动视图中的一段,日志里的原始事件保持不变。压缩过程本身也被写成事件,因此可以在事后回放某个检查点覆盖了哪些内容,也可以在恢复会话时重新折叠出相同视图。
逐层收缩的六项能力
| 能力 | 处理对象 | 动作 | 调用模型 | 触发条件 |
|---|---|---|---|---|
| 单条结果外置 | 超过 12,500 Token 的单条工具结果 | 保留头尾,完整内容写入文件并给出路径 | 否 | 结果进入视图时 |
| 工具结果修剪 | 超过 8,192 字符的单条结果 | 保留 4,096 字符头部、标记、1,024 字符尾部 | 否 | 压缩触发后、摘要之前 |
| 图片外置 | 超出预算的图片 | 替换为带只读路径的文本 | 否 | 服务商因图片预算拒绝请求 |
| 摘要压缩 | 最老的一段连续历史 | 替换为一条检查点 | 是 | 用量达到触发阈值 |
| 超限恢复 | 当前视图 | 强制做一次最大缩减后重试原请求 | 是 | 服务商确认上下文溢出 |
| 手动压缩 | 最老的一段连续历史 | 在空闲会话上强制执行 | 是 | 用户执行命令 |
前三项不需要模型请求,可以在摘要之前反复执行。工具结果修剪完成后会重新计量,回到预算范围就直接结束本轮,只有仍然超出才进入摘要。
预算如何计算
设模型窗口为 \(W\),本轮输出预留为 \(O\),额外余量为 65,536 Token。触发阈值与保留预算分别为:
200K 窗口、输出预留 20K 时,保留预算约 28,800,触发阈值取 160,000 与 114,464 的较小值,结果为 114,464。128K 窗口、输出预留 16K 时,保留预算约 17,920,触发阈值取 102,400 与 46,464 的较小值,结果为 46,464。小窗口下 65,536 的余量会成为主导项,触发点明显低于窗口的八成。
保留量也可以按模型配置成绝对 Token 数,两种方式不能同时设置,并且保留预算必须小于实际触发阈值。
一次压缩的执行过程
- 每轮请求前检查最近一次路由请求的用量,按当前路由的窗口与输出预留计算阈值。
- 达到阈值后先执行工具结果修剪,不需要模型调用。
- 重新计量当前视图,已经回到预算范围就结束本轮。
- 仍然超出时,从系统消息之后的第一条开始选取最老的一段连续历史,保留约 16% 的尾部,并把切分位置向前移动到工具调用与结果成对的位置。
- 组装摘要请求:回放视图第 0 条系统消息、上一次请求的工具声明、待压缩历史,把压缩指令作为最后一条用户消息追加。这个布局复用最近一次请求的稳定前缀。
- 校验摘要体积小于被遮蔽范围的估算,不满足就失败,不写入检查点。
- 依次写入开始、摘要、替换、结束四个事件,视图把旧范围换成一条检查点消息,下一轮重新投影系统提示词与请求头。
恢复与重新注入
视图第 0 条系统消息永远不进入压缩范围,压缩后仍在原位。
工具声明每轮按当前组装结果重新记录,检查点不承担工具定义。
计划状态以只写日志的整值替换事件保存,恢复、分叉与压缩都通过折叠日志复原。
项目指令以持久用户消息进入会话,可能被检查点遮蔽;后续对账时新增、替换与移除的指令会重新注入。
图片在省略事件中保存附件路径,模型可以按路径重新读取原图;超过单条上限的工具结果保留完整文件路径,用读取或搜索工具取回。
待办列表存放在工具调用参数中,压缩可能把它遮蔽,恢复后需要重新确认当前待办。
摘要设计与失败处理
摘要固定八个章节:主要请求与意图、关键技术概念、文件与代码、错误与修复、待处理工作、当前工作、下一步、关键上下文。每个章节都要保留并按顺序输出,空章节写“无”,不能删除章节。
规则要求短条目,保留精确的路径、命令、错误字符串、标识符、数值、函数签名与语法片段,忠实记录用户的纠正,摘要中不提及压缩请求或上下文被压缩。已存在检查点时按合并方式处理:保留仍然成立的事实,丢弃过期事实,合并新信息,不能原样搬运。
摘要只接受文本,返回图片时直接失败;达到输出上限时视为不完整,不把截断内容写入历史。摘要前导告诉下一轮模型这是自动生成的早期对话压缩,把它当作既定背景,直接继续后续消息。
失败处理分几层。摘要不小于被遮蔽范围时失败,不写入检查点。默认允许重试一次,多次之后仍然超过阈值就报错。服务商确认溢出后的恢复只允许一次,并且只有视图替换代次前进才重试原请求。未闭合的开始事件形成持久锁,后续压缩报告忙碌;早于新一轮会话开始的陈旧开始事件被忽略。取消的优先级最高,已经记录的修剪与图片省略不会撤回;自动压缩失败只记录警告,本轮继续执行。
能力的组合方式
插件化结构适合四类需要:同一个框架服务不同窗口与不同模型路由时,按提供方和模型分别设置阈值、保留量与摘要模型;工具结果、图片、摘要与手动命令需要分别替换时,按部署挂载对应能力;需要比较“只修剪结果”“修剪后再摘要”“直接摘要”三条路径时,每个组件单独启用并记录用量变化;需要替换摘要器时,只实现摘要接口,压力计算、范围选择、稳定性检查与事件事务仍由基础部分负责。
组合方式不能代替摘要质量设计。摘要模板仍要保留事实、来源、失败原因与待办,预算数值也需要结合真实请求测量。
4. 压缩策略最佳实践
压缩前先定义任务状态和原始产物的存储位置。上下文消息只承担当前决策,事件日志承担恢复与审计,文件或对象存储承担大结果和原文。三者职责清晰后,压缩可以只替换消息视图,不会破坏历史记录。
四个实现在同一组阶段上的做法不同:
| 阶段 | Claude Code | Codex | OpenCode | DeepSeek Harness |
|---|---|---|---|---|
| 计算压力 | 有效窗口减去缓冲,另按单轮增长提前 15,000 | 窗口 90% 与 95% 两层上限 | 窗口减去预留,达到可用上限即触发 | 窗口八成与扣除余量后的较小值 |
| 选择范围 | 旧工具结果与整轮交互 | 最近 20,000 Token 用户消息,或 64,000 Token 混合项 | 最近 15,000 Token 原文,按整轮切分 | 保留 16% 尾部,切分前移到工具配对完整 |
| 生成检查点 | 九部分摘要,输出上限 20,000 | 四段摘要或服务端压缩项 | 七章节摘要或服务商原生检查点 | 八章节摘要,空章节写“无” |
| 恢复状态 | 文件、技能、计划、后台任务 | 重新生成环境状态 | 检查点加最近原文 | 系统消息、工具声明、计划日志、图片路径 |
| 失败处理 | 按轮次删除重试 3 次,连续 3 次熔断 | 逐条删除最老历史,切换替换模型 | 依次缩到 70%、50%、35%,模板提示一次 | 摘要须更小,重试一次,事务锁 |
| 缓存处理 | 服务端缓存编辑删除旧结果 | 新窗口重建稳定前缀 | 工具结果提前截断 | 摘要指令接在稳定前缀之后 |
结果截断降低单次请求成本,局部删除降低历史噪声,整体摘要恢复跨阶段任务状态,服务端压缩减少本地摘要负担。实际选择取决于模型能力、任务时长、材料体积与协议支持,同一个框架在不同部署下也可以组合出不同的路径。
| 实践 | 执行规则 | 验收方式 |
|---|---|---|
| 低成本操作优先 | 先截断超大工具结果,再处理重复消息,最后生成整体摘要 | 统计每层释放的 token 与额外请求数 |
| 保留精确事实 | 文件路径、URL、哈希、ID、时间、数值、失败错误码逐字保存 | 从检查点恢复后再次调用工具,标识符仍可用 |
| 保留任务关系 | 摘要同时写明目标、动作、结果、来源和下一步 | 新模型能解释结果服务于哪个目标 |
| 原始材料外置 | 全文、日志、图片和网页写入带版本的产物路径 | 根据路径可以重新读取和核查原文 |
| 状态独立维护 | 阶段、待办、权限、工作目录、预算不依赖聊天历史推断 | 压缩或重启后状态字段仍一致 |
| 工具调用成对处理 | 压缩范围不能拆开 assistant tool call 与 tool result | 消息协议检查通过,模型不会重复或误配调用 |
| 稳定前缀优先 | 系统规则、工具定义和固定示例保持稳定,动态信息追加到末尾 | 监控 Prompt Cache 命中与首词时延 |
| 失败可恢复 | 保存压缩前后 token、触发原因、摘要版本和重试次数 | 模拟超限、取消和摘要失败后能继续任务 |
| 压缩结果可审计 | 记录摘要覆盖的消息范围和原始产物引用 | 可以回答“这条结论来自哪次操作” |
摘要模板建议固定字段,避免每次压缩都改变结构。下面的字段适用于编码、检索和产品文档任务:
目标:用户要完成什么,范围是否发生变化
已确认事实:带时间、数值、来源和状态
关键决策:采用或放弃了哪些方案,原因是什么
已执行动作:工具、文件、接口和结果
失败与限制:错误、权限、网络、尚未验证的假设
当前状态:阶段、已完成项、进行中项目、待办
原始产物:文件路径、URL、对象 ID、版本或哈希
下一步:恢复后应执行的第一项动作压缩触发量可以用三组数据共同评估:输入 token 减少量、压缩额外请求成本、任务恢复后的成功率。只看压缩比例会掩盖摘要错误;只看缓存命中又可能忽略模型重复调用。至少需要记录压缩前后 token、摘要耗时、缓存命中、失败重试、重复工具调用和用户重新说明次数。
任务类型会影响保留策略。代码修改任务重点保留文件路径、变更内容、测试命令和失败日志;检索任务重点保留来源、发布日期、事实和冲突结论;规划任务重点保留约束、决策、依赖和待确认事项。对于包含大量探索材料的任务,优先使用子 Agent 隔离中间过程,主 Agent 只接收结构化结果,可以减少压缩发生的频率。
不要把压缩摘要当作新的永久知识。跨任务经验需要经过独立评估后写入项目文档、知识库或记忆系统;当前任务的检查点只服务于当前会话恢复。压缩完成后,继续让模型读取原始产物并核查关键事实,能够及时发现摘要中遗漏的限定条件。