llm-spendguard
Actively minimize and govern LLM spend before work runs — then prove the result against the provider's bill.
uvx --from llm-spendguard spendguard scan # 10 seconds. No key, no config, nothing leaves your machine.
That reads the coding-agent transcripts already on your disk and tells you what that work costs at API rates. Nothing is installed into your interpreter and nothing is uploaded — see what leaves your machine.
Then, when you want the guard rails, set up once:
pip install 'llm-spendguard[all]' && spendguard init --all # ONE command: caps + identity, auto-detect your $0 subscription lanes, and wire the gate, MCP tools, in-chat receipt & slash-commands
spendguard run -- python your_job.py # gate ONE command: cost estimate before submit + hard caps
spendguard reconcile all # your ledger vs the provider's actual bill, with the gap NAMED
spendguard submit-jobs jobs.jsonl # hand it a WHOLE job set + a goal; it plans the cheapest route and runs it
spendguard init is safe to re-run and works fully standalone (no account). init alone writes config and auto-detects
your $0 lanes; init --all also wires the gate, MCP tools, receipt, assistant rule and slash-commands (add --quick
for zero prompts). It ends on a status card + the one command that proves the path end-to-end.
Six things it does that the observability and gateway tools don't:
- Estimates a batch before you submit it — not after the tokens are billed.
- Refuses to price a model it doesn't know, loudly, instead of logging $0. (Elsewhere a $0 price can also silently exempt the call from your budget cap — an unknown model becomes an uncapped model.)
- Reconciles to the actual invoice and names the residual as ungoverned spend, with a published error rate — see ACCURACY.md.
- Learns the cheaper config and proves it's safe before adopting it (measure → test for output equivalence → adopt only if quality holds → apply → remember what failed).
- Plans a whole job set for you — the simplest contract there is: hand it the jobs and a goal (a budget, how urgent), and it picks the cheapest route per call — the Batch API, a subscription lane you already pay for, or the metered API — capability-matched to each call's structured-output needs, runs it, and returns. It estimates the whole set first and refuses before spending a cent over your budget. You never hand-tune which path each call takes; that's spendguard's job. See the whole-job contract.
- Manages constrained plans actively — sheds eligible work from a capped/overage lane to another ready $0 lane,
chooses the cheapest measured-good
(model, effort), substitutes an actually served model inside a bounded economic envelope, and delegates self-contained agentic work to codex/gemini/zai/kimi CLIs when the current plan is capped.
Zero required dependencies. Fail-open by design: a cost tool must never be the reason your job doesn't run. Learn more at https://llmspendguard.com · Docs & quickstart →
The global model reference is available without login at llmspendguard.com/catalog.
Its row as_of is the authored date the row's price/capability payload last changed; “snapshot ingested” is the
server's separate observation time. Local standalone operation remains complete; SaaS sync and dashboards are optional.
📘 New here? Read the Solution Specification — the whole story end to end: why it exists, the value, the journey of a dollar (call → gate → ledger → reconcile → push), the design, and how it's tested, secured, and operated.
What leaves your machine
Three modes. The default is local-only — you have to opt in to each of the other two.
| Mode | What leaves | How to turn it on |
|---|---|---|
| Local-only (default) | Nothing. scan, run, the gate, the ledger, reconcile, trust all work with no network except the providers' own free billing endpoints, which send nothing — they only read your usage. |
— (this is the default) |
| LLM attribution (opt-in) | Excerpts of local coding-agent transcripts go to a model provider so an LLM can classify which project/team spend belongs to. De-identified first (deid.engine). |
you run spendguard chat … / accounting --run |
| Team dashboard (opt-in) | Per-day, per-model roll-ups only: day, provider, model, kind, project, $ and token counts. No prompts, no outputs, no keys. | you configure saas.json and run saas sync |
Two things worth stating plainly, because they are the questions a careful reviewer asks first:
- Your API keys never leave the machine and are never sent to our server. Provider billing is read locally with your key; only the resulting numbers are pushed, and only if you connect a team dashboard.
- Coding-agent transcripts can contain secrets (a
.envechoed into a session, a pasted credential). That is exactly why LLM attribution is opt-in and de-identified rather than on by default — and whyscan, the command we ask you to run first, never sends them anywhere.
spendguard run --show prints the exact bootstrap that will execute in your process. Nothing is downloaded at
runtime, ever.
The chat extra reads your claude.ai session — here is exactly what it does and why
The claude.ai desktop app caches no conversations on disk, so the only way to account for chat usage is
claude.ai's own API, authenticated with the sessionKey cookie your browser already holds. That is why the
optional chat extra decrypts a cookie — and we understand how that phrase reads in 2026, so here is the whole
of it:
- It is opt-in twice: you must install the
chatextra and setchat.enabled(orSPENDGUARD_CHAT_ENABLED). Nothing happens by default;scan, the gate, the ledger and reconcile never touch it. - What it reads:
~/Library/Application Support/Claude/Cookies(the app's own Chromium cookie DB) and only the entriessessionKey,lastActiveOrg,cf_clearance. The AES key comes from your macOS Keychain, which prompts you for permission — spendguard cannot bypass that prompt, and you can deny it. - Where the token goes: nowhere. It authenticates requests to claude.ai from your machine. It is never logged,
never printed, never sent to our server, and never included in a roll-up. The decrypt happens in-process
(the
cryptographydep exists precisely so the key is not passed on a command line wherepscould see it). - What is cached: the cookie,
chmod 0600, with a TTL (chat.cookie_ttl_h, default 12h), cleared on any 401. Delete~/.spendguard/to purge it. - What is pushed: if — and only if — you connect a team dashboard, the same per-day roll-ups as everything else. No conversation text, no titles, no token.
- Don't want any of that? Skip the extra entirely. You keep the gate, the ledger, reconcile,
scanand Claude Code / Codex accounting; you lose only claude.ai chat value. Or set the session key yourself and no cookie is ever read.
Why it's worth having: chat is where a large share of plan-covered work actually happens, and leaving it out doesn't make the spend disappear — it makes your total quietly wrong, which is the one thing this project exists to prevent.
Why llm-spendguard?
Cost overruns don't announce themselves — they slip in silently: a hardcoded price that drifted from the real rate, a forgotten model swap, under-batching that re-bills a shared prompt every request, a job cancelled "to save money" that still bills for completed work, an ungated script in some other venv quietly leaking spend. spendguard stops those before the spend (the gate hard-stops over a cap, prices from a verified table, finds the leaks) and learns what was actually worth it — so "cheaper" never quietly costs you quality.
Born from a real incident: a "cost-conscious" day meant to cost ~$33 actually cost $149.76 — a price constant was hardcoded wrong (GPT-5.5 at the old GPT-5 rate) and jobs ran 1 item/request (the shared prompt re-billed every call). spendguard makes those mistakes impossible to ship silently — and goes further: it reconstructs what you should do cheaper, and won't let "cheaper" cost you quality.
What it does
Enforce → shed → route → delegate → save → prove → learn.
- gate — overlay on the OpenAI/Anthropic SDKs (armed by
spendguard run --, byimport spendguard, or venv-wide via the opt-in hook): estimates every batch/real-time call — chat, Responses API, Anthropic messages, and embeddings (realtime + batchinputbodies) — hard-stops over a cap (per-batch + cross-process daily/monthly) — then asks if interactive. - reliability (no 429 storms) — one admission chokepoint paces every fan under the vendor's real rpm/tpm (seeded from the catalog's published limits, refined by AIMD from live headers) and diverts an over-deadline burst to the Batch API — so a thousand-wide fan is paced or batched, never stormed. Durable, crash-resumable, exactly-once; the caller is always served with no 429 surfaced. See docs/PLAN_429_storm_to_batch.md.
- pricing — one canonical, verifiable table (layered from LiteLLM + curated + override), cross-checked
vs OpenRouter; an
auditfails CI if any code hardcodes a disagreeing price. - reconcile — actual $ from real billed tokens;
reconcile-ledgercompares the local ledger to provider billing to find leaks (ungoverned spend from a non-gated venv/repo). - report — daily/weekly/monthly email with spend totals + a leak alert + the advisor's top learnings.
- learning advisor — a per-call cost+quality corpus → confidence-scored, lifecycle-tracked insights;
briefpre-fills a plan,optimizerecommends the cheapest config that held quality,experimentproves it (cost↓ and same-output),promoteruns it and keeps the output. Cost-per-good-result, not per-token. - model-advisor (over MCP) — which model for this job? answered from your own measured usage:
adviseranks the models you've used for a job-type by $/good,recommendpicks the top-K on the cost×quality frontier at the precision the job needs (agentic quality-bar), andbakeoffmeasures untried models on a task sample so they enter the ranking. Exposed as a local MCP server —spendguard mcp(stdio, no SDK dep) — so any MCP client (Claude Code / Desktop, an IDE agent) can call it as tools. See Model-advisor. - active routing —
advisor.shed_lanesplus live cap/overage state moves eligible work to ready $0 lanes; best-value resolves the cheapest measured-good(model, effort); unserved requests get the best catalog-ranked served substitute within anchor rate ×1.25. Shed results distinguish avoided-overage $ from quota-preserved value. - warm shared MCP + fleet — one long-lived daemon per tool is shared by sessions;
spendguard fleetreports the deployed green version and stale consumers instead of leaving long-running processes silently behind. - cost levers — prompt-caching audit/test, semantic cache + batch dedup, cost-aware cascade routing.
- observability — emits OpenTelemetry GenAI-convention metrics+spans → Langfuse / Helicone / Phoenix / any OTLP backend.
The advisor's own LLM use is itself caged (a separate caps.meta budget, tagged spendguard:*, excluded
from the corpus it analyzes) so the governor can't overspend governing.
Cost reporting has three axes and never blends them: Total $X = $A API + $S Subscription (plan) + $R Remote (provider) :: Est sub value $V; guarded savings (measured + counterfactual) is a separate third axis, never added
to real dollars or estimated subscription value.
Delegate work to another plan
When the current plan is capped or on paid overage, spendguard delegate can hand a self-contained subtask to a
configured $0 or cheaper subscription plan. It is a two-hop flow: a plan-covered classifier decides whether the
task is a one-shot, an agentic workspace task, or needs the current Claude context; then the selected provider CLI
runs it. The default is a dry-run that returns the classification, route, separate real-$ and est-value estimate,
and estimated dollars saved versus measured Claude overage:
spendguard delegate "summarize this design" --files docs/design.md
spendguard delegate "fix and test this module" --provider codex --files src/app.py --yes
MCP clients can use the same engine with spendguard_delegate (execute=false by default). The honest boundary:
this offloads a SELF-CONTAINED subtask to another provider's plan; it does NOT reroute the calling agent's own
turns. Use it for bounded work you can describe completely; keep live-context conversational work in the current
agent. This implements the packaged assistant guidance in §8 of the installed spendguard:rule: batchable
comprehension goes through lane routing, while agentic subwork can be handed to another provider CLI when the current
plan is capped or on overage.
Docs: Architecture + diagrams · Diagrams · Durable queue + dedup · Plan shedding · Fleet / update propagation · Model-advisor (MCP) · Use with Claude/Cursor · Methodology · Roadmap (teams/orgs/SaaS) · Module map · Contributing · Changelog · Setup
Use with an AI assistant: spendguard install-rule --global writes a rule into CLAUDE.md so every Claude/Cursor conversation routes the LLM code it builds through spendguard — then spendguard install-skills adds the slash-commands: /spend (status), /spendguard-reconcile (trust the number), /spendguard-learn (advisor), /spendguard-prompts (the prompt lab), /spendguard-close (monthly close). See Use with Claude.
Teams & orgs: each user keeps their own ledger and local caps; the connected server also serves effective advisory
or enforced caps through GET /v1/policy, which the client pulls and enforces (an enforced org cap can only be tightened
locally). Opt-in roll-ups carry decisions, shed effectiveness, savings, and fleet/client status to the dashboard. The
client is production-ready and fully standalone. The team/org dashboard is live at
llmspendguard.com — see ROADMAP.md.
Quickstart
A) Set up with Claude (recommended). Point Claude Code / the desktop app at this repo and say:
Install spendguard from this repo and run the guided setup in
SETUP.md.
Or just run spendguard init — it reads the config registry (src/spendguard/config_schema.py — the
single source of truth for every setting, its default, valid options, and whether it's secret) and walks you
through caps, projects, and providers conversationally, one question at a time, then writes your config.
Pointed at this repo, Claude does the same end-to-end: installs the package, runs the interview off that same
registry, and wires up the gate. Details: SETUP.md.
B) pip + code.
pip install llm-spendguard
# or, from a clone of this repo:
pip install -e .
import spendguard # importing the guard now GATES every OpenAI/Anthropic call in this process
# spendguard.install(cap=75) # optional — set a per-batch cap (import already installed the gate)
import spendguard auto-installs the gate (idempotent, fail-open), so pip install + import is enough — no more
silently-ungated spend. Knobs: SPENDGUARD_NO_AUTOINSTALL=1 opts out; SPENDGUARD_REQUIRE=1 makes the import
fail-closed (raises if an SDK is present but the gate can't enforce here — refuse loudly rather than spend
ungated). For a hard guarantee in a script, keep spendguard.require() at the top.
To gate one command without touching your interpreter at all — the default, and the shape ddtrace-run and
opentelemetry-instrument use:
spendguard run -- python your_job.py
spendguard run --show # print the exact bootstrap that will execute, before you trust it
Nothing is written into site-packages and nothing persists once the process exits. To arm every process in a
venv you own, that's an explicit opt-in: spendguard install-hook --venv .venv (remove with --uninstall).
We don't default to writing startup hooks — see Architecture for why.
Configure with spendguard init (interactive — or init --quick for non-interactive defaults) / spendguard config (show current); see the configuration reference.
Where to go next
| Full CLI reference | Every command, grouped: enforce · see the money · plan · prove · attribute · setup. |
| Configuration & subsystem reference | Env knobs, caps by class, pricing layers, the learned estimator, the advisor, observability, de-identification. |
| Accuracy | How close these numbers are to your real invoice — and what we do not capture. |
| Architecture | The gate chokepoint and the extensibility seams. |
| Use with Claude / Cursor | Make every assistant session gated; the slash-commands. |
| Solution spec | The whole story end to end: why, value, the journey of a dollar, design, ops. |
| Setup · Changelog · Contributing · Security |
Safety
Fail-open: any estimation or patch error logs a warning and lets the call proceed — the gate
never breaks a job by accident. Only the deliberate over-cap stop blocks. Disable instantly with
spendguard off (checked per-call, live) — and the kill switch is honored even if the gate itself errors.
Getting help
- Website: https://llmspendguard.com
- Bugs / feature requests: GitHub Issues
- Questions / ideas / show-and-tell: GitHub Discussions
- Contributing: see CONTRIBUTING.md.
Metadata
Release files for llm-spendguard 0.12.19
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| llm_spendguard-0.12.19.tar.gz | 2.3 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| llm_spendguard-0.12.19-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.7 MB
Release files / llm_spendguard-0.12.19.tar.gz
| Download URL | llm_spendguard-0.12.19.tar.gz |
|---|---|
| Size | 2.3 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
63b66c6e4076ee7845a17135b742db37bba7cf9cc39073031ebe6e3d38e8d578
|
|
BLAKE2b-256 checksum How to use checksums |
bf499e4a333c0267c1a5cc51384c497c365ca7abe3027eeedd3cc38eb9cd0c2d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / llm_spendguard-0.12.19-py3-none-any.whl
| Download URL | llm_spendguard-0.12.19-py3-none-any.whl |
|---|---|
| Size | 1.4 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a1b515f8a7dc871cc900b27db65e823a692fdce1c30a901566ca2b59c0f32427
|
|
BLAKE2b-256 checksum How to use checksums |
41e3758571972df4604d5a5cd0846c0a611b85d0ca1b52e9366829f5f09816c9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log