上下文压缩与快照管理
问题
长时间运行的 Agent 会话不可避免地耗尽上下文窗口。每一轮都增加工具调用、结果和推理,而这些内容不会自行缩减。如果不压缩,Agent 要么触及硬 token 上限而失败,要么模型的注意力会随着有用信息被埋在大量过时输出之下而退化。可变长度的上下文块——目录列表、git status、搜索结果——使问题更加严重,因为其大小不可预测,可能在单一轮次中飙升数个数量级。
第二个更微妙的问题:捕获时准确的上下文会随着会话推进而变得具有误导性。轮次 3 的 git status 在轮次 30 时仍被视为当前事实,除非有某种东西将其标记为时间点快照。
这些问题影响任何跨轮次持久化的 Agent 运行时。并非特定于任何单一实现。
黄金法则
截断时带恢复指针,永远不要盲截
当上下文块超过字符或 token 上限时,截断它——但始终追加恢复指针:一条具体指令,告诉模型调用哪个工具、用什么参数来检索完整输出。没有恢复指针的截断是死胡同。模型知道数据存在,知道它被截断了,但没有回去的路径。这会产生幻觉式补全或静默失败。
响应式紧缩,而非按计划
固定窗口紧缩(如"每 20 轮摘要一次")要么触发太早丢失有用上下文,要么触发太晚撞到 token 上限。响应式紧缩监控实际填充率并在窗口真正满时触发。紧缩过程摘要较旧的轮次,同时保留近期轮次和活跃的工具结果,在会话中途恢复预算而不丢失工作线索。
给每个快照加标签
任何捕获时间点状态的上下文块——git status、目录列表、进程输出、文件内容——都必须携带显式标签:捕获时间和一条说明数据不会自动更新的注释。没有这个标签,模型会将过时数据视为当前数据,产生与世界实际状态矛盾的推理。
给每个可变长度块设上限
每个长度依赖于外部状态的上下文块都必须有硬字符或 token 上限。目录列表、搜索结果、git diff、日志输出——所有这些都可能不可预测地飙升。没有上限,单个大结果可以在一轮中消耗上下文预算的大部分,挤掉其他所有内容。
适用场景
- Agent 的性能随着会话变长而明显下降。
- Agent 反复基于过时数据行动(旧的 git status、过时的文件内容、陈旧的搜索结果)。
- 可变长度上下文块偶尔飙升并挤掉其他信息。
- 会话足够长,上下文窗口接近其 token 限制。
- Agent 对截断输出进行幻觉式补全而非重新获取。
权衡
| 决策 | 收益 | 代价 |
|---|---|---|
| 带恢复指针的截断 | 模型始终可以检索完整数据 | 需要完整数据时多一次往返 |
| 响应式紧缩 | 动态延长有效会话长度 | 较旧的上下文是有损压缩的,非无损保留 |
| 快照标记 | 防止基于过时数据的推理 | 每个上下文注入点都必须添加标签 |
| 可变块的硬字符上限 | 单个块不能耗尽预算 | 有用数据可能被截断;恢复指针的质量很重要 |
| 紧缩时保留近期轮次 | 模型保留当前任务的工作记忆 | 紧缩逻辑必须区分"近期"和"旧" |
实现模式
- 对每个可变长度上下文块强制执行字符或 token 上限。上限应保守设置——提高一个太紧的上限比调试一个块消耗了整个预算的会话要容易。
- 追加截断通知,指明获取完整数据所需的具体工具和调用方式。通知应具体:"运行 [工具] 加 [这些参数] 以查看完整输出",而非模糊的"输出已被截断"。
- 为每个注入的快照标记捕获时间戳和数据为静态的说明。将标签放在块的顶部而非底部——模型按顺序处理 token,需要在开始推理内容之前就看到过时警告。
- 实现填充率监控器,在上下文窗口达到阈值(如 80% 满)时触发紧缩。紧缩过程应将较旧轮次摘要为浓缩叙述,同时逐字保留最近轮次和任何活跃的工具结果。
- 紧缩时,保留截断块中的恢复指令。如果截断通知本身被紧缩掉,模型就失去了回到完整数据的路径。
- 在紧缩输出中包含显式指令,告诉模型什么被摘要了以及如何在需要时重新获取详情。模型应知道紧缩发生了以及丢失了什么。
踩坑指南
没有恢复指针的截断是死胡同。 如果你限制了上下文块但不告诉模型调用哪个工具获取完整输出,模型就没有回到数据的路径。它要么幻觉缺失的内容,要么静默放弃依赖它的任务。
快照标签防止过时推理。 没有"这是时间 T 的快照"标签注入的 git status 会导致模型将数据视为实时的。它会产生与实际当前状态矛盾的分支、diff 和合并推理。
紧缩必须保留恢复指针。 如果早期轮次的截断通知本身在紧缩中被摘要掉,模型就失去了重新获取完整数据的能力。恢复指针应在紧缩过程中存活。
可变长度块可能飙升数个数量级。 小项目中的目录列表是 20 行;在 monorepo 中可能是 20,000 行。Git diff 通常很小;在大规模重构后可能很大。上限必须为最坏情况设置,而非常见情况。
响应式紧缩不能在活跃工具使用期间触发。 如果紧缩在多步工具序列进行中触发,它可能摘要掉模型下一步需要的中间结果。将紧缩门控在静默状态——轮次之间,而非轮次中途。
Claude Code 实证
Claude Code 的压缩策略在长时间运行的交互式会话环境中展示了这些原则:
Git status 作为带标签的、有上限的快照。 注入上下文的 git status 字符串携带显式说明:"this status is a snapshot in time, and will not update during the conversation。"当输出超过 2000 字符阈值时,它被截断并附带消息指引模型运行特定工具以查看完整输出。标签加恢复指针的配对意味着模型始终知道数据是过时的,且始终有获取新鲜数据的路径。
长会话的响应式紧缩。 当上下文窗口在长会话中被填满时,运行时触发紧缩过程,摘要较旧的对话轮次同时保留最近的交流和任何活跃的工具结果。模型获得关于什么被摘要的显式上下文。这种方法在没有硬轮次限制的情况下延长有效会话长度——只要紧缩在每次过程中能恢复足够预算,会话就可以运行数小时。
所有可变长度注入的字符上限。 每个大小依赖于外部状态的上下文块——git status、目录列表、文件内容——都有硬字符上限。设计哲学是没有单个上下文来源应该能够耗尽所有其他来源的预算。当触及上限时,截断通知始终包含检索完整输出所需的具体工具调用,确保模型永远不会被困在死胡同。
恢复指令作为一等内容。 Claude Code 中的截断通知不是事后想法或泛泛的警告。每个通知都指明模型应使用的确切工具和参数。这种具体性很重要:模糊的"输出已被截断"消息迫使模型猜测如何恢复,而具体的指令让它可以立即行动。