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.
- breakerbox-0.1.0/.gitignore +24 -0
- breakerbox-0.1.0/LICENSE +21 -0
- breakerbox-0.1.0/PKG-INFO +278 -0
- breakerbox-0.1.0/README.md +256 -0
- breakerbox-0.1.0/pyproject.toml +53 -0
- breakerbox-0.1.0/src/breakerbox/__init__.py +25 -0
- breakerbox-0.1.0/src/breakerbox/cli.py +64 -0
- breakerbox-0.1.0/src/breakerbox/codegen.py +183 -0
- breakerbox-0.1.0/src/breakerbox/control.py +63 -0
- breakerbox-0.1.0/src/breakerbox/events.py +60 -0
- breakerbox-0.1.0/src/breakerbox/graph.example.json +28 -0
- breakerbox-0.1.0/src/breakerbox/graph.schema.json +55 -0
- breakerbox-0.1.0/src/breakerbox/graphspec.py +295 -0
- breakerbox-0.1.0/src/breakerbox/guard.py +499 -0
- breakerbox-0.1.0/src/breakerbox/ledger.py +220 -0
- breakerbox-0.1.0/src/breakerbox/meter.py +90 -0
- breakerbox-0.1.0/src/breakerbox/prices.json +8715 -0
- breakerbox-0.1.0/src/breakerbox/pricing.py +110 -0
- breakerbox-0.1.0/src/breakerbox/pricing_update.py +97 -0
- breakerbox-0.1.0/src/breakerbox/report/__init__.py +0 -0
- breakerbox-0.1.0/src/breakerbox/report/generate.py +161 -0
- breakerbox-0.1.0/src/breakerbox/report/template.html.j2 +96 -0
- breakerbox-0.1.0/src/breakerbox/sink.py +69 -0
- breakerbox-0.1.0/src/breakerbox/tripwire.py +89 -0
- breakerbox-0.1.0/tests/fixtures/graphspec/linear.json +23 -0
- breakerbox-0.1.0/tests/fixtures/graphspec/linear.py +43 -0
- breakerbox-0.1.0/tests/fixtures/graphspec/over_allocation.errors.json +3 -0
- breakerbox-0.1.0/tests/fixtures/graphspec/over_allocation.json +15 -0
- breakerbox-0.1.0/tests/fixtures/graphspec/router_cycle.json +28 -0
- breakerbox-0.1.0/tests/fixtures/graphspec/router_cycle.py +62 -0
- breakerbox-0.1.0/tests/fixtures/graphspec/with_code.json +20 -0
- breakerbox-0.1.0/tests/fixtures/graphspec/with_code.py +41 -0
- breakerbox-0.1.0/tests/test_codegen.py +107 -0
- breakerbox-0.1.0/tests/test_control_integration.py +89 -0
- breakerbox-0.1.0/tests/test_graphspec.py +138 -0
- breakerbox-0.1.0/tests/test_guard_integration.py +178 -0
- breakerbox-0.1.0/tests/test_ledger.py +197 -0
- breakerbox-0.1.0/tests/test_meter.py +42 -0
- breakerbox-0.1.0/tests/test_pricing.py +102 -0
- breakerbox-0.1.0/tests/test_pricing_update.py +43 -0
- breakerbox-0.1.0/tests/test_report.py +101 -0
- breakerbox-0.1.0/tests/test_sink_integration.py +80 -0
- 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/
|
breakerbox-0.1.0/LICENSE
ADDED
|
@@ -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 · No stored keys · 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 · No stored keys · 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"
|