派 5 个子 agent 并行干活,账单却几乎只算一份——扒一扒 Claude Code 的 Prompt Cache 与多 agent 缓存共享
你有没有想过:Claude Code 一次能并行派出好几个子 agent 同时改代码,可它的 token 账单为什么没有跟着翻好几倍?
答案藏在一个很多人听过、但理解得不太准的机制里:Prompt Cache(提示缓存),以及建立在它之上的多 agent 缓存共享。这篇我顺着 Anthropic 的官方工程博客、官方文档和社区对源码的逆向分析,把它从原理到定价、再到"省钱魔法"完整捋了一遍。
Anthropic 自己说得很直白:Claude Code 的整个执行框架(harness)就是围绕 prompt cache 建起来的。缓存命中率高,不只是省钱,还能换来更低延迟、更慷慨的订阅额度——他们甚至给命中率设了告警,掉太低就当线上事故(SEV)处理。所以理解它,基本等于理解了 Claude Code 为什么能"既自主又便宜"。
一、原理:它缓存的不是"语义",而是"这串前缀我见过没"
先破除一个误会:prompt cache 不是让 Claude"记住"你聊过的内容。它做的事更机械——前缀匹配(prefix match)。

一次 API 请求,内容按固定顺序拼好:tools(工具定义)→ system(系统提示)→ messages(消息历史)。你在某个块上打一个断点(cache_control),断点之前的整段前缀会被哈希成一把"缓存键"。下次请求如果前缀的哈希一样,就直接复用上次算好的中间结果(KV cache),跳过重算。
这把"缓存键"由五个要素共同决定,任意一个变了,钥匙就变,配不上原来的锁:
- 系统提示(system prompt)——那段长指令
- 工具定义(tool definitions)——增删任何工具都会变
- 模型(model)——Opus 和 Sonnet 架构不同,算出来的 KV 不通用,缓存不能跨模型复用
- 消息前缀(message prefix)——到断点为止的对话历史
- 思考配置(thinking config)——扩展思考的开关或预算变了也会失效
命中的唯一标准非常严格:前缀必须字节级完全一致(byte-for-byte)。有个被反复传播的真实事故:某团队在系统提示开头写了 "Today is {今天日期}...",就这一个每天都变的日期 token,导致全天零命中,缓存形同虚设。所以那句话值得记死——缓存匹配认的是字节,不是意思。
二、账怎么算:写一次贵一点,读多次省很多
理解了"什么会命中",再看"命中到底省多少"。

价格都用相对"基础输入价"的倍率来表示:
| 类型 | 倍率 | 说明 |
|---|---|---|
| 基础输入(不缓存) | 1.0× | 每次全量计算 |
| 命中读取 cache read | 0.1× | 读缓存里算好的结果,省钱的来源 |
| 写入 cache write(5 分钟 TTL) | 1.25× | 第一次建立缓存的成本,默认时长 |
| 写入 cache write(1 小时 TTL) | 2.0× | 贵一倍但活 12 倍长,适合稀疏/突发流量 |
所以盈亏逻辑很简单:写一次只贵 25%,但之后每读一次省 90%——只要读回一次就开始回本,读得越多越赚。反过来,如果你的前缀每次都在变(写了却从不命中),那就是纯亏那 25%。默认 TTL 是 5 分钟,每次命中会自动刷新且不额外收费,所以只要请求不断,一个热前缀可以一直活着。
另一半是失效的"级联"特性:因为是前缀匹配,你改动的位置越靠前,被废掉的越多——
- 改 tools(最靠前)→
tools + system + messages整段全废; - 改 system → 工具还在,
system + messages废; - 改某条 message → 只废从那条往后的部分。
这就是"顺序为什么重要"的根因:把最稳定的东西(工具、系统提示)放最前,最易变的(用户输入、时间戳)放最后。另外,自动缓存模式会从你的断点回看约 20 个内容块,自动帮你找最长的可复用前缀,不用手动猜断点位置。
三、勘误:粘性锁存(sticky latch)不是"保护",是"破坏后锁死"
这是我最需要明确的一点,也是这篇最有价值的部分。

社区对 Claude Code 源码的分析显示,真相是:promptCacheBreakDetection.ts 这个模块追踪 14 种不同的缓存破坏向量,而 sticky latch 的作用是——一旦某个向量触发、缓存被破坏,这个"闩锁"就锁死在破坏状态,本会话剩余时间里不再尝试恢复缓存。
为什么要"锁死"而不是"修好"?因为缓存一旦被破坏,系统再去反复"尝试恢复 → 又破坏"只会白烧 token。索性认栽锁定,同时给你一个明确信号:这条会话的缓存回不来了,别指望"撤销刚才的改动"能救回来——撤销也没用。
这恰好解释了很多人遇到的现象:会话开头很快,越用越慢。每一次配置改动都累积一次缓存破坏,而 sticky latch 让破坏不可逆,命中率像下楼梯一样一级级掉、下去就上不来。常见的破坏向量包括:中途换模型、增删工具或挂载新 MCP、改 CLAUDE.md、装新 Skill、闲置超 TTL、加删图片、改 thinking 设置、每请求都变的请求头(如时间戳、attribution header)等等。
实践结论很清晰:把所有配置改动都挪到会话开局——开会话之前就把模型、CLAUDE.md/规则、MCP、Skill 都定好;任务中途尽量别动它们,临时指令写进当前对话,而不是去改长期记忆。
四、核心:多 agent 缓存共享,省钱魔法在 Fork
终于到了开篇那个问题——为什么并行派多个子 agent,账单不翻倍?

先纠正一个容易混淆的点:并不是所有"子 agent"都共享缓存。这里要分清两种:
- 普通 subagent(指定类型,如 Explore、Plan、code-reviewer):它有自己独立的系统提示,上下文从头开始(fresh、isolated),不会字节级继承父的完整前缀。它能复用的只是公共子集——比如它的工具列表是父工具的子集,可以蹭到工具定义那部分的缓存。
- Fork(分叉)(调用 Task 时省略
subagent_type触发):它是父 agent 的"上下文克隆"——系统提示 + 工具定义 + 对话历史都与父完全一致,因此第一个请求就命中父的缓存。这才是真正的"字节级继承"。
Fork 省钱有多夸张?举官方分析里的例子:父 agent 要重构一个模块,并行派出 5 个 fork 分别改 schema、service 层、router、tests、types。此时它们的共享前缀大约 8 万 token,而每个子任务真正"新"的只有那条 ~200 token 的指令(“你负责改 schema”)——重叠率高达 99.75%。配上缓存命中的 90% 折扣,对父来说,同样一次并行分派的成本从 $4 降到 $0.5。
Fork 是怎么做到"字节级一致"的?靠三层冻结:① 系统提示通过**线程传递(threading)**直接复用父上次渲染好的结果,而不是重新计算(避免任何字节漂移);② 全工具透传 + 继承父模型;③ 甚至连禁用的 Agent 工具都保留在工具池里,只为让工具定义的字节完全对齐。用 CLAUDE_CODE_FORK_SUBAGENT=1 启用,且 Fork 不能再 spawn 出 Fork,防止无限套娃。
五、实战:把命中率守住 + Claude Code 自己的工程智慧

你能做的,可以按风险分级记:
- 🔴 高危(几乎必废):中途换模型、增删工具/重载 MCP、改 CLAUDE.md、装新 Skill、闲置超 TTL。
- 🟡 中危(看情况):改 thinking 开关/预算、reload 插件。
- 🟢 低危(基本安全):正常对话(只在末尾追加消息)、侧线探索走 subagent/Fork、反复读同一文件(文件内容也能被复用)。
四条保命口诀:① 静态在前、动态在后,时间戳/git 状态塞进用户消息而绝不写进 system 正文;② 要换模型就用 subagent 做侧活,主线模型不变、长前缀不动;③ 临时指令写进当前对话,别改 CLAUDE.md 这种长期记忆;④ 接本地模型(Ollama/llama.cpp 等)变慢,多半是 attribution header 每次变化在破坏缓存,可在 ~/.claude/settings.json 里关掉它。
而Claude Code 自己则把"为缓存而设计"做到了极致,几个例子很能说明问题:
- 系统提示拆成"稳定区 + 增长区":稳定的指令和工具常驻缓存,只让对话本身逐轮增长,整体 prompt 复用率高达 92%。
- Plan Mode 故意不换工具集:直觉做法是进 plan 模式就只留只读工具,但那会废掉缓存;它选择换约束、不换工具,用别的方式实现"只读",守住前缀。
- 压缩(compaction)也复用父前缀:压缩请求长得和父上次几乎一模一样(同前缀、同工具、同历史),只有压缩提示是新 token,所以"总结一下上文"这件事本身也很便宜。
- 启动时预热(warm-up):开场先发几个 dummy 请求,把工具列表、subagent 前缀先写进缓存;等真正调用时直接命中,而这点延迟还被"取标题"等元数据请求顺手盖掉了。
写在最后
把这一圈走下来,会发现 Prompt Cache 是 Claude Code 这套 harness 的地基,而不是一个可选的优化项。它的逻辑层层咬合:前缀匹配决定了"什么能复用"→ 字节级一致决定了"怎么才算复用"→ 定价倍率决定了"复用值多少钱"→ Fork 把这套机制推到极致,让多 agent 并行的边际成本逼近于零。
而那个看起来反直觉的 sticky latch,其实是同一套哲学的延伸:承认缓存破坏不可逆,于是干脆锁定、不做无用功——和上一篇聊到的"断路器"异曲同工(顺带一提,断路器存在的部分原因,正是历史上一次压缩重试失控,单日浪费了约 25 万次 API 调用)。
对我们这些在它之上搭东西的人,结论朴素得有点无聊,却最值钱:开局把稳定的前缀定好,任务中就别再去碰它。 守住前缀,就是守住钱包。
参考来源
- Anthropic 工程博客:Lessons from building Claude Code — Prompt caching is everything
- Claude Platform 官方文档:Prompt caching、Context editing、Sub-agents(platform.claude.com / code.claude.com)
- 社区源码逆向分析:Claude Code source-leak 分析、Claude Code from Source(Fork Agents 章节)、LLM 流量 trace 分析
本文为个人学习整理,图示为自制,关键数字与机制以官方文档/博客为准。理解若有偏差,欢迎评论区指正。
更多推荐


所有评论(0)