From 91c9fb7b1fb1591b1868dc29be4ef1be098d84ad Mon Sep 17 00:00:00 2001 From: Roy Tong Date: Wed, 9 Sep 2026 17:34:24 +0800 Subject: [PATCH 1/2] fix(gain): price re-sends at cache-read rate while the prefix stays cached --- README.md | 2 ++ scripts/offload-gain | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/README.md b/README.md index 7e7600c..841a842 100644 --- a/README.md +++ b/README.md @@ -509,6 +509,8 @@ default) is the most pessimistic reading. ### What the number does not include +**Prompt caching.** The money figures price every re-send of the context at full input rate. In Claude Code the conversation prefix is served from the prompt cache — first send a cache write at 1.25x input, re-sends cache reads at 0.1x — so a fast conversation pays far less to hold the same context. In the worked example above, `held 10 turns` at full price is $1.000, but with the cache holding it is about $0.215, which turns the 93% saving into roughly 78%. The flat figure is the honest one when turns are more than a cache TTL apart (5m by default) and the prefix is re-bought each turn; `gain -v` and `gain --json` print the cache-aware figure beside it. + **Follow-up verification.** After a worker answers, the model often runs a `grep` and a narrow re-read to check it. Those tokens do enter context and are not in the ledger, so real savings are somewhat lower than reported. diff --git a/scripts/offload-gain b/scripts/offload-gain index 96395ae..3dee09f 100755 --- a/scripts/offload-gain +++ b/scripts/offload-gain @@ -168,6 +168,14 @@ def main(): still_pay = tout * main_in * a.amp usd = would_have - worker_cost - still_pay usd_amp = usd + # Prompt-cache-aware companion: Claude Code keeps the prompt cached, so the + # first send is a cache WRITE (1.25x input) and every re-send a READ (0.1x). + # The flat figure above prices every re-send at full input — right when the + # cache expires between turns (5m TTL), an overstatement in a fast one. + hold_mult = 1.25 + 0.10 * (a.amp - 1) if a.amp >= 1 else a.amp + would_have_cached = (tin + b_tok_undelegated) * main_in * hold_mult + still_pay_cached = tout * main_in * hold_mult + usd_cached = would_have_cached - worker_cost - still_pay_cached by = collections.defaultdict(lambda: {"calls": 0, "in": 0, "out": 0, "secs": 0}) for r in rows: k = f'{r.get("provider","?")}/{r.get("model","?")}@{r.get("effort","?")}' @@ -180,6 +188,8 @@ def main(): "blocked_tokens_not_delegated": b_tok_undelegated, "worker_secs": secs_total, "usd_would_have_paid": round(would_have, 4), "usd_worker_cost": round(worker_cost, 4), "usd_still_paid": round(still_pay, 4), "usd_net_saved": round(usd, 4), + "usd_would_have_paid_cached": round(would_have_cached, 4), + "usd_saved_cached": round(usd_cached, 4), "blocks_dropped": dropped, "amp": a.amp, "usd_saved_amplified": round(usd_amp, 2), "by_worker": by, "ledger": LEDGER}, indent=2)) return @@ -292,6 +302,8 @@ def main(): print(f" What the cheap worker charged instead -${worker_cost:>9,.4f}") print(f" What you still pay for its answers -${still_pay:>9,.4f}") print(f" You saved ${usd:>9,.4f}") + print(f" With prompt caching (1.25x write, 0.1x re-reads)") + print(f" would have paid / saved ${would_have_cached:>9,.4f} / ${usd_cached:,.4f}") print(f" (your model at ${a.rate:g}/1M input, file held for {a.amp:g} turn{'s' if a.amp != 1 else ''})") if a.history: From 911ed2b38931e8704152785bc3894d6d4eb94db8 Mon Sep 17 00:00:00 2001 From: Roy Tong Date: Sat, 10 Oct 2026 08:33:31 +0800 Subject: [PATCH 2/2] ci: re-trigger checks (log expired on previous run)