Circuit breaker + hierarchical cost budgeting for AI agent workflows
Project description
The visual agent builder that can't be hacked into — because there's nothing to hack.
No server execution · No stored keys · Codegen only
pip install breakerbox
🔗 Live site · The demo ↓ · Quickstart ↓
Prototype anywhere; ship with Breakerbox. You draw a workflow on a canvas, and it generates
plain, editable LangGraph Python you download and run yourself — wrapped in guard(), a
hierarchical dollar budget that stops runaway loops at a hop boundary and writes a receipt of
exactly where the money went. In the bundled demo, a runaway retry loop that burns
$12.63 unguarded stops at $0.82 under guard(). Three things make it different:
- Zero attack surface — codegen only. No endpoint ever executes your flows; no provider key is ever stored or transmitted. There's nothing on our side to compromise. (See Why no Run button?)
- Budget-first. The only builder where hierarchical budget escrow, trip rules, and side-effect tagging are part of the canvas itself.
- Code you own. The output is readable, hand-editable Python — scaffolding, not a walled garden.
Breakerbox is the product; the Python package is
breakerbox(pip install breakerbox). The import, CLI, and API names are unchanged.
Contents
- Quickstart
- The demo
- What it actually does
- Build it visually
- Gate cost in CI
- Why no Run button?
- How it compares
- Notes & limitations
- Cloud dashboard (optional)
- Roadmap
- Contributing
- License
Quickstart
pip install breakerbox
Wrap the compiled LangGraph app you already have — the call site doesn't change:
from breakerbox import guard
app = guard(
my_langgraph_app,
budget_usd=5.00, # hard ceiling for the whole workflow DAG
max_hops=50, # total model/tool steps across all branches
ttl_seconds=600, # wall-clock limit
velocity_usd_per_min=2.0, # trip if the burn rate spikes
on_trip="pause", # "pause" | "kill"
)
result = app.invoke(inputs) # the same call you already make
Every model and tool dispatch inside the graph — including sub-agents and subgraphs — inherits one real-dollar budget. When a limit trips, the workflow stops at the next hop boundary (never mid-call) and drops a shareable HTML receipt. No proxy, no Docker, no gateway to run: enforcement lives inside your process, so there's no direct-call bypass.
Prefer to start on the canvas? Open the builder, draw the graph, and let it write this for you:
cd cloud/dashboard && npm install && npm run dev # http://localhost:3000/builder
The demo
A broken retry loop that never drops its context, so each hop costs more than the last
(examples/runaway_demo) — no API keys required:
==============================================================
UNGUARDED runaway : ran 60 hops, spent $12.63
GUARDED : killed early, spent $0.82 (budget $0.90)
AVERTED : $11.81
==============================================================
It stops strictly under budget — because the model declares a real max_tokens, the
reserve estimate is a true upper bound, so the call that would cross the line is blocked
before it runs. Every run also writes a self-contained report.html:
────────────────────────────────────────────────────────
breakerbox receipt · killed (budget)
stopped at $0.8157 budget $0.9000 hops 13
projected (naive linear extrapolation, likely an underestimate): $3.13
────────────────────────────────────────────────────────
(See sample_receipt.html for the full HTML.)
What it actually does
- Hierarchical budget escrow. Not a flat session counter. A parent sub-allocates budget
to child agents; a child can never spend beyond its allocation; a parent's remaining is
budget − Σ(child allocations) − own spend. Accounting is reserve → execute → reconcile (a credit-card hold), so parallel branches can't race past the ceiling. Concurrency-safe and property-tested (Σ spent + Σ reserved ≤ root budget, always). - Graceful trip actions.
pause— checkpoints via LangGraph's native checkpointer and raisesBudgetPaused;app.resume(checkpoint_id, extra_budget_usd=...)continues from where it stopped.kill— stops, finalizes the receipt, raisesBudgetKilledlisting which side-effecting tools already fired (so you can compensate).
- Self-metering. Counts input tokens locally (tiktoken) and meters streamed output
chunks, then reconciles against the provider's reported usage and flags discrepancies —
never trusts a single
usagefield, never meters an unknown model as$0. If a call ran withoutmax_tokensand the cap landed one hop late, the receipt flags that hop explicitly. - The receipt. Terminal summary + single-file
report.html(inline CSS/SVG, no JS, no external assets) + JSON. Leads with the indisputable number — stopped at $Y, budget $Z — with the projection as clearly-labelled fine print.
Build it visually
cloud/dashboard/app/builder is a drag-and-drop canvas
(model / tool / router / start / end nodes) with a live Budget Tree — root budget →
per-node allocations → unallocated remainder. Over-allocate and it turns red and blocks
export. Hit Generate and you get the guarded Python above, ready to copy or download.
Everything runs in your browser; the spec → Python codegen is shared with the breakerbox build spec.json CLI and locked to it by golden-fixture tests (Python and TS, enforced in CI).
Cost forecast (before you run). The canvas forecasts a run's cost while you design it — a p50–p95 band per node and for the whole graph, with a what-if loop slider and a per-node model swap, all computed in the browser with no API calls. It's a labelled estimate and advisory-only: it never changes the generated Python. Most builders show cost after a run; this shows it first.
AI suggest (bring your own key). Any model/tool/router node has an optional "Suggest code" helper: describe the step, and it drafts a Python body for you to copy in. It calls the model directly from your browser with your own API key — the key is stored only in your browser and sent only to the provider, never to a Breakerbox server (there is no server in this path). Anthropic supports browser-direct calls; OpenAI blocks browser CORS, so for OpenAI you point the base URL at your own CORS-enabled proxy. The suggestion is copy-paste scaffolding — it's never written into the saved graph, so codegen stays deterministic.
Gate cost in CI
Make your agent's cost a unit test. The provable cost ceiling — every reachable hop at full
max_tokens, a loop charged max_hops × the costliest hop — is computed with zero API calls,
so it runs in CI and fails a pull request that pushes a graph past your budget:
# .github/workflows/cost-ceiling.yml
- uses: actions/checkout@v4
- uses: Amitcoh1/agentbreaker@main # pin a released tag (e.g. @v0.6.0) for reproducible CI
with:
spec: "specs/*.spec.json"
max: "2.00" # red PR if any spec's ceiling exceeds $2.00 (or is unbounded)
Same check locally or in any pipeline — breakerbox ceiling specs/*.spec.json --max 2.00 exits 1
when over-limit or unbounded. See CI budget gate.
Needs
breakerbox >= 0.6.0(the release that shipsbreakerbox ceiling).
Why no Run button?
Server-side flow builders that run your graphs and hold your provider keys have been a repeated remote-code-execution target. In Langflow (the category leader — 150k+ GitHub stars, backed by DataStax, an IBM company) the pattern is well documented and public:
- CVE-2025-3248 (CVSS 9.8) — unauthenticated RCE via the
/api/v1/validate/codeendpoint that passed user input toexec(); on CISA's KEV (added 2025-05-05), exploited by the Flodrix botnet. - CVE-2025-34291 (CVSS 9.4 v4.0 / 8.8 v3.1) — a CORS/CSRF account-takeover chain (a victim visits a malicious page) that exfiltrates the API keys and tokens stored in a workspace; on CISA KEV (added 2026-05-21). Reportedly used by the MuddyWater APT for initial access (Ctrl-Alt-Intel via The Hacker News; not confirmed by CISA).
- CVE-2026-33017 (CVSS 9.8) — unauthenticated RCE via the public flow-build endpoint
(
/api/v1/build_public_tmp/…); on CISA KEV (added 2026-03-25), exploited within ~20 hours of disclosure (Sysdig) and separately used to drop Monero/XMRig cryptominers (Trend Micro). - CVE-2026-5027 (CVSS 8.8) — path-traversal arbitrary file write via
/api/v2/files, escalatable to RCE; ~7,000 instances exposed (Censys, incl. historical scan data) with active exploitation observed (VulnCheck). Not on CISA KEV.
This isn't a knock on Langflow's product — it's an architectural fact: a server that runs your flows and holds your keys is a high-value target. Breakerbox removes the target. The canvas only ever produces a Python string you run yourself, and your API keys live in your own environment — never in a dashboard, database, or edge function. There's no endpoint to exploit because no endpoint executes anything. That's the trade: you give up one-click cloud runs, and in exchange there is nothing to breach. Prototype in a tool like Langflow if you like; ship the production-safe version here.
How it compares (facts only)
| Langflow | LiteLLM budgets | Breakerbox | |
|---|---|---|---|
| Visual graph building | ✅ rich | — | ✅ budget-first |
| Server executes your flows | ✅ (attack surface) | n/a | ❌ by design |
| Stores your provider keys | ✅ | ✅ (proxy) | ❌ never |
| Hierarchical per-agent dollar escrow | — | — (flat, per-key) | ✅ |
| Graceful pause/resume at a hop boundary | — | — (hard error) | ✅ |
| Catches a runaway run under the org ceiling | — | — (never fires) | ✅ |
| Output is plain, editable Python | partial (export) | n/a | ✅ core promise |
LiteLLM/Portkey/Kong-style gateways solve a different layer (org-wide per-key spend) and compose fine alongside this — the gateway caps the org, Breakerbox governs one workflow's internal structure.
Behind a gateway (LiteLLM / Portkey): point your model at the gateway's base URL and wrap the
compiled app in guard(). Metering is a LangChain callback, not a network hook, so it composes
with any OpenAI-compatible proxy with no extra wiring:
model = ChatOpenAI(model="openai/gpt-4o", base_url="http://localhost:4000", api_key="sk-litellm-…")
app = guard(compiled_app, budget_usd=5.00) # gateway routes + holds the key; guard budgets the run
The catch most gateway users miss: a key limit has to sit high enough not to block real work, so a single runaway run can burn $180 while still under a $500 ceiling — the key never fires until the damage is already account-wide, and then it 429s everyone. Breakerbox trips that one run at $2, at a hop boundary. A key limit is the fuse box for the building; Breakerbox is the breaker on this circuit.
vs LiteLLM / vs Langflow, in one line: a gateway like LiteLLM guards the key — one flat ceiling, blind to a single run's internal structure. A builder like Langflow runs your flow — server-side, holding your keys. Breakerbox guards the workflow and runs nothing: a hierarchical per-agent budget baked into plain Python you own. Prototype with either; ship the guarded version here.
Every claim above maps to a public, verifiable fact.
Notes & limitations (read before you rely on it)
on_trip="pause"needs a checkpointer. Compile your graph with one (.compile(checkpointer=MemorySaver()));guard()raises if it's missing.- The overshoot rule: the guard never interrupts a call mid-flight. If your model has
no
max_tokens, the reserve estimate can under-count and the cap is enforced one hop late (overshoot bounded by a single call) — the receipt flags that hop. Setmax_tokensand it stops strictly under budget. - Prices (
prices.json, ~2000 models) are sourced from LiteLLM's community-maintained price table. Refresh them any time withbreakerbox update-prices(bundled table is the offline fallback). Still spot-check the models you care about. Override per-model or setunknown_model="default_rate"to meter unknown models at a conservative rate instead of failing.
Cloud dashboard (optional)
→ Open the dashboard — watch runs stream live, and sign in to keep runs private to your account.
Everything above works with zero cloud. If you want a shared, live view of runs, point the guard at a Supabase-backed dashboard:
app = guard(my_app, budget_usd=5.00, report_to="https://YOUR_REF.functions.supabase.co/ingest")
Events stream to the dashboard as the run executes (best-effort and non-blocking — a cloud outage never affects the run); each run gets a shareable, unlisted URL with a live timeline.
To keep runs private to your account, sign in to the dashboard, create a key under Settings → Your ingest key, and set it where your agents run:
export BREAKERBOX_INGEST_KEY="abk_…" # from the dashboard; runs become private to you
Without a personal key the legacy shared key still works and runs stay public (shareable link).
Deploy runbook and code in cloud/ (Supabase + Next.js on Vercel).
Roadmap
Where this is headed — horizons, what we will and won't build — is public in
ROADMAP.md. The codegen-only, no-stored-keys architecture is a permanent
constraint, not a phase.
Contributing
Issues and PRs welcome. Good first stops: the open issues
and the ROADMAP.md horizons. To work on it locally:
pip install -e ".[dev]"
pytest -q # pricing, ledger (+ hypothesis), tripwire, meter, guard, report, sink
ruff check .
The dashboard (cloud/dashboard) has its own checks: npm run typecheck, npm run lint,
npm test (codegen + price-table parity), npm run build. CI runs both suites on every push.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file breakerbox-0.6.0.tar.gz.
File metadata
- Download URL: breakerbox-0.6.0.tar.gz
- Upload date:
- Size: 75.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
34867cbf216351a6f182a326ee4f7ae633815fff398e91c450046129601b2a1b
|
|
| MD5 |
a712c1a7ea85af5f1ef388949c05a262
|
|
| BLAKE2b-256 |
ab2239f8bb94e58db001df8e56f82e2e2ff926ea49fe2ea149bbd2f1962bbc97
|
Provenance
The following attestation bundles were made for breakerbox-0.6.0.tar.gz:
Publisher:
publish.yml on Amitcoh1/agentbreaker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
breakerbox-0.6.0.tar.gz -
Subject digest:
34867cbf216351a6f182a326ee4f7ae633815fff398e91c450046129601b2a1b - Sigstore transparency entry: 2251469251
- Sigstore integration time:
-
Permalink:
Amitcoh1/agentbreaker@fcb530613197542c12206ab823c11039c5af2d8c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Amitcoh1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fcb530613197542c12206ab823c11039c5af2d8c -
Trigger Event:
push
-
Statement type:
File details
Details for the file breakerbox-0.6.0-py3-none-any.whl.
File metadata
- Download URL: breakerbox-0.6.0-py3-none-any.whl
- Upload date:
- Size: 68.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7de35a29cc86d8b3883b3bf7cf43fff66dc0df45d7a0af3b15a81a148b1a2d5d
|
|
| MD5 |
9601f7ff6ed3751312da3e564745f4d5
|
|
| BLAKE2b-256 |
a0619d1df47ba26b7c873141d85a3ab82c50afa66b45063d32e6e5130f1b670d
|
Provenance
The following attestation bundles were made for breakerbox-0.6.0-py3-none-any.whl:
Publisher:
publish.yml on Amitcoh1/agentbreaker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
breakerbox-0.6.0-py3-none-any.whl -
Subject digest:
7de35a29cc86d8b3883b3bf7cf43fff66dc0df45d7a0af3b15a81a148b1a2d5d - Sigstore transparency entry: 2251469427
- Sigstore integration time:
-
Permalink:
Amitcoh1/agentbreaker@fcb530613197542c12206ab823c11039c5af2d8c -
Branch / Tag:
refs/heads/main - Owner: https://github.com/Amitcoh1
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@fcb530613197542c12206ab823c11039c5af2d8c -
Trigger Event:
push
-
Statement type: