# 上下文压缩与快照管理

## 问题

长时间运行的 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 中的截断通知不是事后想法或泛泛的警告。每个通知都指明模型应使用的确切工具和参数。这种具体性很重要：模糊的"输出已被截断"消息迫使模型猜测如何恢复，而具体的指令让它可以立即行动。
