izeigerman/claude-thermos

GitHub: izeigerman/claude-thermos

通过本地反向代理在 Claude Code 会话空闲期间自动预热 prompt 缓存,避免缓存过期导致的重复编码费用。

Stars: 111 | Forks: 2

# claude-thermos **别再为重建 Claude Code 缓存买单了。** 当你的主 agent 等待 subagent 超过 5 分钟时,它的 prompt 缓存会悄悄过期,下一次交互会以写入费率重新编码你的整个对话,而不是以低廉的价格读取。在涉及大量 subagent 的长会话中,这大约占你账单的 20%。`claude-thermos` 可以保持缓存活跃,让你永远不必支付这笔无谓的开销。 ## 用法 像平常一样运行 Claude Code 即可,只需通过 `uvx` 使用 `claude-thermos`: ``` uvx claude-thermos # instead of: claude uvx claude-thermos -p "fix the bug" # any claude args pass straight through ``` 要求安装 Python 3.11+,并且 `claude` CLI 位于你的 `PATH` 中。 这样就足够了。预热过程会在后台自动运行。如果想在某次运行中禁用它而不更改命令,可以设置 `CLAUDE_WARMER_DISABLE=1`。 参数调整(均为可选): | 标志 | 默认值 | 含义 | |---------------------|---------|--------------------------------------------| | `--idle` | `270` | 主 agent 必须空闲多少秒后才开始预热 | | `--interval` | `270` | 预热周期之间的间隔秒数 | | `--max-cycles` | `4` | 每次空闲片段的最大预热次数(设为 `auto` 为无限次) | | `--subagent-window` | `540` | subagent 被视为“仍在活动”的持续秒数 | ## 为什么你的缓存总会过期 Claude Code 的 prompt 缓存使用 **5 分钟的 TTL**。只要缓存保持有效,在每次交互中,你的整个对话历史都会以 **0.1x** 的输入价格从缓存中读取,而不是以全价重新发送。 如果在同一前缀上的请求之间超过 5 分钟,缓存就会过期。导致此时间差的主要原因不是你在思考,而是主 agent **被运行时间超过 5 分钟的 subagent 阻塞了**。subagent 具有不同的 system prompt 和工具集,因此它的请求具有*不同的*缓存前缀,永远不会刷新主 agent 的缓存。在 subagent 工作时,主 agent 的缓存历史会不受影响地变旧;超过 5 分钟后,缓存就会消失。当 subagent 返回时,主 agent 会以逐字节相同且仅追加的历史记录恢复,并发现其缓存丢失,从而被迫以 **1.25x** 的写入费率进行完全重新编码。 到那时,历史记录已经非常庞大,因此重新编码的成本很高:单次崩溃就会重新写入 200K 到 500K 个 token。根据大约 185 次本地会话的测量,这些重建约占总账单的 **22%**,这笔钱全花在了对刚刚还处于缓存中的内容进行重新编码上。 ## 工作原理 `claude-thermos` 通过一个小型本地反向代理启动 Claude Code(它将 `ANTHROPIC_BASE_URL` 指向一个 loopback 端口;所有流量仍然会发送到真实的 Anthropic API)。 1. **观察。** 代理会监视 `/v1/messages` 流量,并将其归类为会话和 *lineage*(谱系),一个 lineage 对应一个缓存前缀,以 model + 工具集 + system 文本为键。第一个携带工具的 lineage 是**主** agent;其余的是 subagent。 2. **检测危险窗口。** 当主 lineage 进入空闲状态 *且* 有 subagent 正在活跃运行时,主前缀就面临过期的风险。 3. **预热。** 在 5 分钟 TTL 的间隔内,它将主 agent 的最后一个真实请求作为一次**预热请求**重新发送:具有相同的可缓存前缀,但 `max_tokens: 1` 且不进行 streaming。生成的单个 token 会被丢弃;其核心在于 prefill,这会读取并刷新整个缓存前缀。预热请求会**直接**发送到 API,绝不经过代理,因此它们不会干扰真实流量。 4. **结果。** 当 subagent 完成时,主 agent 的缓存依然有效。它只需支付低廉的读取费用,而不是昂贵的完全重写。 每次预热只需花费一次缓存读取 (0.1x);而它阻止的每次重写在一个更大的前缀上本将花费一次写入费用 (1.25x),因此这笔交易对你非常有利。 ## 事件日志与节省情况 每个会话都会写入到: ``` ~/.claude-thermos/logs// ├── 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` | 以成功恢复(实际避免了一次重写)结束的“空闲且伴随 subagent”的片段 | | `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` 换算成美元,请**将其乘以你的 model 每个 input token 的价格**: ``` dollars saved ≈ net_savings × (input token price) ``` 例如,在输入价格为 $3 / 1M token 的情况下,如果 `net_savings` 为 `1_200_000`,则意味着该会话节省了约 `1_200_000 × $3 / 1_000_000 = $3.60`。
标签:AI辅助编程, Claude, CVE检测, Python, SOC Prime, 开发工具, 无后门, 缓存优化, 逆向工具