breakerbox 0.1.0__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 (43) hide show
  1. breakerbox-0.1.0/.gitignore +24 -0
  2. breakerbox-0.1.0/LICENSE +21 -0
  3. breakerbox-0.1.0/PKG-INFO +278 -0
  4. breakerbox-0.1.0/README.md +256 -0
  5. breakerbox-0.1.0/pyproject.toml +53 -0
  6. breakerbox-0.1.0/src/breakerbox/__init__.py +25 -0
  7. breakerbox-0.1.0/src/breakerbox/cli.py +64 -0
  8. breakerbox-0.1.0/src/breakerbox/codegen.py +183 -0
  9. breakerbox-0.1.0/src/breakerbox/control.py +63 -0
  10. breakerbox-0.1.0/src/breakerbox/events.py +60 -0
  11. breakerbox-0.1.0/src/breakerbox/graph.example.json +28 -0
  12. breakerbox-0.1.0/src/breakerbox/graph.schema.json +55 -0
  13. breakerbox-0.1.0/src/breakerbox/graphspec.py +295 -0
  14. breakerbox-0.1.0/src/breakerbox/guard.py +499 -0
  15. breakerbox-0.1.0/src/breakerbox/ledger.py +220 -0
  16. breakerbox-0.1.0/src/breakerbox/meter.py +90 -0
  17. breakerbox-0.1.0/src/breakerbox/prices.json +8715 -0
  18. breakerbox-0.1.0/src/breakerbox/pricing.py +110 -0
  19. breakerbox-0.1.0/src/breakerbox/pricing_update.py +97 -0
  20. breakerbox-0.1.0/src/breakerbox/report/__init__.py +0 -0
  21. breakerbox-0.1.0/src/breakerbox/report/generate.py +161 -0
  22. breakerbox-0.1.0/src/breakerbox/report/template.html.j2 +96 -0
  23. breakerbox-0.1.0/src/breakerbox/sink.py +69 -0
  24. breakerbox-0.1.0/src/breakerbox/tripwire.py +89 -0
  25. breakerbox-0.1.0/tests/fixtures/graphspec/linear.json +23 -0
  26. breakerbox-0.1.0/tests/fixtures/graphspec/linear.py +43 -0
  27. breakerbox-0.1.0/tests/fixtures/graphspec/over_allocation.errors.json +3 -0
  28. breakerbox-0.1.0/tests/fixtures/graphspec/over_allocation.json +15 -0
  29. breakerbox-0.1.0/tests/fixtures/graphspec/router_cycle.json +28 -0
  30. breakerbox-0.1.0/tests/fixtures/graphspec/router_cycle.py +62 -0
  31. breakerbox-0.1.0/tests/fixtures/graphspec/with_code.json +20 -0
  32. breakerbox-0.1.0/tests/fixtures/graphspec/with_code.py +41 -0
  33. breakerbox-0.1.0/tests/test_codegen.py +107 -0
  34. breakerbox-0.1.0/tests/test_control_integration.py +89 -0
  35. breakerbox-0.1.0/tests/test_graphspec.py +138 -0
  36. breakerbox-0.1.0/tests/test_guard_integration.py +178 -0
  37. breakerbox-0.1.0/tests/test_ledger.py +197 -0
  38. breakerbox-0.1.0/tests/test_meter.py +42 -0
  39. breakerbox-0.1.0/tests/test_pricing.py +102 -0
  40. breakerbox-0.1.0/tests/test_pricing_update.py +43 -0
  41. breakerbox-0.1.0/tests/test_report.py +101 -0
  42. breakerbox-0.1.0/tests/test_sink_integration.py +80 -0
  43. breakerbox-0.1.0/tests/test_tripwire.py +57 -0
@@ -0,0 +1,24 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .venv/
8
+ venv/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .hypothesis/
12
+ breakerbox_reports/
13
+ examples/runaway_demo/reports/
14
+ *.html
15
+ !src/breakerbox/report/*.html.j2
16
+ !examples/runaway_demo/sample_receipt.html
17
+ !cloud/marketing/public/reel/reel.html
18
+ .DS_Store
19
+
20
+ # local cloud secrets (never commit)
21
+ cloud/.secrets.local
22
+
23
+ # supabase CLI temp
24
+ cloud/supabase/.temp/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Amit Cohen
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,278 @@
1
+ Metadata-Version: 2.4
2
+ Name: breakerbox
3
+ Version: 0.1.0
4
+ Summary: Circuit breaker + hierarchical cost budgeting for AI agent workflows
5
+ Project-URL: Homepage, https://github.com/Amitcoh1/agentbreaker
6
+ Author-email: Amit Cohen <amit.cellebrite@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: agents,budget,circuit-breaker,cost,langgraph,llm
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.11
13
+ Requires-Dist: jinja2>=3.1
14
+ Requires-Dist: langchain-core>=0.3
15
+ Requires-Dist: langgraph>=0.2
16
+ Requires-Dist: tiktoken>=0.7
17
+ Provides-Extra: dev
18
+ Requires-Dist: hypothesis>=6; extra == 'dev'
19
+ Requires-Dist: pytest>=8; extra == 'dev'
20
+ Requires-Dist: ruff>=0.6; extra == 'dev'
21
+ Description-Content-Type: text/markdown
22
+
23
+ <p align="center">
24
+ <picture>
25
+ <source media="(prefers-color-scheme: dark)" srcset="docs/brand/logo-dark.svg">
26
+ <img alt="Breakerbox" src="docs/brand/logo-light.svg" width="360">
27
+ </picture>
28
+ </p>
29
+
30
+ <p align="center"><strong>The visual agent builder that can't be hacked into — because there's nothing to hack.</strong></p>
31
+
32
+ <p align="center">No server execution&nbsp;·&nbsp;No stored keys&nbsp;·&nbsp;Codegen only</p>
33
+
34
+ <p align="center">
35
+ <a href="https://github.com/Amitcoh1/agentbreaker/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Amitcoh1/agentbreaker/actions/workflows/ci.yml/badge.svg"></a>
36
+ <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-1f2328.svg"></a>
37
+ <img alt="Python 3.11+" src="https://img.shields.io/badge/python-3.11%2B-1f2328.svg">
38
+ <a href="https://github.com/Amitcoh1/agentbreaker/stargazers"><img alt="Stars" src="https://img.shields.io/github/stars/Amitcoh1/agentbreaker?color=b8860b"></a>
39
+ </p>
40
+
41
+ <p align="center">
42
+ <img src="docs/brand/shots/builder.png" alt="The Breakerbox builder: a graph canvas, a live Budget Tree, and guard config" width="820">
43
+ </p>
44
+
45
+ Prototype anywhere; ship with **Breakerbox**. You draw a workflow on a canvas, and it generates
46
+ plain, editable LangGraph **Python** you download and run yourself — wrapped in `guard()`, a
47
+ hierarchical dollar budget that stops runaway loops at a hop boundary and writes a receipt of
48
+ exactly where the money went. Three things make it different:
49
+
50
+ 1. **Zero attack surface — codegen only.** No endpoint ever executes your flows; no provider
51
+ key is ever stored or transmitted. There's nothing on our side to compromise. (See
52
+ [Why no Run button?](#why-no-run-button))
53
+ 2. **Budget-first.** The only builder where hierarchical budget escrow, trip rules, and
54
+ side-effect tagging are part of the canvas itself.
55
+ 3. **Code you own.** The output is readable, hand-editable Python — scaffolding, not a walled
56
+ garden.
57
+
58
+ > **Breakerbox** is the product; the Python package is **`breakerbox`** (`pip install
59
+ > breakerbox`). The import, CLI, and API names are unchanged.
60
+
61
+ ## Contents
62
+
63
+ - [Quickstart](#quickstart)
64
+ - [The demo](#the-demo)
65
+ - [What it actually does](#what-it-actually-does)
66
+ - [Build it visually](#build-it-visually)
67
+ - [Why no Run button?](#why-no-run-button)
68
+ - [How it compares](#how-it-compares-facts-only)
69
+ - [Notes & limitations](#notes--limitations-read-before-you-rely-on-it)
70
+ - [Cloud dashboard (optional)](#cloud-dashboard-optional)
71
+ - [Roadmap](#roadmap)
72
+ - [Contributing](#contributing)
73
+ - [License](#license)
74
+
75
+ ## Quickstart
76
+
77
+ ```bash
78
+ pip install breakerbox
79
+ ```
80
+
81
+ Wrap the compiled LangGraph app you already have — the call site doesn't change:
82
+
83
+ ```python
84
+ from breakerbox import guard
85
+
86
+ app = guard(
87
+ my_langgraph_app,
88
+ budget_usd=5.00, # hard ceiling for the whole workflow DAG
89
+ max_hops=50, # total model/tool steps across all branches
90
+ ttl_seconds=600, # wall-clock limit
91
+ velocity_usd_per_min=2.0, # trip if the burn rate spikes
92
+ on_trip="pause", # "pause" | "kill"
93
+ )
94
+
95
+ result = app.invoke(inputs) # the same call you already make
96
+ ```
97
+
98
+ Every model and tool dispatch inside the graph — including sub-agents and subgraphs —
99
+ inherits one real-dollar budget. When a limit trips, the workflow stops **at the next hop
100
+ boundary** (never mid-call) and drops a shareable HTML receipt. No proxy, no Docker, no
101
+ gateway to run: enforcement lives *inside* your process, so there's no direct-call bypass.
102
+
103
+ Prefer to start on the canvas? Open the builder, draw the graph, and let it write this for you:
104
+
105
+ ```bash
106
+ cd cloud/dashboard && npm install && npm run dev # http://localhost:3000/builder
107
+ ```
108
+
109
+ ## The demo
110
+
111
+ A broken retry loop that never drops its context, so each hop costs more than the last
112
+ ([`examples/runaway_demo`](examples/runaway_demo)) — no API keys required:
113
+
114
+ ```
115
+ ==============================================================
116
+ UNGUARDED runaway : ran 60 hops, spent $12.63
117
+ GUARDED : killed early, spent $0.82 (budget $0.90)
118
+ AVERTED : $11.81
119
+ ==============================================================
120
+ ```
121
+
122
+ It stops **strictly under budget** — because the model declares a real `max_tokens`, the
123
+ reserve estimate is a true upper bound, so the call that would cross the line is blocked
124
+ before it runs. Every run also writes a self-contained `report.html`:
125
+
126
+ ```
127
+ ────────────────────────────────────────────────────────
128
+ breakerbox receipt · killed (budget)
129
+ stopped at $0.8157 budget $0.9000 hops 13
130
+ projected (naive linear extrapolation, likely an underestimate): $3.13
131
+ ────────────────────────────────────────────────────────
132
+ ```
133
+
134
+ (See [`sample_receipt.html`](examples/runaway_demo/sample_receipt.html) for the full HTML.)
135
+
136
+ ## What it actually does
137
+
138
+ - **Hierarchical budget escrow.** Not a flat session counter. A parent sub-allocates budget
139
+ to child agents; a child can never spend beyond its allocation; a parent's remaining is
140
+ `budget − Σ(child allocations) − own spend`. Accounting is reserve → execute → reconcile
141
+ (a credit-card hold), so parallel branches can't race past the ceiling. Concurrency-safe
142
+ and property-tested (`Σ spent + Σ reserved ≤ root budget`, always).
143
+ - **Graceful trip actions.**
144
+ - `pause` — checkpoints via LangGraph's native checkpointer and raises `BudgetPaused`;
145
+ `app.resume(checkpoint_id, extra_budget_usd=...)` continues from where it stopped.
146
+ - `kill` — stops, finalizes the receipt, raises `BudgetKilled` listing which
147
+ **side-effecting** tools already fired (so you can compensate).
148
+ - **Self-metering.** Counts input tokens locally (tiktoken) and meters streamed output
149
+ chunks, then reconciles against the provider's reported usage and flags discrepancies —
150
+ never trusts a single `usage` field, never meters an unknown model as `$0`. If a call ran
151
+ without `max_tokens` and the cap landed one hop late, the receipt flags that hop explicitly.
152
+ - **The receipt.** Terminal summary + single-file `report.html` (inline CSS/SVG, no JS, no
153
+ external assets) + JSON. Leads with the indisputable number — **stopped at $Y, budget $Z** —
154
+ with the projection as clearly-labelled fine print.
155
+
156
+ ## Build it visually
157
+
158
+ [`cloud/dashboard/app/builder`](cloud/dashboard/app/builder) is a drag-and-drop canvas
159
+ (model / tool / router / start / end nodes) with a live **Budget Tree** — root budget →
160
+ per-node allocations → unallocated remainder. Over-allocate and it turns red and **blocks
161
+ export**. Hit **Generate** and you get the guarded Python above, ready to copy or download.
162
+ Everything runs in your browser; the spec → Python codegen is shared with the `breakerbox
163
+ build spec.json` CLI and locked to it by golden-fixture tests (Python and TS, enforced in CI).
164
+
165
+ **AI suggest (bring your own key).** Any model/tool/router node has an optional "Suggest code"
166
+ helper: describe the step, and it drafts a Python body for you to copy in. It calls the model
167
+ **directly from your browser with your own API key** — the key is stored only in your browser
168
+ and sent only to the provider, never to a Breakerbox server (there is no server in this path).
169
+ Anthropic supports browser-direct calls; OpenAI blocks browser CORS, so for OpenAI you point the
170
+ base URL at your own CORS-enabled proxy. The suggestion is copy-paste scaffolding — it's never
171
+ written into the saved graph, so codegen stays deterministic.
172
+
173
+ ## Why no Run button?
174
+
175
+ Server-side flow builders that run your graphs and hold your provider keys have been a
176
+ repeated remote-code-execution target. In Langflow (the category leader, ~100k+ GitHub
177
+ stars, IBM/DataStax-backed) the pattern is well documented and public:
178
+
179
+ - **CVE-2025-3248** (CVSS 9.8) — unauthenticated RCE via a code-validation endpoint that
180
+ passed user input to `exec()`; on CISA's KEV list, used to deploy the Flodrix botnet.
181
+ - **CVE-2025-34291** (CVSS 9.4) — an account-takeover chain that also **exfiltrates the API
182
+ keys stored in a workspace**; on CISA KEV, used by the MuddyWater APT for initial access.
183
+ - **CVE-2026-33017** (CVSS 9.8) — unauthenticated RCE via the public flow-build endpoint;
184
+ on CISA KEV, weaponized within ~20 hours of disclosure to drop cryptominers.
185
+ - **CVE-2026-5027** (CVSS 8.8) — path-traversal RCE via file upload, with ~7,000 exposed
186
+ instances observed under active exploitation.
187
+
188
+ This isn't a knock on Langflow's product — it's an architectural fact: **a server that runs
189
+ your flows and holds your keys is a high-value target.** Breakerbox removes the target. The
190
+ canvas only ever produces a Python *string* you run yourself, and your API keys live in your
191
+ own environment — never in a dashboard, database, or edge function. There's no endpoint to
192
+ exploit because no endpoint executes anything. That's the trade: you give up one-click cloud
193
+ runs, and in exchange there is nothing to breach. **Prototype in a tool like Langflow if you
194
+ like; ship the production-safe version here.**
195
+
196
+ ## How it compares (facts only)
197
+
198
+ | | Langflow | LiteLLM budgets | Breakerbox |
199
+ |---|:---:|:---:|:---:|
200
+ | Visual graph building | ✅ rich | — | ✅ budget-first |
201
+ | Server executes your flows | ✅ *(attack surface)* | n/a | ❌ by design |
202
+ | Stores your provider keys | ✅ | ✅ *(proxy)* | ❌ never |
203
+ | Hierarchical per-agent dollar escrow | — | — *(flat session)* | ✅ |
204
+ | Graceful pause/resume at a hop boundary | — | — *(hard error)* | ✅ |
205
+ | Catches a runaway run *under* the org ceiling | — | — *(never fires)* | ✅ |
206
+ | Output is plain, editable Python | partial *(export)* | n/a | ✅ core promise |
207
+
208
+ LiteLLM/Portkey/Kong-style gateways solve a different layer (org-wide per-key spend) and
209
+ compose fine alongside this — the gateway caps the org, Breakerbox governs one workflow's
210
+ internal structure.
211
+
212
+ **The catch most gateway users miss:** a key limit has to sit high enough not to block real work,
213
+ so a single runaway run can burn **$180 while still *under* a $500 ceiling** — the key never fires
214
+ until the damage is already account-wide, and then it 429s everyone. Breakerbox trips that one run
215
+ at $2, at a hop boundary. *A key limit is the fuse box for the building; Breakerbox is the breaker on
216
+ this circuit.*
217
+
218
+ Every claim above maps to a public, verifiable fact.
219
+
220
+ ## Notes & limitations (read before you rely on it)
221
+
222
+ - **`on_trip="pause"` needs a checkpointer.** Compile your graph with one
223
+ (`.compile(checkpointer=MemorySaver())`); `guard()` raises if it's missing.
224
+ - **The overshoot rule:** the guard never interrupts a call mid-flight. If your model has
225
+ **no** `max_tokens`, the reserve estimate can under-count and the cap is enforced one hop
226
+ late (overshoot bounded by a single call) — the receipt flags that hop. Set `max_tokens`
227
+ and it stops strictly under budget.
228
+ - **Prices** (`prices.json`, ~2000 models) are sourced from LiteLLM's community-maintained
229
+ price table. Refresh them any time with `breakerbox update-prices` (bundled table is the
230
+ offline fallback). Still spot-check the models you care about. Override per-model or set
231
+ `unknown_model="default_rate"` to meter unknown models at a conservative rate instead of
232
+ failing.
233
+
234
+ ## Cloud dashboard (optional)
235
+
236
+ Everything above works with zero cloud. If you want a shared, **live** view of runs, point
237
+ the guard at a Supabase-backed dashboard:
238
+
239
+ ```python
240
+ app = guard(my_app, budget_usd=5.00, report_to="https://YOUR_REF.functions.supabase.co/ingest")
241
+ ```
242
+
243
+ Events stream to the dashboard as the run executes (best-effort and non-blocking — a cloud
244
+ outage never affects the run); each run gets a shareable, unlisted URL with a live timeline.
245
+
246
+ To keep runs **private to your account**, sign in to the dashboard, create a key under
247
+ **Settings → Your ingest key**, and set it where your agents run:
248
+
249
+ ```bash
250
+ export BREAKERBOX_INGEST_KEY="abk_…" # from the dashboard; runs become private to you
251
+ ```
252
+
253
+ Without a personal key the legacy shared key still works and runs stay public (shareable link).
254
+ Deploy runbook and code in [`cloud/`](cloud) (Supabase + Next.js on Vercel).
255
+
256
+ ## Roadmap
257
+
258
+ Where this is headed — horizons, what we will and won't build — is public in
259
+ [`ROADMAP.md`](ROADMAP.md). The codegen-only, no-stored-keys architecture is a permanent
260
+ constraint, not a phase.
261
+
262
+ ## Contributing
263
+
264
+ Issues and PRs welcome. Good first stops: the open [issues](https://github.com/Amitcoh1/agentbreaker/issues)
265
+ and the [`ROADMAP.md`](ROADMAP.md) horizons. To work on it locally:
266
+
267
+ ```bash
268
+ pip install -e ".[dev]"
269
+ pytest -q # pricing, ledger (+ hypothesis), tripwire, meter, guard, report, sink
270
+ ruff check .
271
+ ```
272
+
273
+ The dashboard (`cloud/dashboard`) has its own checks: `npm run typecheck`, `npm run lint`,
274
+ `npm test` (codegen + price-table parity), `npm run build`. CI runs both suites on every push.
275
+
276
+ ## License
277
+
278
+ [MIT](LICENSE)
@@ -0,0 +1,256 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="docs/brand/logo-dark.svg">
4
+ <img alt="Breakerbox" src="docs/brand/logo-light.svg" width="360">
5
+ </picture>
6
+ </p>
7
+
8
+ <p align="center"><strong>The visual agent builder that can't be hacked into — because there's nothing to hack.</strong></p>
9
+
10
+ <p align="center">No server execution&nbsp;·&nbsp;No stored keys&nbsp;·&nbsp;Codegen only</p>
11
+
12
+ <p align="center">
13
+ <a href="https://github.com/Amitcoh1/agentbreaker/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Amitcoh1/agentbreaker/actions/workflows/ci.yml/badge.svg"></a>
14
+ <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-1f2328.svg"></a>
15
+ <img alt="Python 3.11+" src="https://img.shields.io/badge/python-3.11%2B-1f2328.svg">
16
+ <a href="https://github.com/Amitcoh1/agentbreaker/stargazers"><img alt="Stars" src="https://img.shields.io/github/stars/Amitcoh1/agentbreaker?color=b8860b"></a>
17
+ </p>
18
+
19
+ <p align="center">
20
+ <img src="docs/brand/shots/builder.png" alt="The Breakerbox builder: a graph canvas, a live Budget Tree, and guard config" width="820">
21
+ </p>
22
+
23
+ Prototype anywhere; ship with **Breakerbox**. You draw a workflow on a canvas, and it generates
24
+ plain, editable LangGraph **Python** you download and run yourself — wrapped in `guard()`, a
25
+ hierarchical dollar budget that stops runaway loops at a hop boundary and writes a receipt of
26
+ exactly where the money went. Three things make it different:
27
+
28
+ 1. **Zero attack surface — codegen only.** No endpoint ever executes your flows; no provider
29
+ key is ever stored or transmitted. There's nothing on our side to compromise. (See
30
+ [Why no Run button?](#why-no-run-button))
31
+ 2. **Budget-first.** The only builder where hierarchical budget escrow, trip rules, and
32
+ side-effect tagging are part of the canvas itself.
33
+ 3. **Code you own.** The output is readable, hand-editable Python — scaffolding, not a walled
34
+ garden.
35
+
36
+ > **Breakerbox** is the product; the Python package is **`breakerbox`** (`pip install
37
+ > breakerbox`). The import, CLI, and API names are unchanged.
38
+
39
+ ## Contents
40
+
41
+ - [Quickstart](#quickstart)
42
+ - [The demo](#the-demo)
43
+ - [What it actually does](#what-it-actually-does)
44
+ - [Build it visually](#build-it-visually)
45
+ - [Why no Run button?](#why-no-run-button)
46
+ - [How it compares](#how-it-compares-facts-only)
47
+ - [Notes & limitations](#notes--limitations-read-before-you-rely-on-it)
48
+ - [Cloud dashboard (optional)](#cloud-dashboard-optional)
49
+ - [Roadmap](#roadmap)
50
+ - [Contributing](#contributing)
51
+ - [License](#license)
52
+
53
+ ## Quickstart
54
+
55
+ ```bash
56
+ pip install breakerbox
57
+ ```
58
+
59
+ Wrap the compiled LangGraph app you already have — the call site doesn't change:
60
+
61
+ ```python
62
+ from breakerbox import guard
63
+
64
+ app = guard(
65
+ my_langgraph_app,
66
+ budget_usd=5.00, # hard ceiling for the whole workflow DAG
67
+ max_hops=50, # total model/tool steps across all branches
68
+ ttl_seconds=600, # wall-clock limit
69
+ velocity_usd_per_min=2.0, # trip if the burn rate spikes
70
+ on_trip="pause", # "pause" | "kill"
71
+ )
72
+
73
+ result = app.invoke(inputs) # the same call you already make
74
+ ```
75
+
76
+ Every model and tool dispatch inside the graph — including sub-agents and subgraphs —
77
+ inherits one real-dollar budget. When a limit trips, the workflow stops **at the next hop
78
+ boundary** (never mid-call) and drops a shareable HTML receipt. No proxy, no Docker, no
79
+ gateway to run: enforcement lives *inside* your process, so there's no direct-call bypass.
80
+
81
+ Prefer to start on the canvas? Open the builder, draw the graph, and let it write this for you:
82
+
83
+ ```bash
84
+ cd cloud/dashboard && npm install && npm run dev # http://localhost:3000/builder
85
+ ```
86
+
87
+ ## The demo
88
+
89
+ A broken retry loop that never drops its context, so each hop costs more than the last
90
+ ([`examples/runaway_demo`](examples/runaway_demo)) — no API keys required:
91
+
92
+ ```
93
+ ==============================================================
94
+ UNGUARDED runaway : ran 60 hops, spent $12.63
95
+ GUARDED : killed early, spent $0.82 (budget $0.90)
96
+ AVERTED : $11.81
97
+ ==============================================================
98
+ ```
99
+
100
+ It stops **strictly under budget** — because the model declares a real `max_tokens`, the
101
+ reserve estimate is a true upper bound, so the call that would cross the line is blocked
102
+ before it runs. Every run also writes a self-contained `report.html`:
103
+
104
+ ```
105
+ ────────────────────────────────────────────────────────
106
+ breakerbox receipt · killed (budget)
107
+ stopped at $0.8157 budget $0.9000 hops 13
108
+ projected (naive linear extrapolation, likely an underestimate): $3.13
109
+ ────────────────────────────────────────────────────────
110
+ ```
111
+
112
+ (See [`sample_receipt.html`](examples/runaway_demo/sample_receipt.html) for the full HTML.)
113
+
114
+ ## What it actually does
115
+
116
+ - **Hierarchical budget escrow.** Not a flat session counter. A parent sub-allocates budget
117
+ to child agents; a child can never spend beyond its allocation; a parent's remaining is
118
+ `budget − Σ(child allocations) − own spend`. Accounting is reserve → execute → reconcile
119
+ (a credit-card hold), so parallel branches can't race past the ceiling. Concurrency-safe
120
+ and property-tested (`Σ spent + Σ reserved ≤ root budget`, always).
121
+ - **Graceful trip actions.**
122
+ - `pause` — checkpoints via LangGraph's native checkpointer and raises `BudgetPaused`;
123
+ `app.resume(checkpoint_id, extra_budget_usd=...)` continues from where it stopped.
124
+ - `kill` — stops, finalizes the receipt, raises `BudgetKilled` listing which
125
+ **side-effecting** tools already fired (so you can compensate).
126
+ - **Self-metering.** Counts input tokens locally (tiktoken) and meters streamed output
127
+ chunks, then reconciles against the provider's reported usage and flags discrepancies —
128
+ never trusts a single `usage` field, never meters an unknown model as `$0`. If a call ran
129
+ without `max_tokens` and the cap landed one hop late, the receipt flags that hop explicitly.
130
+ - **The receipt.** Terminal summary + single-file `report.html` (inline CSS/SVG, no JS, no
131
+ external assets) + JSON. Leads with the indisputable number — **stopped at $Y, budget $Z** —
132
+ with the projection as clearly-labelled fine print.
133
+
134
+ ## Build it visually
135
+
136
+ [`cloud/dashboard/app/builder`](cloud/dashboard/app/builder) is a drag-and-drop canvas
137
+ (model / tool / router / start / end nodes) with a live **Budget Tree** — root budget →
138
+ per-node allocations → unallocated remainder. Over-allocate and it turns red and **blocks
139
+ export**. Hit **Generate** and you get the guarded Python above, ready to copy or download.
140
+ Everything runs in your browser; the spec → Python codegen is shared with the `breakerbox
141
+ build spec.json` CLI and locked to it by golden-fixture tests (Python and TS, enforced in CI).
142
+
143
+ **AI suggest (bring your own key).** Any model/tool/router node has an optional "Suggest code"
144
+ helper: describe the step, and it drafts a Python body for you to copy in. It calls the model
145
+ **directly from your browser with your own API key** — the key is stored only in your browser
146
+ and sent only to the provider, never to a Breakerbox server (there is no server in this path).
147
+ Anthropic supports browser-direct calls; OpenAI blocks browser CORS, so for OpenAI you point the
148
+ base URL at your own CORS-enabled proxy. The suggestion is copy-paste scaffolding — it's never
149
+ written into the saved graph, so codegen stays deterministic.
150
+
151
+ ## Why no Run button?
152
+
153
+ Server-side flow builders that run your graphs and hold your provider keys have been a
154
+ repeated remote-code-execution target. In Langflow (the category leader, ~100k+ GitHub
155
+ stars, IBM/DataStax-backed) the pattern is well documented and public:
156
+
157
+ - **CVE-2025-3248** (CVSS 9.8) — unauthenticated RCE via a code-validation endpoint that
158
+ passed user input to `exec()`; on CISA's KEV list, used to deploy the Flodrix botnet.
159
+ - **CVE-2025-34291** (CVSS 9.4) — an account-takeover chain that also **exfiltrates the API
160
+ keys stored in a workspace**; on CISA KEV, used by the MuddyWater APT for initial access.
161
+ - **CVE-2026-33017** (CVSS 9.8) — unauthenticated RCE via the public flow-build endpoint;
162
+ on CISA KEV, weaponized within ~20 hours of disclosure to drop cryptominers.
163
+ - **CVE-2026-5027** (CVSS 8.8) — path-traversal RCE via file upload, with ~7,000 exposed
164
+ instances observed under active exploitation.
165
+
166
+ This isn't a knock on Langflow's product — it's an architectural fact: **a server that runs
167
+ your flows and holds your keys is a high-value target.** Breakerbox removes the target. The
168
+ canvas only ever produces a Python *string* you run yourself, and your API keys live in your
169
+ own environment — never in a dashboard, database, or edge function. There's no endpoint to
170
+ exploit because no endpoint executes anything. That's the trade: you give up one-click cloud
171
+ runs, and in exchange there is nothing to breach. **Prototype in a tool like Langflow if you
172
+ like; ship the production-safe version here.**
173
+
174
+ ## How it compares (facts only)
175
+
176
+ | | Langflow | LiteLLM budgets | Breakerbox |
177
+ |---|:---:|:---:|:---:|
178
+ | Visual graph building | ✅ rich | — | ✅ budget-first |
179
+ | Server executes your flows | ✅ *(attack surface)* | n/a | ❌ by design |
180
+ | Stores your provider keys | ✅ | ✅ *(proxy)* | ❌ never |
181
+ | Hierarchical per-agent dollar escrow | — | — *(flat session)* | ✅ |
182
+ | Graceful pause/resume at a hop boundary | — | — *(hard error)* | ✅ |
183
+ | Catches a runaway run *under* the org ceiling | — | — *(never fires)* | ✅ |
184
+ | Output is plain, editable Python | partial *(export)* | n/a | ✅ core promise |
185
+
186
+ LiteLLM/Portkey/Kong-style gateways solve a different layer (org-wide per-key spend) and
187
+ compose fine alongside this — the gateway caps the org, Breakerbox governs one workflow's
188
+ internal structure.
189
+
190
+ **The catch most gateway users miss:** a key limit has to sit high enough not to block real work,
191
+ so a single runaway run can burn **$180 while still *under* a $500 ceiling** — the key never fires
192
+ until the damage is already account-wide, and then it 429s everyone. Breakerbox trips that one run
193
+ at $2, at a hop boundary. *A key limit is the fuse box for the building; Breakerbox is the breaker on
194
+ this circuit.*
195
+
196
+ Every claim above maps to a public, verifiable fact.
197
+
198
+ ## Notes & limitations (read before you rely on it)
199
+
200
+ - **`on_trip="pause"` needs a checkpointer.** Compile your graph with one
201
+ (`.compile(checkpointer=MemorySaver())`); `guard()` raises if it's missing.
202
+ - **The overshoot rule:** the guard never interrupts a call mid-flight. If your model has
203
+ **no** `max_tokens`, the reserve estimate can under-count and the cap is enforced one hop
204
+ late (overshoot bounded by a single call) — the receipt flags that hop. Set `max_tokens`
205
+ and it stops strictly under budget.
206
+ - **Prices** (`prices.json`, ~2000 models) are sourced from LiteLLM's community-maintained
207
+ price table. Refresh them any time with `breakerbox update-prices` (bundled table is the
208
+ offline fallback). Still spot-check the models you care about. Override per-model or set
209
+ `unknown_model="default_rate"` to meter unknown models at a conservative rate instead of
210
+ failing.
211
+
212
+ ## Cloud dashboard (optional)
213
+
214
+ Everything above works with zero cloud. If you want a shared, **live** view of runs, point
215
+ the guard at a Supabase-backed dashboard:
216
+
217
+ ```python
218
+ app = guard(my_app, budget_usd=5.00, report_to="https://YOUR_REF.functions.supabase.co/ingest")
219
+ ```
220
+
221
+ Events stream to the dashboard as the run executes (best-effort and non-blocking — a cloud
222
+ outage never affects the run); each run gets a shareable, unlisted URL with a live timeline.
223
+
224
+ To keep runs **private to your account**, sign in to the dashboard, create a key under
225
+ **Settings → Your ingest key**, and set it where your agents run:
226
+
227
+ ```bash
228
+ export BREAKERBOX_INGEST_KEY="abk_…" # from the dashboard; runs become private to you
229
+ ```
230
+
231
+ Without a personal key the legacy shared key still works and runs stay public (shareable link).
232
+ Deploy runbook and code in [`cloud/`](cloud) (Supabase + Next.js on Vercel).
233
+
234
+ ## Roadmap
235
+
236
+ Where this is headed — horizons, what we will and won't build — is public in
237
+ [`ROADMAP.md`](ROADMAP.md). The codegen-only, no-stored-keys architecture is a permanent
238
+ constraint, not a phase.
239
+
240
+ ## Contributing
241
+
242
+ Issues and PRs welcome. Good first stops: the open [issues](https://github.com/Amitcoh1/agentbreaker/issues)
243
+ and the [`ROADMAP.md`](ROADMAP.md) horizons. To work on it locally:
244
+
245
+ ```bash
246
+ pip install -e ".[dev]"
247
+ pytest -q # pricing, ledger (+ hypothesis), tripwire, meter, guard, report, sink
248
+ ruff check .
249
+ ```
250
+
251
+ The dashboard (`cloud/dashboard`) has its own checks: `npm run typecheck`, `npm run lint`,
252
+ `npm test` (codegen + price-table parity), `npm run build`. CI runs both suites on every push.
253
+
254
+ ## License
255
+
256
+ [MIT](LICENSE)
@@ -0,0 +1,53 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "breakerbox"
7
+ version = "0.1.0"
8
+ description = "Circuit breaker + hierarchical cost budgeting for AI agent workflows"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ authors = [{ name = "Amit Cohen", email = "amit.cellebrite@gmail.com" }]
13
+ keywords = ["langgraph", "llm", "budget", "circuit-breaker", "agents", "cost"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "License :: OSI Approved :: MIT License",
17
+ ]
18
+ # Runtime deps are added per phase (see DECISIONS.md).
19
+ dependencies = [
20
+ "langgraph>=0.2",
21
+ "langchain-core>=0.3",
22
+ "tiktoken>=0.7",
23
+ "jinja2>=3.1",
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ dev = ["pytest>=8", "ruff>=0.6", "hypothesis>=6"]
28
+
29
+ [project.scripts]
30
+ breakerbox = "breakerbox.cli:main"
31
+
32
+ [project.urls]
33
+ Homepage = "https://github.com/Amitcoh1/agentbreaker"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/breakerbox"]
37
+
38
+ # Without this, the sdist defaults to the whole repo — it swept in cloud/**/node_modules
39
+ # + .next caches (235 MB, over PyPI's 100 MB limit). Scope it to actual source.
40
+ [tool.hatch.build.targets.sdist]
41
+ only-include = ["src/breakerbox", "tests", "README.md", "LICENSE", "pyproject.toml"]
42
+
43
+ [tool.ruff]
44
+ line-length = 100
45
+ target-version = "py311"
46
+ # Golden codegen fixtures are generated artifacts, not source — don't lint them.
47
+ extend-exclude = ["tests/fixtures"]
48
+
49
+ [tool.ruff.lint]
50
+ select = ["E", "F", "I", "UP", "B"]
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
@@ -0,0 +1,25 @@
1
+ """Breakerbox: circuit breaker + hierarchical cost budgeting for agent workflows."""
2
+
3
+ from breakerbox.guard import (
4
+ BudgetKilled,
5
+ BudgetPaused,
6
+ GuardedApp,
7
+ guard,
8
+ mark_side_effecting,
9
+ )
10
+ from breakerbox.ledger import InsufficientBudget, Ledger
11
+ from breakerbox.pricing import PriceTable, UnknownModelError, cost_microusd
12
+
13
+ __all__ = [
14
+ "BudgetKilled",
15
+ "BudgetPaused",
16
+ "GuardedApp",
17
+ "InsufficientBudget",
18
+ "Ledger",
19
+ "PriceTable",
20
+ "UnknownModelError",
21
+ "cost_microusd",
22
+ "guard",
23
+ "mark_side_effecting",
24
+ ]
25
+ __version__ = "0.0.1"