cacheeconomics
You point it at logs you already have. It tells you what your prompt caching actually cost, which part of the prompt is wasting the money, and how much of that is recoverable.
It runs on your machine, opens no sockets, and will not print a dollar figure it cannot tie to an invoice.
Why this is not just adding up tokens
Your provider bills input tokens at four different rates: fresh input at 1x, a cache read at 0.1x, a five-minute cache write at 1.25x, a one-hour write at 2x. Your usage counters report the totals. They do not report which decisions put tokens in the expensive buckets, and that is where the money is.
Three specific ways it leaks:
A one-hour cache is only cheaper inside a band. It costs 2x to write where the short one costs 1.25x, so it wins only when your requests are more than five minutes and less than an hour apart. Faster than that, the cheap entry was still alive and you overpaid. Slower, both had expired and you overpaid. We measured this on live API calls: $0.190 for the 5m arm against $0.114 for the 1h arm on the same prefix, 40.2% cheaper, with all eight probes matching predictions written down before the run. The per-call receipts are kept with the source; ask and I will send them.
Below a model's minimum, a cache marker does nothing and nobody tells you. The
threshold is 512, 1024, 2048 or 4096 tokens depending on the model, and it does
not track model generation the way you would expect: claude-opus-5 is 512,
claude-opus-4-6 is 4096. Under it the provider quietly processes your request
uncached and reports cache_creation_input_tokens: 0. No error.
A write nobody reads is pure premium. You paid 1.25x or 2x for a prefix and then changed something at the top of it. Usage counters cannot tell you that happened, because "we rebuilt this instead of extending it" is a fact about prompt structure, not about token totals.
What you need to run it
One of these, in rough order of how little work they are for you:
Your Claude Code transcripts. Already on your disk, nothing to instrument. Run
cacheeconomics claude-code.
Your LiteLLM proxy logs. If you run LiteLLM you are already writing
StandardLoggingPayload records. That file is the input. This path needs a
recent enough LiteLLM to include prompt_tokens_details; without the cache
class split the tool will tell you it cannot read the rows rather than guess.
Request bodies your gateway already logs. Langfuse, Helicone and the LiteLLM proxy all keep full bodies. This is the first path that can answer "what would a different prompt layout have cost", because it can see prompt structure.
An instrumented capture, using the recorder in this package. Needs a small code change on your side and gives the strongest answers.
The file you have decides what the tool will claim. It works this out from the contents, not from what you tell it, so an export cannot ask for more confidence than it earns.
Install and run
pip install cacheeconomics
Python 3.9 or newer and nothing else. pip install "cacheeconomics[litellm]"
only if you are mounting the live proxy plugin.
The source repository is private. If you need to read the code or file something, contact the author address on this package.
# your own Claude Code usage, read locally
cacheeconomics claude-code --allow-unreconciled
# a trace, reconciled against the bill you actually received
cacheeconomics analyze trace.jsonl --invoice-usd 4820.16
# LiteLLM proxy logs, unchanged
cacheeconomics analyze standard_logging.jsonl --from litellm --invoice-usd 4820.16
# Bedrock or Vertex: the rate comes off your cloud bill, not from us
cacheeconomics analyze trace.jsonl --target-id amazon-bedrock/converse --effective-rate 1.10
# compare cache placement policies over the same requests
cacheeconomics bakeoff trace.jsonl --by-agent
# check a cache config before you ship it
cacheeconomics checks --prefix-tokens 900 --model claude-opus-5 --breakpoints 5
# what does this build actually know?
cacheeconomics registry
checks works as a CI gate. Exit 0 means everything passed, 2 means something
failed, and 3 means nothing failed but something could not be evaluated, usually
a model with no dated registry entry. That third code exists because "I did not
check" is not "this passed", and collapsing them gives you a green build where
the check that catches silently-ignored cache markers never ran. Exit 1 means the
tool itself broke.
What it refuses to tell you
This part matters more than the analysis, because it is what makes the analysis worth reading.
No invoice, no dollars. Every figure stays hidden until it reconciles against
money that actually left your account, within 5%. Without --invoice-usd the
report opens with FIGURES WITHHELD and prints [figure withheld] where each
number would be. --allow-unreconciled releases them for internal drafts, and it
is a flag you have to type rather than a default, so publishing an unreconciled
number is always somebody's decision.
Structure and usage have to agree. Spend comes from usage counters; structural findings come from segment sizes. If those two descriptions of the same prompt disagree by more than 5%, you still get the findings and you do not get their dollar figures.
Coverage is measured in money, not rows. A structural claim needs to cover 90% of your billed tokens before a figure attaches to it. Nine small structured requests next to one huge unstructured one is 90% of rows and can be 8% of the bill, and a recommendation costed from 8% of your spend does not describe your workload.
Bedrock and Vertex are not priced here. AWS and Google operate and invoice those,
so the rates are not ours to publish and we do not borrow Anthropic's. Pass
--effective-rate from your own cloud bill, which is the better number anyway:
partner rates vary by region and endpoint type, and nobody at your volume pays
list.
Every number says what kind of number it is. Measured means observed in usage fields. Modeled means projected, always as a range, with the pessimistic end as the headline. Verified means observed in production after a change shipped. Nothing gets promoted between them quietly.
Privacy
Everything runs locally. Nothing in the package opens a socket, the registry is packaged data, and every input is a path on your disk.
Prompt text is optional. Hashes, structure and token counts are enough for every finding here.
Segment identifiers are keyed HMAC-SHA-256 rather than bare digests, because a
bare digest of a short policy line is confirmable by anyone holding a guess.
There is deliberately no --key flag: argv shows up in ps and lands in shell
history, so the key comes from CACHEECONOMICS_HMAC_KEY or a --key-file whose
permissions get checked before it is read.
Keying and scoping are different things, and the difference matters on a shared
gateway. A key stops someone guessing your content. It does nothing about
equality, because equality is the whole job of an identifier: under one key,
identical text produces an identical id no matter who sent it. So identity is
scoped to the tenant as well. Pass --tenant on a multi-tenant export and two
tenants sending the same policy block stop sharing an id. That is also the
arithmetically correct answer, since caches are isolated per tenant and those two
requests could never have shared a cache entry.
What is in here
harness/cacheeconomics/
registry.py dated capability and pricing registry, refuses to guess
cost.py the four token classes, date-effective pricing
money.py a dollar figure that cannot be printed until it is released
trace.py ingest, tiering, reconciliation
analyzer.py the finding rules (EFF-1, VOL-1, MIN-1, TTL-1, FAN-1, ...)
report.py text and self-contained HTML
simulate.py replay a trace against a modelled cache; the policy bake-off
allocate.py placement policies, including a model of LiteLLM auto-injection
tiers.py the DP tier allocator and an exact evaluator that agrees with it
monitor.py runtime diagnostics, bounded state
plugin.py a gateway plugin that decides inside the request path
recorder.py instrumented capture
segment.py the single traversal of a request body
cli.py the commands above
data/ providers.json, pricing.json
The measurement work behind the numbers above lives in the source repository rather than in the installed package: the live TTL experiment and its per-call receipts, the landscape review with provenance pinned by commit and SHA-256, and the findings filed against OpenHands, SWE-agent and browser-use. Ask if you want them.
Eight provider surfaces are described. The recorded rates are Anthropic
first-party list prices, and the table names which surfaces they are valid for:
anthropic/direct and anthropic/claude-platform-on-aws. A test fails if a
model is ever added there without a dated rate.
The scope is default-deny, so a surface earns those rates by being named rather than by being absent from an exclusion list. The alternative is how this went wrong once already: before the scope existed, a Bedrock trace priced straight off the Anthropic table and produced a confident total no AWS bill would match.
Running it live
plugin.py can sit inside a LiteLLM proxy and place cache markers on outgoing
requests. It observes by default and only mutates when you ask.
The observing half is tested here. The mutating half is not: nothing in this
repository has watched a real LiteLLM proxy forward one of these requests, so we
do not know whether cache_control on a message content block survives LiteLLM's
translation to the Anthropic wire format. If it gets stripped you would get churn
and no cache writes while the plugin's own counters cheerfully reported
placements that never arrived. That is why mutate defaults to False and why
this paragraph exists.
Testing
1011 tests, no dependencies beyond pytest, run in the source repository. CI runs them on Python 3.9 and 3.13 with only pytest installed, so an accidental dependency fails the build rather than shipping to you.
License
Apache-2.0. Copyright 2026 KCG Consulting LLC.
Metadata
Release files for cacheeconomics 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cacheeconomics-0.2.0.tar.gz | 190.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cacheeconomics-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 393.4 kB
Release files / cacheeconomics-0.2.0.tar.gz
| Download URL | cacheeconomics-0.2.0.tar.gz |
|---|---|
| Size | 190.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e2a8182fcd340d476dfbd270827093bf02e0761032106ee9f4b5992de929d39e
|
|
BLAKE2b-256 checksum How to use checksums |
433f32c5d9fc40e966101c57a21b63782a8b16e4f128601c754498a453ad675c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / cacheeconomics-0.2.0-py3-none-any.whl
| Download URL | cacheeconomics-0.2.0-py3-none-any.whl |
|---|---|
| Size | 202.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
dfaca77f109012405be450115f04933c63de0c99303db70c6f295dba446bf19a
|
|
BLAKE2b-256 checksum How to use checksums |
1d30b2fd75199af8f764b5b172d9ad167def909ec448ed66c82ab7dcff14d553
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|