【Claude Code-上下文实测】Compaction 与 prompt cache 协同:一个想改写历史,一个需要稳定前缀
非线智能API经验 [Claude Code-上下文实测] 第17篇
摘要
Compaction 和 prompt cache 都在优化长上下文成本,但它们的目标并不天然一致。prompt cache 依赖稳定前缀:system prompt、工具定义、项目规则、长文档越稳定,缓存复用越好。compaction 则会把旧历史替换成摘要,改变上下文前缀结构,可能导致 cache miss。
因此,长会话 agent 不能只说“开缓存”和“开压缩”。真正的工程问题是:哪些内容属于稳定前缀,哪些属于动态尾部,cache breakpoint 放在哪里,compact point 是否会跨过稳定区,工具定义和工具结果分别如何处理。缓存和压缩要协同设计,否则可能出现 token 变少了、缓存却被打碎的反效果。
数据来源:非线智能Nonlinear 官网
证据层级
Anthropic Prompt Caching 文档、Prompt Caching cookbook、Tool use with prompt caching 文档和 Pricing 文档可以支撑 prompt cache 的机制、成本和工具定义缓存策略。Anthropic Compaction 文档可以支撑压缩会替换旧上下文的机制。arXiv “Don't Break the Cache” 这类论文适合支撑长时 agentic 任务中 prompt caching 需要针对动态工具结果和缓存策略专门优化。
Claude Code 生态里的第三方文章可以作为实践解释,但不应替代 Anthropic API 官方文档。
prompt cache 优化的是什么
prompt cache 的核心收益来自重复前缀复用。对 coding agent 来说,适合缓存的内容通常包括:
| 内容 | 为什么适合缓存 |
|---|---|
| system prompt | 长期稳定,几乎每轮都出现 |
| 项目规则 | CLAUDE.md、编码规范、仓库约束变化频率低 |
| 工具定义 | schema 和 description 体积大,重复发送成本高 |
| MCP 描述 | 如果工具集固定,属于高成本前缀 |
| 长文档 / 参考资料 | 多轮任务会反复引用 |
缓存希望这些内容保持稳定:顺序稳定、文本稳定、分段稳定、cache breakpoint 稳定。越多动态内容混进前缀,越容易 cache miss。
compaction 优化的是什么
compaction 的目标是让长会话继续运行。它通常会把旧消息归纳成摘要,用更短的 compacted context 替代完整历史。对长时任务来说,compaction 能减少输入 token,也能移除低价值历史。
但这个动作会改变消息数组。append-only 会话虽然越来越长,但早期前缀通常是稳定的;一旦 compaction 把旧历史替换成摘要,前缀内容和边界就变了。对 cache 来说,这不是“同一前缀变短”,而是“前缀被改写”。
二者冲突的根源
可以把冲突写成一句话:
prompt cache 想保留可重复前缀;compaction 想用摘要替换历史前缀。
典型冲突场景如下:
| 场景 | 结果 |
|---|---|
| 会话接近上限后一次性 compact | 大段历史前缀变化,后续缓存命中下降 |
| 工具结果插入在缓存边界之前 | 每次工具调用都污染 cacheable prefix |
| compact 摘要每次自由发挥 | 摘要语序和字段波动,产生无意义 churn |
| 工具定义顺序不稳定 | 即使没有压缩,缓存也会 miss |
| MCP 工具动态增删 | schema 前缀变化,cache key 失效 |
| 压缩把项目规则也重写 | 稳定区被误当成历史区处理 |
所以问题不是“compaction 会不会破坏 cache”,而是“它破坏的是哪一段 cache,以及能不能把破坏限制在动态区”。
上下文分层
建议把请求上下文拆成四层:
| 层 | 内容 | 变化频率 | 缓存策略 | 压缩策略 |
|---|---|---|---|---|
| Stable Prefix | system prompt、工具定义、项目规则、长期约束 | 低 | 主要 cache breakpoint | 不压缩、不重写 |
| Semi-stable State | 当前任务摘要、关键决策、已验证事实 | 中 | 可短期缓存 | 阶段性结构化更新 |
| Dynamic Tail | 最新用户消息、工具结果、测试日志、错误输出 | 高 | 通常不追求长缓存 | 主要压缩对象 |
| Audit Store | 原始日志、长文件片段、截图、完整工具输出 | 外置 | 不进入 prompt cache | 用引用替代原文 |
这个分层的核心是:不要让高频变化内容进入 Stable Prefix,也不要让 compaction 改写 Stable Prefix。
cache breakpoint 设计
cache breakpoint 应尽量放在稳定内容之后,而不是动态内容之后。一个典型顺序是:
[system prompt]
[project rules / CLAUDE.md summary]
[tool definitions / MCP descriptors]
<cache breakpoint>
[task state summary]
[recent conversation]
[live tool window]
如果工具定义很大,且 provider 支持工具定义相关缓存策略,应优先让工具定义处在稳定、排序固定、可缓存的位置。延迟加载工具时,也要避免每轮随机改变工具列表顺序或 schema 表达。
cache breakpoint 放错位置会出现两类问题:
| 错误位置 | 后果 |
|---|---|
| 放在动态 tool result 后 | 每次工具结果变化都会让前缀失效 |
| 放在工具定义前 | 大工具 schema 无法复用,缓存收益变小 |
| 放在动态 metadata 后 | trace id、session id、attribution block 造成 cache miss |
| 放在 compact 摘要内部 | 摘要字段变化导致边界不稳定 |
compact point 设计
compact point 应优先作用于 Dynamic Tail,其次作用于 Semi-stable State,尽量不要跨过 Stable Prefix。
推荐策略:
1、稳定前缀只通过版本更新改变,不通过 compact 改写。
2、工具结果和错误日志定期变成结构化摘要。
3、阶段结束时,把 Dynamic Tail 汇总进 Semi-stable State。
4、Semi-stable State 使用固定字段,减少无意义文本波动。
5、完整原文移到 Audit Store,通过 hash / artifact 引用。
这样,compact 后变化主要发生在 cache breakpoint 之后,或者只影响一个可预期的 task state segment,而不是打碎整段前缀。
摘要格式也会影响缓存
即使摘要内容不可避免会变化,摘要结构仍然可以稳定。自由散文摘要常见问题是每次措辞、顺序、粒度都不同,导致 diff 很大。结构化摘要能减少这种无意义 churn:
{
"goal": "...",
"constraints": [],
"verified_facts": [],
"decisions": [],
"changed_files": [],
"open_issues": [],
"next_steps": []
}
字段顺序固定、类别固定、状态标记固定,可以让 prefix diff 更容易归因。即使 cache 不能命中旧摘要,也能帮助观测系统判断到底是“任务状态真实变化”,还是“摘要表达抖动”。
工具定义与工具结果要分开治理
工具定义和工具结果都和工具有关,但缓存策略完全不同:
| 项 | 工具定义 | 工具结果 |
|---|---|---|
| 体积 | 可能很大 | 可能极大 |
| 变化频率 | 低到中 | 高 |
| 是否适合缓存 | 适合 | 通常不适合 |
| 是否适合压缩 | 一般不压缩,做裁剪 / 懒加载 | 适合摘要 / 外置 |
| 风险 | schema 顺序抖动导致 cache miss | 长日志污染上下文 |
如果把工具结果和工具定义混在同一前缀区,缓存会被高频工具输出污染。如果为了压缩工具结果而重写工具定义区,也会破坏本来最值得缓存的高成本 segment。
成本观测指标
协同设计必须用指标验证,不能只凭感觉。至少记录:
| 指标 | 用途 |
|---|---|
input_tokens |
总输入成本 |
cache_creation_input_tokens |
缓存写入成本 |
cache_read_input_tokens |
缓存复用收益 |
uncached_input_tokens |
未命中部分 |
ttft_ms |
首 token 延迟 |
prefix_churn_tokens |
稳定前缀变化规模 |
changed_segments |
哪些 segment 变化 |
compact_event_id |
哪次 compact 造成变化 |
tool_schema_hash |
工具定义是否稳定 |
task_state_hash |
摘要是否真实变化 |
一个重要指标是 compact 后 1、3、5 轮 cache read token 的恢复情况。如果 compact 后长时间 cache read 降低,说明 compact point 可能跨过了稳定区,或者摘要格式抖动过大。
失败模式
| 失败模式 | 触发原因 | 修复策略 |
|---|---|---|
| compact 后 cache miss 激增 | 压缩改写稳定前缀 | Stable Prefix 不参与 compact |
| 工具定义缓存不稳定 | schema 顺序或描述动态生成 | canonical sort,schema version hash |
| 动态 metadata 破坏缓存 | trace id、session id 进入前缀 | metadata 移出 cacheable prefix |
| 摘要每次大幅 diff | 自由散文摘要 | 固定字段结构化摘要 |
| cache write 成本过高 | 频繁写入大前缀 | 增大复用窗口,减少无效 breakpoint |
| 压缩省 token 但变慢 | cache read 下降、TTFT 上升 | 比较 compact 前后 cache 指标 |
验证清单
• 同一任务分别运行 append-only、阈值 compact、阶段性 compact,比较成本和成功率。
• 把 cache breakpoint 分别放在工具定义前、工具定义后、动态尾部后,观察 cache read token。
• 构造动态 tool result 进入前缀的场景,验证 cache miss 是否可归因。
• 压缩后连续请求 5 轮,记录 cache read / write / uncached token。
• 打乱工具定义顺序,确认 canonicalization 后 hash 不变。
• 对比自由摘要和结构化摘要的 prefix churn。
工程结论
prompt cache 和 compaction 不是互斥能力,但它们必须有边界。缓存负责复用稳定前缀,压缩负责收敛动态历史。只要 stable prefix、semi-stable state、dynamic tail 和 audit store 分层清楚,compaction 就可以减少上下文压力,而不必打碎最有价值的缓存。
参考链接
• Anthropic Prompt Caching
• Anthropic Cookbook: Prompt caching with the Claude API
• Anthropic: Tool use with prompt caching
• Anthropic Pricing
• Anthropic Compaction
• arXiv: Don't Break the Cache
• ClaudeCodeCamp: How Prompt Caching Actually Works in Claude Code
• Start Debugging: Claude Code 2.1.169 Adds --safe-mode and a /cd That Keeps the Prompt Cache Warm
本文由非线智能API Claude Code 行业专家整理编写