llm-spendguard 0.2.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. llm_spendguard-0.2.9/LICENSE +21 -0
  2. llm_spendguard-0.2.9/PKG-INFO +449 -0
  3. llm_spendguard-0.2.9/README.md +406 -0
  4. llm_spendguard-0.2.9/pyproject.toml +63 -0
  5. llm_spendguard-0.2.9/setup.cfg +4 -0
  6. llm_spendguard-0.2.9/src/llm_spendguard.egg-info/PKG-INFO +449 -0
  7. llm_spendguard-0.2.9/src/llm_spendguard.egg-info/SOURCES.txt +137 -0
  8. llm_spendguard-0.2.9/src/llm_spendguard.egg-info/dependency_links.txt +1 -0
  9. llm_spendguard-0.2.9/src/llm_spendguard.egg-info/entry_points.txt +2 -0
  10. llm_spendguard-0.2.9/src/llm_spendguard.egg-info/requires.txt +27 -0
  11. llm_spendguard-0.2.9/src/llm_spendguard.egg-info/top_level.txt +1 -0
  12. llm_spendguard-0.2.9/src/spendguard/__init__.py +64 -0
  13. llm_spendguard-0.2.9/src/spendguard/__main__.py +8 -0
  14. llm_spendguard-0.2.9/src/spendguard/adapters.py +93 -0
  15. llm_spendguard-0.2.9/src/spendguard/advise.py +89 -0
  16. llm_spendguard-0.2.9/src/spendguard/advisor.py +260 -0
  17. llm_spendguard-0.2.9/src/spendguard/attribution.py +113 -0
  18. llm_spendguard-0.2.9/src/spendguard/audit.py +64 -0
  19. llm_spendguard-0.2.9/src/spendguard/backfill.py +108 -0
  20. llm_spendguard-0.2.9/src/spendguard/bootstrap.py +95 -0
  21. llm_spendguard-0.2.9/src/spendguard/brief.py +132 -0
  22. llm_spendguard-0.2.9/src/spendguard/budget.py +324 -0
  23. llm_spendguard-0.2.9/src/spendguard/bulkgate.py +365 -0
  24. llm_spendguard-0.2.9/src/spendguard/cacheaudit.py +156 -0
  25. llm_spendguard-0.2.9/src/spendguard/cachetest.py +164 -0
  26. llm_spendguard-0.2.9/src/spendguard/callio.py +305 -0
  27. llm_spendguard-0.2.9/src/spendguard/calls.py +298 -0
  28. llm_spendguard-0.2.9/src/spendguard/cascade.py +96 -0
  29. llm_spendguard-0.2.9/src/spendguard/chat.py +1108 -0
  30. llm_spendguard-0.2.9/src/spendguard/claudecode.py +474 -0
  31. llm_spendguard-0.2.9/src/spendguard/cli.py +256 -0
  32. llm_spendguard-0.2.9/src/spendguard/codex.py +284 -0
  33. llm_spendguard-0.2.9/src/spendguard/compare.py +53 -0
  34. llm_spendguard-0.2.9/src/spendguard/config.py +299 -0
  35. llm_spendguard-0.2.9/src/spendguard/config_schema.py +146 -0
  36. llm_spendguard-0.2.9/src/spendguard/conv.py +1007 -0
  37. llm_spendguard-0.2.9/src/spendguard/coverage.py +100 -0
  38. llm_spendguard-0.2.9/src/spendguard/emit.py +158 -0
  39. llm_spendguard-0.2.9/src/spendguard/equivalence.py +121 -0
  40. llm_spendguard-0.2.9/src/spendguard/estimate.py +124 -0
  41. llm_spendguard-0.2.9/src/spendguard/experiment.py +353 -0
  42. llm_spendguard-0.2.9/src/spendguard/gate.py +1012 -0
  43. llm_spendguard-0.2.9/src/spendguard/guard.py +80 -0
  44. llm_spendguard-0.2.9/src/spendguard/history.py +255 -0
  45. llm_spendguard-0.2.9/src/spendguard/learn.py +171 -0
  46. llm_spendguard-0.2.9/src/spendguard/ledger.py +524 -0
  47. llm_spendguard-0.2.9/src/spendguard/ledger_sync.py +529 -0
  48. llm_spendguard-0.2.9/src/spendguard/migrate_charges.py +95 -0
  49. llm_spendguard-0.2.9/src/spendguard/models.py +156 -0
  50. llm_spendguard-0.2.9/src/spendguard/notify.py +94 -0
  51. llm_spendguard-0.2.9/src/spendguard/prices.json +205 -0
  52. llm_spendguard-0.2.9/src/spendguard/pricing.py +252 -0
  53. llm_spendguard-0.2.9/src/spendguard/py.typed +0 -0
  54. llm_spendguard-0.2.9/src/spendguard/realtime_oracle.py +137 -0
  55. llm_spendguard-0.2.9/src/spendguard/receipt.py +781 -0
  56. llm_spendguard-0.2.9/src/spendguard/reconcile.py +190 -0
  57. llm_spendguard-0.2.9/src/spendguard/reconcile_anthropic.py +156 -0
  58. llm_spendguard-0.2.9/src/spendguard/reconcile_openai.py +117 -0
  59. llm_spendguard-0.2.9/src/spendguard/refresh.py +93 -0
  60. llm_spendguard-0.2.9/src/spendguard/remote.py +124 -0
  61. llm_spendguard-0.2.9/src/spendguard/report.py +263 -0
  62. llm_spendguard-0.2.9/src/spendguard/resources.py +894 -0
  63. llm_spendguard-0.2.9/src/spendguard/review.py +152 -0
  64. llm_spendguard-0.2.9/src/spendguard/saas.py +808 -0
  65. llm_spendguard-0.2.9/src/spendguard/schedule.py +95 -0
  66. llm_spendguard-0.2.9/src/spendguard/semcache.py +254 -0
  67. llm_spendguard-0.2.9/src/spendguard/setup.py +517 -0
  68. llm_spendguard-0.2.9/src/spendguard/share.py +143 -0
  69. llm_spendguard-0.2.9/src/spendguard/signal.py +141 -0
  70. llm_spendguard-0.2.9/src/spendguard/submit.py +124 -0
  71. llm_spendguard-0.2.9/src/spendguard/sync.py +89 -0
  72. llm_spendguard-0.2.9/src/spendguard/tag.py +65 -0
  73. llm_spendguard-0.2.9/src/spendguard/trust.py +112 -0
  74. llm_spendguard-0.2.9/src/spendguard/ui.py +32 -0
  75. llm_spendguard-0.2.9/src/spendguard/validate.py +157 -0
  76. llm_spendguard-0.2.9/src/spendguard/workdone.py +211 -0
  77. llm_spendguard-0.2.9/tests/test_adapters.py +239 -0
  78. llm_spendguard-0.2.9/tests/test_advise.py +149 -0
  79. llm_spendguard-0.2.9/tests/test_advisor.py +31 -0
  80. llm_spendguard-0.2.9/tests/test_attribution.py +47 -0
  81. llm_spendguard-0.2.9/tests/test_attribution_resolve.py +95 -0
  82. llm_spendguard-0.2.9/tests/test_audit.py +118 -0
  83. llm_spendguard-0.2.9/tests/test_backfill.py +177 -0
  84. llm_spendguard-0.2.9/tests/test_batch1.py +87 -0
  85. llm_spendguard-0.2.9/tests/test_bootstrap.py +128 -0
  86. llm_spendguard-0.2.9/tests/test_brief.py +18 -0
  87. llm_spendguard-0.2.9/tests/test_bulkgate.py +157 -0
  88. llm_spendguard-0.2.9/tests/test_cacheaudit.py +18 -0
  89. llm_spendguard-0.2.9/tests/test_cascade.py +35 -0
  90. llm_spendguard-0.2.9/tests/test_charges_migration.py +76 -0
  91. llm_spendguard-0.2.9/tests/test_chat_digest.py +63 -0
  92. llm_spendguard-0.2.9/tests/test_chat_value.py +54 -0
  93. llm_spendguard-0.2.9/tests/test_classify_evidence.py +82 -0
  94. llm_spendguard-0.2.9/tests/test_claudecode.py +108 -0
  95. llm_spendguard-0.2.9/tests/test_cli.py +29 -0
  96. llm_spendguard-0.2.9/tests/test_codex.py +111 -0
  97. llm_spendguard-0.2.9/tests/test_conv.py +32 -0
  98. llm_spendguard-0.2.9/tests/test_coverage.py +51 -0
  99. llm_spendguard-0.2.9/tests/test_emit_envelope.py +44 -0
  100. llm_spendguard-0.2.9/tests/test_equivalence.py +25 -0
  101. llm_spendguard-0.2.9/tests/test_experiment.py +26 -0
  102. llm_spendguard-0.2.9/tests/test_gate.py +138 -0
  103. llm_spendguard-0.2.9/tests/test_gate_cli.py +46 -0
  104. llm_spendguard-0.2.9/tests/test_gate_failclosed.py +90 -0
  105. llm_spendguard-0.2.9/tests/test_guard.py +56 -0
  106. llm_spendguard-0.2.9/tests/test_history.py +42 -0
  107. llm_spendguard-0.2.9/tests/test_learn.py +56 -0
  108. llm_spendguard-0.2.9/tests/test_ledger.py +35 -0
  109. llm_spendguard-0.2.9/tests/test_ledger_leak.py +76 -0
  110. llm_spendguard-0.2.9/tests/test_ledger_sync.py +344 -0
  111. llm_spendguard-0.2.9/tests/test_models.py +28 -0
  112. llm_spendguard-0.2.9/tests/test_pricing.py +86 -0
  113. llm_spendguard-0.2.9/tests/test_receipt.py +235 -0
  114. llm_spendguard-0.2.9/tests/test_reconcile.py +26 -0
  115. llm_spendguard-0.2.9/tests/test_reconcile_anthropic.py +172 -0
  116. llm_spendguard-0.2.9/tests/test_reconcile_core.py +126 -0
  117. llm_spendguard-0.2.9/tests/test_reconcile_e2e.py +124 -0
  118. llm_spendguard-0.2.9/tests/test_reconcile_openai.py +103 -0
  119. llm_spendguard-0.2.9/tests/test_reconcile_report.py +58 -0
  120. llm_spendguard-0.2.9/tests/test_remote.py +71 -0
  121. llm_spendguard-0.2.9/tests/test_report_build.py +43 -0
  122. llm_spendguard-0.2.9/tests/test_resources_gpu.py +168 -0
  123. llm_spendguard-0.2.9/tests/test_runner.py +67 -0
  124. llm_spendguard-0.2.9/tests/test_saas.py +90 -0
  125. llm_spendguard-0.2.9/tests/test_saas_payload.py +144 -0
  126. llm_spendguard-0.2.9/tests/test_saas_rollup.py +44 -0
  127. llm_spendguard-0.2.9/tests/test_schedule.py +124 -0
  128. llm_spendguard-0.2.9/tests/test_segment_attribution.py +167 -0
  129. llm_spendguard-0.2.9/tests/test_semcache.py +48 -0
  130. llm_spendguard-0.2.9/tests/test_setup.py +44 -0
  131. llm_spendguard-0.2.9/tests/test_signal.py +72 -0
  132. llm_spendguard-0.2.9/tests/test_spend_ledger.py +222 -0
  133. llm_spendguard-0.2.9/tests/test_stream_capture.py +120 -0
  134. llm_spendguard-0.2.9/tests/test_submit.py +56 -0
  135. llm_spendguard-0.2.9/tests/test_tag.py +68 -0
  136. llm_spendguard-0.2.9/tests/test_transcript_pure.py +54 -0
  137. llm_spendguard-0.2.9/tests/test_trust.py +46 -0
  138. llm_spendguard-0.2.9/tests/test_work_summary.py +45 -0
  139. llm_spendguard-0.2.9/tests/test_workdone.py +249 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ash Damle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,449 @@
1
+ Metadata-Version: 2.4
2
+ Name: llm-spendguard
3
+ Version: 0.2.9
4
+ Summary: A pre-spend GATE + learning advisor for LLM API cost: caps every call, prices from a verified table, and learns the cheapest config that keeps quality.
5
+ Author: Ash Damle
6
+ License: MIT
7
+ Project-URL: Homepage, https://llmspendguard.com
8
+ Project-URL: Documentation, https://docs.llmspendguard.com/
9
+ Project-URL: Repository, https://github.com/llmspendguard/llm-spendguard
10
+ Project-URL: Changelog, https://github.com/llmspendguard/llm-spendguard/blob/main/CHANGELOG.md
11
+ Keywords: llm,openai,anthropic,cost,budget,finops,tokens,prompt-caching,observability
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Topic :: System :: Monitoring
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: openai
22
+ Requires-Dist: openai>=1.0; extra == "openai"
23
+ Requires-Dist: tiktoken; extra == "openai"
24
+ Provides-Extra: anthropic
25
+ Requires-Dist: anthropic>=0.40; extra == "anthropic"
26
+ Provides-Extra: otel
27
+ Requires-Dist: opentelemetry-sdk; extra == "otel"
28
+ Provides-Extra: chat
29
+ Requires-Dist: cryptography>=3; extra == "chat"
30
+ Provides-Extra: all
31
+ Requires-Dist: openai>=1.0; extra == "all"
32
+ Requires-Dist: tiktoken; extra == "all"
33
+ Requires-Dist: anthropic>=0.40; extra == "all"
34
+ Requires-Dist: opentelemetry-sdk; extra == "all"
35
+ Requires-Dist: cryptography>=3; extra == "all"
36
+ Provides-Extra: dev
37
+ Requires-Dist: pytest>=7; extra == "dev"
38
+ Requires-Dist: build; extra == "dev"
39
+ Requires-Dist: twine; extra == "dev"
40
+ Requires-Dist: ruff>=0.6; extra == "dev"
41
+ Requires-Dist: coverage[toml]>=7; extra == "dev"
42
+ Dynamic: license-file
43
+
44
+ # llm-spendguard
45
+
46
+ A pre-spend **governor** for LLM API cost (OpenAI + Anthropic): it caps every call before the spend,
47
+ prices from a verified table, and **learns the cheapest config that still keeps quality** — then proves
48
+ and enforces it. Zero required dependencies; install is one line; it never breaks a job (fail-open).
49
+ Learn more at https://llmspendguard.com · **[Docs & quickstart →](https://docs.llmspendguard.com/)**
50
+
51
+ > 📘 **New here? Read the [Solution Specification](docs/SOLUTION-SPEC.md)** — the whole story end to end: why it
52
+ > exists, the value, the journey of a dollar (call → gate → ledger → reconcile → push), the design, and how it's
53
+ > tested, secured, and operated.
54
+
55
+ ## Why llm-spendguard?
56
+ Cost overruns don't announce themselves — they slip in silently: a hardcoded price that drifted from the
57
+ real rate, a forgotten model swap, under-batching that re-bills a shared prompt every request, a job
58
+ cancelled "to save money" that still bills for completed work, an ungated script in some other venv quietly
59
+ leaking spend. spendguard stops those before the spend (the gate hard-stops over a cap, prices from a
60
+ verified table, finds the leaks) **and** learns what was actually worth it — so "cheaper" never quietly
61
+ costs you quality.
62
+
63
+ Born from a real incident: a "cost-conscious" day meant to cost ~$33 actually cost **$149.76** — a price
64
+ constant was hardcoded wrong (GPT-5.5 at the old GPT-5 rate) and jobs ran 1 item/request (the shared prompt
65
+ re-billed every call). spendguard makes those mistakes impossible to ship silently — and goes further:
66
+ it reconstructs *what* you should do cheaper, and won't let "cheaper" cost you quality.
67
+
68
+ ## What it does
69
+ **Enforce → see → plan → prove → learn.**
70
+ - **gate** — overlay on the OpenAI/Anthropic SDKs (auto-installs via `sitecustomize.py`): estimates every
71
+ batch/real-time call, **hard-stops** over a cap (per-batch + cross-process daily/monthly) — then *asks* if interactive.
72
+ - **pricing** — one canonical, verifiable table (layered from LiteLLM + curated + override), cross-checked
73
+ vs OpenRouter; an `audit` fails CI if any code hardcodes a disagreeing price.
74
+ - **reconcile** — actual $ from real billed tokens; **`reconcile-ledger`** compares the local ledger to
75
+ provider billing to find **leaks** (ungoverned spend from a non-gated venv/repo).
76
+ - **report** — daily/weekly/monthly email with spend totals + a leak alert + the advisor's top learnings.
77
+ - **learning advisor** — a per-call cost+quality corpus → confidence-scored, lifecycle-tracked **insights**;
78
+ `brief` pre-fills a plan, `optimize` recommends the cheapest config that held quality, `experiment` proves
79
+ it (cost↓ **and** same-output), `promote` runs it and keeps the output. Cost-per-**good**-result, not per-token.
80
+ - **cost levers** — prompt-caching audit/test, semantic cache + batch dedup, cost-aware cascade routing.
81
+ - **observability** — emits OpenTelemetry GenAI-convention metrics+spans → Langfuse / Helicone / Phoenix / any OTLP backend.
82
+
83
+ The advisor's own LLM use is itself **caged** (a separate `caps.meta` budget, tagged `spendguard:*`, excluded
84
+ from the corpus it analyzes) so the governor can't overspend governing.
85
+
86
+ **Docs:** [Architecture + diagrams](docs/ARCHITECTURE.md) · [Use with Claude/Cursor](docs/USING-WITH-CLAUDE.md) · [Methodology](docs/README.md) · [Roadmap (teams/orgs/SaaS)](docs/ROADMAP.md) · [Module map](src/spendguard/README.md) · [Contributing](CONTRIBUTING.md) · [Changelog](CHANGELOG.md) · [Setup](SETUP.md)
87
+
88
+ **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 `/spend` (status) and `/spendguard-learn` (advisor) as slash-commands. See [Use with Claude](docs/USING-WITH-CLAUDE.md).
89
+ **Teams & orgs:** each user keeps their own ledger + sets their own caps (partner, not supervisor); opt-in roll-up for shared visibility + pooled learnings via the SaaS (separate repo). The client (this package) is **production-ready and fully standalone**. The team/org dashboard (a separate server) is **in development** — see [ROADMAP.md](docs/ROADMAP.md).
90
+
91
+ ## Quickstart
92
+
93
+ **A) Set up with Claude (recommended).** Point Claude Code / the desktop app at this repo and say:
94
+ > *Install spendguard from this repo and run the guided setup in `SETUP.md`.*
95
+
96
+ Or just run `spendguard init` — it reads the **config registry** (`src/spendguard/config_schema.py` — the
97
+ single source of truth for every setting, its default, valid options, and whether it's secret) and walks you
98
+ through caps, projects, and providers **conversationally**, one question at a time, then writes your config.
99
+ Pointed at this repo, Claude does the same end-to-end: installs the package, runs the interview off that same
100
+ registry, and wires up the gate. Details: [SETUP.md](SETUP.md).
101
+
102
+ **B) pip + code.**
103
+ ```
104
+ pip install llm-spendguard # once published to PyPI
105
+ # or, from a clone of this repo:
106
+ pip install -e .
107
+ ```
108
+ ```python
109
+ import spendguard # importing the guard now GATES every OpenAI/Anthropic call in this process
110
+ # spendguard.install(cap=75) # optional — set a per-batch cap (import already installed the gate)
111
+ ```
112
+ `import spendguard` auto-installs the gate (idempotent, fail-open), so `pip install` + `import` is enough — no more
113
+ silently-ungated spend. Knobs: `SPENDGUARD_NO_AUTOINSTALL=1` opts out; `SPENDGUARD_REQUIRE=1` makes the import
114
+ **fail-closed** (raises if an SDK is present but the gate can't enforce here — refuse loudly rather than spend
115
+ ungated). For a hard guarantee in a script, keep `spendguard.require()` at the top.
116
+
117
+ Or auto-install for every process in a venv — drop this in `sitecustomize.py`:
118
+ ```python
119
+ import spendguard; spendguard.install()
120
+ ```
121
+ Configure with `spendguard init` (interactive — or `init --quick` for non-interactive defaults) / `spendguard
122
+ config` (show current); see [Configuration](#configuration-prices-providers-models).
123
+
124
+ ## CLI — full command reference
125
+ ```
126
+ # enforce / control
127
+ spendguard status | on | off # kill switch (persistent flag)
128
+ spendguard doctor # is the gate ENFORCING in THIS interpreter? (+ ledger-leak check)
129
+ spendguard install-hook --venv <path> # gate every process in ANOTHER venv/repo (--uninstall to remove)
130
+ spendguard install-hook --user [--python P] # gate a python's per-USER site (system-python bypass; PEP668-safe, no pip)
131
+ spendguard install-rule [--global|--project DIR] # drop the spendguard rule into CLAUDE.md → every AI chat wires it in
132
+ spendguard install-skills # deploy /spend + /spendguard-learn as Claude slash-commands
133
+ spendguard coverage # across ALL pythons (3.11/3.14/…): which can call LLMs & which are GATED
134
+ # in code, fail-closed: import spendguard; spendguard.require() # refuses to run if NOT actually gated
135
+
136
+ # teams / orgs (client seam → future server repo, llmspendguard.com)
137
+ spendguard saas [status|ping|push|pull] # opt-in roll-up; partner not supervisor; private until you enable it
138
+
139
+ # see the money
140
+ spendguard receipt [--json|--line] # running today/7d/month tally; auto-emitted after every flow
141
+ spendguard report [--alert-threshold 150] [--email] # daily/weekly/monthly + ledger-leak alert + top learnings
142
+ spendguard reconcile openai|anthropic [--by-day] # actual billed batch spend from the provider
143
+ spendguard reconcile all # UNIFIED view: every source (LLM+GPU) via one account-anchored loop
144
+ spendguard reconcile-ledger [--since DATE] # local gate ledger vs provider billing → find LEAKS
145
+ spendguard calls [--intent X] # per-intent cost + good% + $/good (opt-in corpus)
146
+ spendguard estimate --items N --from-sample f.jsonl --packs 1,30
147
+ spendguard pricing | cross-check | check-prices | sync-prices # canonical table · OpenRouter drift · freshness · LiteLLM sync
148
+ spendguard audit [--ci] # fail if a script hardcodes a price ≠ the table
149
+
150
+ # plan / decide (the briefing + advisor loop)
151
+ spendguard brief --task "..." # "what we need to do" → pre-filled confirm-or-correct plan
152
+ spendguard advise [--intent X] [--plan M] # deterministic per-intent ranking by $/good (no spend)
153
+ spendguard optimize --intent X [--plan M] # caged LLM recommendation (cheapest config that holds quality)
154
+ spendguard models [show <model>] # per-model learnings, auto-applied (reasoning/cache/tokens)
155
+ spendguard insights list|export|import # living insights; opt-in scrubbed collective learning
156
+ spendguard backtest --as-of DATE # replay advise as of a past date
157
+
158
+ # prove / run cheaper (estimate-first, caged by caps.meta)
159
+ spendguard experiment --intent X --model M... [--semantic embed|rubric] [--run] # A/B cost↓ + same-output, graduated
160
+ spendguard promote --intent X --model M [--input chunk.jsonl] [--batch] [--run] # run the winner + KEEP output
161
+ spendguard cache-audit | cache-test --script f.py [--run] # prompt-caching: find + prove savings
162
+ spendguard cascade --ladder cheap,…,strong --intent X [--prompt …] --run # cheap→verify→escalate
163
+ spendguard cache-stats | dedup --input f.jsonl --out u.jsonl | dedup-populate # response cache + batch dedup
164
+
165
+ # work-done attribution (org → team × project), all sources
166
+ spendguard claude-code [show|sync|classify|work|story] # mine ~/.claude → Claude Code spend + work (incremental, classified)
167
+ spendguard chat [test|show|discover|classify|loop|work|story|sync|status|accept] # claude.ai chat adapter (OPT-IN, on-device, macOS)
168
+ spendguard resources [show|snapshot|sync|discover] # vast.ai GPU → org/team/project (discover [--agentic] recovers destroyed boxes)
169
+
170
+ # cold start / corpus
171
+ spendguard bootstrap [--repo] [--transcripts] # mine ALL history → corpus + insights (free, then estimate)
172
+ spendguard fetch-io [--cap 50] # recover real prompt+output from providers (free)
173
+ spendguard backfill [--intent-map …] # seed corpus + graph from the batch ledgers (free)
174
+ spendguard mine-history {intents,graph,git} [--apply] # reconstruct intents/edges from the repo (free)
175
+ spendguard mine-conv {index,synth} [--run] # mine session transcripts for the cost playbook
176
+ spendguard validate # re-check learnings vs the current corpus (lifecycle)
177
+
178
+ # setup
179
+ spendguard init | config # guided setup / show resolved config
180
+ spendguard schedule [--daily] [--remove] # install the OS-native scheduler (launchd/cron/schtasks)
181
+ ```
182
+
183
+ ### The workflow it's built around
184
+ **brief** (pre-filled plan) → **experiment** (prove the cheapest config that holds quality, graduated) →
185
+ **promote** (run it + keep the output) → the gate **enforces** caps → **reconcile-ledger** (catch leaks vs
186
+ provider billing) → **report** (daily email: totals + leak alert + top learnings) → **validate** (learnings
187
+ stay true as data grows) → those learnings feed the next **brief**.
188
+
189
+ ### Gate another repo
190
+ The gate auto-installs per venv via a `sitecustomize.py` hook. To gate another project:
191
+ ```
192
+ spendguard install-hook --venv /path/to/that-repo/.venv # pip-installs spendguard + writes the hook
193
+ ```
194
+ Then every process in that venv is gated (kill switch: `GATE_DISABLE=1` or `spendguard off`). Until a repo
195
+ is gated, its provider spend shows up in `reconcile-ledger` as a **leak** (billed but ungoverned).
196
+
197
+ ### Enforce the gate on remote / distributed compute (vast.ai, any SSH host)
198
+ The gate only governs the interpreter it's loaded in — a freshly-spun-up box's `python3` is **ungated** until it's
199
+ provisioned, so remote LLM scripts can spend silently. Make it structural — *gate at provision, verify before spend,
200
+ sync before teardown*:
201
+ ```
202
+ spendguard remote onstart # boot snippet → bake into the instance onstart (gates every python3)
203
+ spendguard remote verify --ssh "ssh -p PORT root@HOST -i KEY" # FAIL-CLOSED: exit≠0 if the box isn't ENFORCING → abort the launch
204
+ spendguard remote sync --ssh "ssh -p PORT root@HOST -i KEY" --project manga2anime # roll the box ledger up to the org (idempotent)
205
+ ```
206
+ On the box itself, an LLM script should also `import spendguard; spendguard.require()` (fail-closed in-process). Then
207
+ an ungated box can't spend: provisioning gates it, `verify` refuses to launch if it didn't, `require()` aborts the
208
+ script, and `sync` attributes the spend before the ephemeral box is destroyed.
209
+
210
+ ### Always-on spend tally (inline receipts)
211
+ After every gated **flow** — a `with spendguard.context(intent=…): …` block, a batch submit at the gate, or a CLI
212
+ command — spendguard prints a compact receipt so what it tracked is visible the moment it happens:
213
+ ```
214
+ spendguard ▸ loinc-typing · 42 calls · in 1.2M / out 300.0K · est $2.10 → actual $1.87 (−11%)
215
+ actual-$ (billed): today $81 · 7d $421 · month $2,015
216
+ est-value (plan, not billed) (as of 2026-06-23): today $1.4k · 7d $8.6k · month $20.2k
217
+ ```
218
+ The two axes are always kept **separate and never summed**: **actual-$** is money billed (the gate ledger, reconciles
219
+ to provider truth); **est-value** is coding-agent usage *value* — **Claude Code + claude.ai + Codex** (what it would
220
+ cost at API rates — covered by your plan), stamped per-source so they sum. It's per-FLOW (not per-call), costs nothing
221
+ (a local read, no LLM, no admin key), and the verbosity is `receipts.level` / `SPENDGUARD_RECEIPTS` =
222
+ `off | footer | flow | verbose` (default `flow`). Check it any time:
223
+ ```
224
+ spendguard receipt # the two-line tally · --line = one compact line · --json = machine-readable
225
+ ```
226
+
227
+ **Surface it in your Claude Code chat** — one command (idempotent; backs up + can `--remove`):
228
+ ```
229
+ spendguard install-receipts --host claude-code # adds a statusLine footer + a per-turn transcript notice
230
+ ```
231
+ It registers two guarded hook protocols in `~/.claude/settings.json`: `receipt --statusline` (always-on footer:
232
+ `cwd · model · ctx% · tally`) and `receipt --stop-hook` (a `systemMessage` line each turn). A hook can never block or
233
+ break a turn. Restart Claude Code to apply.
234
+
235
+ **Other hosts (Codex, editors, menubar).** Codex has no in-chat hook, but spendguard still TRACKS it
236
+ (`spendguard codex show` → channel=codex, billed=false). To surface the tally anywhere, point a **sink** at a file
237
+ and render that: `receipts.sinks` / `SPENDGUARD_RECEIPTS_SINK` = `stderr` (default) | `stdout` | `file:<path>`
238
+ (comma-separated). e.g. `spendguard config set receipts.sinks 'stderr,file:~/.spendguard/receipt.log'`, then
239
+ `tail -f ~/.spendguard/receipt.log` in a pane.
240
+
241
+ ## Knobs (env)
242
+ `GATE_CAP=<$>` (default 75) · `GATE_ALLOW=1` (permit one over-cap run) · `GATE_DISABLE=1` (off for one run)
243
+ · `GATE_RT_BUDGET=<$>` (per-process realtime ceiling, default 50) · `SPENDGUARD_HOME=<dir>` (data/flag/log location,
244
+ default `~/.spendguard`) · `SPENDGUARD_ENV=<path>` (.env for keys)
245
+ · `SPENDGUARD_RECEIPTS=off|footer|flow|verbose` (inline-receipt verbosity, default `flow`; also `receipts.level` in config.json)
246
+ · `SPENDGUARD_RECEIPTS_SINK=stderr|stdout|file:<path>` (where the auto-receipt goes, comma-sep; also `receipts.sinks`)
247
+ · `SPENDGUARD_CC_DIR` / `SPENDGUARD_CODEX_DIR` (override the Claude Code / Codex session dirs for est-value mining)
248
+ · `SPENDGUARD_NO_AUTOINSTALL=1` (don't gate on `import spendguard`) · `SPENDGUARD_REQUIRE=1` (fail-closed import —
249
+ raise if an SDK is present but the gate can't enforce) · `SPENDGUARD_ALLOW_ANON=1` (allow team push with a
250
+ non-email contributor; off by default so anon ids can't create phantom members)
251
+ · **batch-1 gate:** `GATE_BATCH1_MIN` (req count = "large", default 50) · `GATE_BATCH1_USD` (or ≥ this $, default 5)
252
+ · `GATE_BATCH1_DAYS` (look-back for a prior test, default 14) · `GATE_REQUIRE_BATCH1=1` (refuse, don't just warn) ·
253
+ `GATE_NO_BATCH1=1` (disable) — warns/refuses a large batch for an intent with no recent realtime/batch-1 test
254
+
255
+ ## Caps by resource class (LLM · compute · total)
256
+ Beyond the per-batch cap, spendguard tracks **cumulative** spend caps split by *what's spending* — so you can
257
+ set a tight LLM sub-limit under a higher overall ceiling. Each class has a `daily` and a `monthly` window
258
+ (`null` = off), stored in `config.json` under `caps`, with an env override for every one:
259
+
260
+ | Cap | Config (nested or flat) | Env | Behaviour |
261
+ |---|---|---|---|
262
+ | **LLM** daily / monthly | `caps.llm.{daily,monthly}` | `GATE_LLM_DAILY` · `GATE_LLM_MONTHLY` | **HARD — gate-enforced** (OpenAI + Anthropic calls hit the gate) |
263
+ | **Compute** daily / monthly | `caps.compute.{daily,monthly}` | `GATE_COMPUTE_DAILY` · `GATE_COMPUTE_MONTHLY` | **alert-only** (remote-compute / vast.ai launches don't pass through the gate — surfaced in the report + dashboard) |
264
+ | **Total** daily / monthly | `caps.total.{daily,monthly}` | `GATE_TOTAL_DAILY` · `GATE_TOTAL_MONTHLY` | overall ceiling (LLM + compute) |
265
+
266
+ These need `budget.backend = sqlite` (the cross-process ledger). The **legacy flat `caps.daily` / `caps.monthly`**
267
+ still work and are honored as the **total** ceiling. (Config storage accepts either the nested `caps.llm.daily`
268
+ or the flat `caps["llm.daily"]` form — see `config.class_cap` / `config_schema.py`.)
269
+
270
+ ## Pricing: layered, broad, low-maintenance
271
+ Prices load in layers, lowest→highest precedence — so you get **2,700+ models across all providers** for free,
272
+ your hand-verified rates always win, and you can override anything:
273
+
274
+ 1. **LiteLLM community dataset** (breadth + freshness) — `spendguard sync-prices` fetches
275
+ [LiteLLM's CI-maintained `model_prices_and_context_window.json`](https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json)
276
+ (~2,300 priced models, 80+ providers), validates it (refuses an empty/bad fetch), and caches it to
277
+ `~/.spendguard/litellm_prices.json`. Read from cache only — **no network at import**.
278
+ 2. **Curated `prices.json`** (shipped in the package) — your verified models (gpt-5.5, opus-4.8, …) override LiteLLM.
279
+ 3. **User override** — `~/.spendguard/prices.json` / `.yaml` / `$SPENDGUARD_PRICES` wins over everything.
280
+
281
+ If nothing loads, a built-in table in `pricing.py` is the final fallback (never breaks). Run `spendguard sync-prices`
282
+ once (and periodically) to refresh; that's the primary freshness mechanism — `check-prices`/`refresh-prices` are backups.
283
+
284
+ ## Configuration (prices, providers, models)
285
+ The curated/override files use this structure (`src/spendguard/prices.json`, `~/.spendguard/prices.json`, or `$SPENDGUARD_PRICES`):
286
+ ```json
287
+ { "_meta": {"verified": "2026-06-13", "source": "https://…", "stale_after_days": 45},
288
+ "providers": {
289
+ "openai": {"models": {"gpt-5.5": {"in_": 5.0, "out": 30.0, "cached_in": 0.5, "batch_in": 2.5, "batch_out": 15.0}}},
290
+ "anthropic": {"models": {"claude-opus-4-8": {"in_": 5.0, "out": 25.0, "cached_in": 0.5, "batch_in": 2.5, "batch_out": 12.5}}}
291
+ }}
292
+ ```
293
+ Add a provider/model by adding an entry. A user-override file only needs the models it changes. `spendguard providers`
294
+ lists what's configured. If the config can't load, the built-in table in `pricing.py` is the fallback (never breaks).
295
+
296
+ ## Pricing freshness
297
+ Prices drift, and a wrong price is the bug that started this project. `spendguard check-prices` shows the
298
+ `verified` date and flags the table **STALE** once it's older than `stale_after_days` (default 45); the daily
299
+ `spendguard report` prints the same warning. To refresh: re-verify against the `source` URL and bump the
300
+ `verified` date in `prices.json`. (A live fetch-and-diff against provider pricing pages is a planned addition.)
301
+
302
+ ## Real-time budget
303
+ Batch cost is known before submit; real-time isn't (output tokens). So the real-time layer **accounts actual
304
+ usage after each call** (and logs it, so real-time spend shows in `report`) and **hard-stops before the next call**
305
+ once per-process cumulative spend crosses `GATE_RT_BUDGET` (default $50) — the runaway-loop guard.
306
+
307
+ ## Email the report
308
+ `spendguard report --email` (or `--email-to addr`) emails the report so a scheduled run isn't missed.
309
+ Config lives in `~/.spendguard/email.json` (gitignored — safe for the secret) or env.
310
+
311
+ **Email needs a *gated* sender — this is universal, not a spendguard limitation.** Mail servers reject
312
+ unauthenticated senders, so every provider makes you prove ownership *somehow* before sending. Pick whichever
313
+ is least friction for you:
314
+
315
+ | Backend | What it takes (one-time) | DNS? | config |
316
+ |---|---|---|---|
317
+ | **Gmail / Workspace SMTP** | a 16-char app password (Google authenticates the send) | no | `{"host":"smtp.gmail.com","port":587,"user":"you@co.com","password":"<app pw>","to":"you@co.com"}` |
318
+ | **SendGrid (Twilio)** | "Single Sender Verification" — click a link in a confirm email | no | SMTP host `smtp.sendgrid.net`, or add a SendGrid backend |
319
+ | **Resend** | verify a domain (SPF/DKIM DNS records) for arbitrary recipients; or send only to your Resend signup email via `onboarding@resend.dev` | yes (for arbitrary recipients) | `{"provider":"resend","to":"you@co.com","from_":"reports@your-verified-domain","api_key":"re_…"}` |
320
+
321
+ **If it isn't configured, it gracefully no-ops** — `report` still prints (and the scheduled task still delivers in-app);
322
+ you'll just see `email not configured — skipping`. A *configured* backend that errors prints `EMAIL FAILED: <reason>`
323
+ (e.g. Resend's "verify a domain" message) without affecting the report. So leaving email unset is a fine default.
324
+
325
+ > **⚠️ Deliverability (shared senders land in spam).** Sending from a provider's *shared* address
326
+ > (e.g. Resend's `onboarding@resend.dev`) **sends fine but frequently lands in Gmail/Workspace Spam** — the
327
+ > domain has no alignment with yours, so receivers distrust it. The report *is* delivered; it's just filtered.
328
+ > Fixes, simplest first: **(1)** in Gmail, "Report as not spam" + a filter on the sender/subject set to
329
+ > *Never send to Spam*; **(2)** use **Gmail/Workspace SMTP** so it sends *as you* from inside Google (inbox, no DNS);
330
+ > **(3)** verify your own domain on the provider and send from it. Also note `api.resend.com` is behind Cloudflare,
331
+ > which 403s the default `urllib` User-Agent — spendguard sets one (don't strip it).
332
+
333
+ ## Compare models (cost-per-result)
334
+ Run one prompt across providers and table **cost + latency + output** — spendguard's angle is
335
+ *cost-per-result* (for deep evals, use promptfoo). Real calls, metered by the gate:
336
+ ```
337
+ spendguard compare --prompt "Summarize X in 3 bullets" \
338
+ --models gpt-5.5,claude-opus-4-8,gemini-2.5-flash,deepseek-chat,qwen-max --show
339
+ ```
340
+ Built-in providers: **openai, anthropic, gemini, deepseek, qwen** (most via their OpenAI-compatible
341
+ endpoints, so the gate already meters them). Keys resolve per provider from env / `~/.spendguard` / `./.env`
342
+ (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `DEEPSEEK_API_KEY`, `DASHSCOPE_API_KEY` for Qwen).
343
+ **Add another in one line:**
344
+ ```python
345
+ from spendguard.adapters import register_provider
346
+ register_provider("together", "https://api.together.xyz/v1", "TOGETHER_API_KEY", ("meta-llama", "mistralai"))
347
+ ```
348
+
349
+ ## Call context & cost-per-good-result (opt-in)
350
+ Beyond *cost*, spendguard can record per-call **context** to build a cost+**quality** corpus. Off by default
351
+ (it can store prompts/outputs — privacy). Enable `calls.enabled` (+ `calls.store_prompts` for snippets and the
352
+ implicit signal).
353
+ - **Tag intent:** `with spendguard.context(intent="loinc-typing", chain="run-42"): ...`
354
+ - **Quality is deferred** — you can't judge an output when it's made, but the *next* call reveals it:
355
+ - *automatic ("used"):* a later call in the same chain that reuses an output marks it good.
356
+ - *explicit / judge:* `spendguard.feedback(call_id, ok=True, source="judge")` — capture the verdicts you already produce.
357
+ - **`spendguard calls`** → per intent: calls, $, good%, and **$/good (cost-per-good-result)** — the efficiency metric.
358
+
359
+ Real-time calls are recorded automatically (caller, prompt/output snippets, latency); batches record job-level.
360
+
361
+ ### Smart attribution (a clean P&L, no manual bookkeeping)
362
+ Every charge is tagged on **two orthogonal dimensions**, so you can slice spend by either without bookkeeping:
363
+ - **WHO** — `org → team → contributor`, which **rolls up** the hierarchy. The contributor is set per install
364
+ (default: git `user.email`); the org/team is resolved server-side from the connection key.
365
+ - **WHAT** — `project · intent · resource` (the repo/work, the labeled task, and whether it's LLM or
366
+ remote-compute GPU).
367
+
368
+ Tagging is automatic: a project is inferred from the repo/cwd, refined by the call corpus's intent/caller and
369
+ the conversation that ran each batch; remote-compute rows route by instance label. The still-ambiguous
370
+ remainder can be resolved by a small, **capped, estimate-first** LLM pass (never auto-run). The result is a
371
+ clean P&L by team / project / intent with no manual entry. (Mechanism: `tag.py` cascade, `signal.py` per
372
+ project·intent·model roll-up, `conv.py` batch→conversation attribution, `saas.py` `org→team→user` push.)
373
+
374
+ ## Learning advisor — *recommend considering history* (Layer 1 deterministic · Layer 2 LLM)
375
+ - **`spendguard advise [--intent X] [--plan MODEL]`** — pure-SQL ranking of your corpus by `$/good` (or `$/M out`
376
+ when quality isn't labeled yet), confidence-weighted, with caveats. No LLM, no spend.
377
+ - **`spendguard backtest --as-of DATE`** — replays `advise` as of a past date (would it have caught known-good calls?).
378
+ - **`spendguard backfill`** — seeds the corpus + learning graph from your real batch ledgers (no spend).
379
+ - **Layer 2 (its own, *caged*, LLM use)** — every op is **estimate-only by default**; `--run` spends, and each paid
380
+ call is tagged `intent=spendguard:*` so it hits a **separate meta budget** (`caps.meta`, default **$2/day**), is kept
381
+ out of your workload budget, and is excluded from the corpus it analyzes:
382
+ - **`spendguard mine`** — synthesize confidence-scored **insights** + learning-graph nodes from the evidence (reasoner).
383
+ - **`spendguard optimize [--intent X] [--plan MODEL]`** — an actionable recommendation citing evidence + insights (reasoner).
384
+ - **`spendguard reconstruct`** — judge a bounded sample of recovered call I/O for quality → real `good%`/`$/good`.
385
+ - **`spendguard review`** — **practice audit**: judges whether usage was *smart*, not just what it cost. Assembles a
386
+ context bundle (cost + quality + token-ratio + sample I/O + linked chat notes) and emits **conditional** insights
387
+ (IF task_class/regime THEN action BECAUSE mechanism) — needs no ground truth, so it's robust where output-judging isn't.
388
+ - **Models are configurable:** `advisor.model` (reasoner, default Opus 4.8) · `advisor.judge_model` (judge, default
389
+ Haiku 4.5) — any priced model / provider. Run any op without `--run` to see the projected cost first.
390
+
391
+ ### Cold start, quality corpus, living insights, collective learning
392
+ - **`spendguard bootstrap`** — the cold-start process: mine **all** history (ledgers → intents → graph → provider I/O →
393
+ conversation) for free, then estimate the caged reasoning. One command, history → corpus → insights.
394
+ - **`spendguard fetch-io`** — recover the **real prompts+outputs** from the providers (OpenAI batch input/output files,
395
+ streamed with early-stop; Anthropic results within 29 days) into a bounded `call_io` sample. **Zero token cost.**
396
+ - **`spendguard validate`** — **living insights**: re-checks each learning against the current corpus and moves it through
397
+ its lifecycle (corroborated → `active` + confidence up; cited model gone / gap inverted → `refuted`/`superseded`). The
398
+ advisor weights by *current* confidence + status, so stale advice sinks as data grows.
399
+ - **`spendguard insights {list,export,import}`** — **collective learning, opt-in + scrubbed**. Export *abstracts* insights
400
+ into generalizable rules (keeps task_class/regime, model names, ratios; strips `$` amounts, intent names, evidence) and
401
+ **previews exactly what would leave**. Import brings community rules in as **low-trust priors** that must be locally
402
+ corroborated by `validate` before they sway the advisor.
403
+
404
+ > **On quality:** a cheap call that fails quality is wasted money, so cost-per-**good**-result is the metric. Two signals are
405
+ > trustworthy: **approach-quality** (`review` — needs no ground truth) and **outcome** (the conversation showing an output was
406
+ > used or redone). Judging output *correctness* in isolation is **not** reliable (an LLM can't verify a value it has no ground
407
+ > truth for) — spendguard quarantines such labels rather than trusting them.
408
+ - **Post-event mining (deterministic, zero spend)** — recover what the live recorder missed:
409
+ - **`spendguard mine-history {intents,graph,git}`** — reconstruct each batch's **intent** from repo artifacts
410
+ (`*batch_id*.json` + a size-bounded content scan of `data/`), add causal graph edges (`preceded`,
411
+ `derived_from`), and read git history for cost/fix signals. `--apply` writes; report-only otherwise.
412
+ - **`spendguard mine-conv {index,synth}`** — mine session transcripts for cost decisions. `index` is cached
413
+ (deterministic); `synth` is the caged reasoner turning the top decision snippets into `source='conversation'`
414
+ insights (estimate-first). Reconstructs your actual playbook (packing, never-cancel, price-basis errors, …).
415
+
416
+ ## Observability (feed your existing stack)
417
+ spendguard emits an event per gated call — it's the *enforcement* layer, not another dashboard; route the
418
+ events to whatever you already run. Three sinks, all optional, none ever block or break the gate:
419
+ - **In-process callback:** `spendguard.on_event(lambda e: log(e))`
420
+ - **Webhook:** `emit.webhook` in `~/.spendguard/config.json` or `$SPENDGUARD_WEBHOOK` — POSTs the event JSON (Slack, your collector, …)
421
+ - **OpenTelemetry:** `emit.otel: true` / `$SPENDGUARD_OTEL` — a `spendguard.cost_usd` counter (needs `opentelemetry-sdk`)
422
+
423
+ Event shape: `{ts, kind: batch|realtime, provider, model, cost, decision}`. Webhook/OTel run on a background
424
+ daemon thread (drop-if-flooded), so even high-volume real-time calls aren't slowed; callbacks run inline (keep them fast).
425
+
426
+ ## Extend to any SDK (zero required deps, fail-open)
427
+ spendguard ships with the OpenAI + Anthropic overlays, but the gate is generic — you can put **any** SDK under
428
+ it without adding a dependency:
429
+ 1. **Intercept it:** `spendguard.register(module_path, ClassName, method, gate_fn)` patches that SDK's call
430
+ method (e.g. `register("cohere", "Client", "chat", gate_fn)`). Write a small `gate_fn` that reads the request
431
+ shape and estimates cost; add the model's prices to the table (`prices.json` / your override).
432
+ 2. **Add an OpenAI-compatible provider in one line** (for `compare` + metering — most providers expose one):
433
+ `from spendguard.adapters import register_provider; register_provider("together", "https://api.together.xyz/v1", "TOGETHER_API_KEY", ("meta-llama", "mistralai"))`.
434
+ 3. **Emit anywhere:** route the per-call event to a webhook, OpenTelemetry, or an in-process callback
435
+ (`spendguard.on_event(...)`) — see [Observability](#observability-feed-your-existing-stack).
436
+
437
+ All of it is **fail-open** (an estimation/patch error logs and lets the call proceed) and needs **no required
438
+ dependencies** — the SDKs and OTel are optional extras.
439
+
440
+ ## Safety
441
+ Fail-**open**: any estimation or patch error logs a warning and lets the call proceed — the gate
442
+ never breaks a job by accident. Only the deliberate over-cap stop blocks. Disable instantly with
443
+ `spendguard off` (checked per-call, live) — and the kill switch is honored even if the gate itself errors.
444
+
445
+ ## Getting help
446
+ - **Website:** https://llmspendguard.com
447
+ - **Bugs / feature requests:** [GitHub Issues](https://github.com/llmspendguard/llm-spendguard/issues)
448
+ - **Questions / ideas / show-and-tell:** [GitHub Discussions](https://github.com/llmspendguard/llm-spendguard/discussions)
449
+ - **Contributing:** see [CONTRIBUTING.md](CONTRIBUTING.md).