Claude-thermos:保持 Claude 会话缓存热度,避免重新编码费用
Show HN: Claude-thermos 能为您保持 Claude 会话的热度
Claude-thermos 通过本地反向代理监控 Claude Code 会话,在主智能体因等待子智能体而空闲超过 5 分钟时,自动发送预热请求刷新提示缓存。实测约 185 次本地会话中,缓存过期导致的重新编码占账单约 22%。工具以 uvx 运行,支持自定义空闲阈值和预热间隔。
一个小工具解决了一个被忽视的成本黑洞,Claude Code 的缓存过期,作者自己测了 185 个会话,省下 22% 费用,对于把 Claude 当开发助手的团队来说是个必装工具。
别再花钱重建你的 Claude Code 缓存了。当你的主智能体等待某个子智能体超过 5 分钟时,它的提示词缓存会悄然过期,下一轮就会以写入费率重新编码你的整个对话,而不是以低廉的费率把它读回来。在包含大量子智能体的长会话中,这大约占你账单的 20%。claude-thermos 让缓存保持温热,这样你就永远不必支付这笔税。
使用
像平常一样运行 Claude Code,但通过 claude-thermos 配合 uvx 来运行:
uvx claude-thermos # instead of: claude
uvx claude-thermos -p "fix the bug" # any claude args pass straight through
需要 Python 3.11+ 以及claudeCLI,位于你的PATH.
就是这样。预热会在后台自动运行。若要在某次运行中禁用它而不修改命令,请设置 CLAUDE_WARMER_DISABLE=1。
调优(全部可选):
| 标志 | 默认 | 含义 |
|---|---|---|
--idle | 270 | 主智能体在预热启动前必须保持空闲的秒数 |
--interval | 270 | 预热周期之间的间隔秒数 |
--max-cycles | 4 | 每次空闲时段的最大预热次数(auto 表示不限制) |
--subagent-window | 540 | 子智能体被视为"仍处于活跃状态"的秒数 |
为什么你的缓存总是过期
Claude Code 的提示词缓存使用 5 分钟 TTL。只要缓存保持存活,每一轮对话的完整历史记录都以 0.1x 的输入价格从缓存中读取,而无需按全价重新发送。
如果同一前缀上的两次请求之间间隔超过 5 分钟,缓存就会过期。造成这种间隔的主要触发因素并不是你在思考。而是主智能体被一个运行超过 5 分钟的子智能体阻塞。子智能体拥有不同的系统提示词和工具集,因此它的请求具有不同的缓存前缀,永远不会刷新主智能体的缓存。在子智能体工作期间,主智能体的缓存历史闲置不动;超过 5 分钟后它就消失了。当子智能体返回时,主智能体以字节完全相同、仅追加的历史继续运行,却发现自己的缓存已丢失,被迫以 1.25 倍的写入费率进行完整的重新编码。
到那时历史已经很大,因此重新编码代价高昂:单次坍缩就会重写 200K 到 500K 个 token。在大约 185 个本地会话中测量,这些重建约占总账单的 22%,这些钱花在了重新编码片刻之前就已经缓存过的内容上。
工作原理
claude-thermos 在一个小型本地反向代理后面启动 Claude Code(它将 ANTHROPIC_BASE_URL 指向一个回环端口;所有流量仍然发往真正的 Anthropic API)。
- 观察。代理监视
/v1/messages流量,并将其分组为会话和谱系,一个谱系就是一个缓存前缀,以模型 + 工具集 + 系统文本为键。第一个带工具的谱系是主智能体;其余的是子智能体。 - 检测危险窗口。当主线程进入空闲状态且某个子智能体正在活跃运行时,主前缀就面临过期风险。
- 预热。在低于 5 分钟 TTL 的间隔内,它会把主智能体最后一次真实请求作为预热请求重放:可缓存前缀完全相同,但
max_tokens: 1且不进行流式传输。那个单独的 token 会被丢弃;关键在于预填充,它会读取并刷新整个已缓存前缀。预热请求直接发往 API,从不经过代理,因此不会干扰真实流量。 - 结果。当子智能体完成时,主智能体的缓存仍然是热的。它只需支付一次廉价的读取,而不是一次完整的重写。
每次预热只需一次缓存读取(0.1x);而它避免的每次重写,本会在一个大得多的前缀上产生一次写入(1.25x),因此这笔交易对你极为有利。
事件日志与节省
每个会话都会写入:
~/.claude-thermos/logs/<session_id>/
├── events.jsonl # append-only structured event stream
└── summary.json # rollup totals, written when the session ends
events.jsonl记录每个请求/响应的 token 用量,以及每一次预热决策(warm_fired、warm_result、cap_reached、resume_detected等)。summary.json是你通常会读取的汇总:
| 字段 | 含义 |
|---|---|
warms_fired | 已发送的预热请求 |
cache_read_total | 这些预热请求读回的 token 数 |
episodes | 以成功恢复(实际避免了一次重写)告终的带子智能体空闲片段 |
rewrite_avoided_tokens | 本会被重写的 token 数,跨所有片段求和 |
warm_cost | 预热让你付出的成本:0.1 × cache_read_total |
rewrite_avoided_cost | 它节省的:1.25 × rewrite_avoided_tokens |
net_savings | rewrite_avoided_cost − warm_cost |
这三个成本数字都以基础输入 token 单位计(token 数已按其缓存倍率加权)。要把net_savings换算成美元,将其乘以你的模型每输入 token 的价格:
dollars saved ≈ net_savings × (input token price)
例如,在输入价格为 $3 / 1M tokens 的情况下,net_savings 的 1_200_000 在该会话中大约节省了 1_200_000 × $3 / 1_000_000 = $3.60。
来源:Hacker News 热门(buzzing.cc 中文翻译) · github.com