Skip to main content

Runtime circuit breakers for AI agents: trip fuses on runaway cost, loops, and retry storms.

Project description

RunFuse ⚡

CI License: MIT Python 3.11+

Runtime circuit breakers for AI agents. Trip fuses on runaway cost, infinite loops, and retry storms — before the invoice does it for you.

from runfuse import Fuse, FusePolicy
from anthropic import Anthropic

fuse = Fuse(FusePolicy(
    max_cost_usd=5.00,            # hard spend ceiling for this run
    max_steps=50,                 # max LLM calls
    max_identical_tool_calls=10,  # loop detector
    max_llm_errors=5,             # retry-storm detector
))
client = fuse.wrap(Anthropic())   # AsyncAnthropic / (Async)OpenAI work too

with fuse.run("research-task"):
    ...  # your agent loop — trips raise FuseTripped, run stops

with fuse.run("untrusted-task", policy=FusePolicy(max_cost_usd=0.50)):
    ...  # per-run override: tighter budget for this run only

Why

Agents aren't chat. One user request fans out into dozens–hundreds of autonomous LLM calls. When an agent enters a retry loop or a planning spiral, nothing stops it — it burns tokens until someone notices the bill.

Existing tools don't cover this:

  • LLM gateways (LiteLLM, Portkey, Helicone) enforce budgets per API key. They see isolated HTTP requests — they can't tell "this run is looping" or kill one sick agent without killing all of them.
  • Observability platforms (LangSmith, Langfuse) are passive — they show you the fire afterwards.

RunFuse enforces at the run level, actively, mid-flight. The with fuse.run(...) scope is the identity gateways don't have.

Install

pip install runfuse            # core (pydantic only)
pip install runfuse[anthropic] # + Anthropic SDK
pip install runfuse[openai]    # + OpenAI SDK
pip install runfuse[langgraph] # + LangGraph/LangChain callback
pip install runfuse[dashboard] # + live web dashboard

Using LangGraph instead of a raw SDK client? Same fuse, one callback:

from runfuse.interceptors.langgraph import RunFuseCallback

with fuse.run("graph-task"):
    graph.invoke(inputs, config={"callbacks": [RunFuseCallback(fuse)]})

Tools the interceptor can't see (MCP server handlers, framework-executed tools)? Guard them directly — trips fire before the tool body runs:

@fuse.guard_tool
def web_search(query: str) -> str: ...

Fuses

Limit Catches
max_cost_usd runaway spend
max_total_tokens token blowout
max_steps unbounded agent loops
max_wall_time_s hung runs
max_llm_errors retry storms
max_identical_tool_calls the agent calling the same tool with the same args, forever
max_burn_rate_usd_per_min spend spikes — $/min over a trailing window (burn_rate_window_s, default 60s) trips long before the total cap would
max_similar_tool_calls fuzzy loops — retries with slightly varied args counted together (similarity_threshold, default 0.9)

Every hard limit also has a soft threshold (default 80%): a warning callback fires before the fuse blows, once per fuse per run.

Costs come from a built-in pricing table (override via Fuse(pricing={...})). Unknown models count as $0 with a logged warning — set strict_pricing=True on the policy to hard-trip instead of running cost-blind.

fuse = Fuse(
    policy,
    on_soft_trip=lambda verdict, state: notify_slack(verdict),
)

Trip semantics — honest by design

A trip raises FuseTripped at the next interception point (the next LLM call). It cannot stop a tool mid-execution — nothing can, and tools that claim otherwise are lying. The same honesty applies to blind spots: streaming calls are refused loudly (stream=True raises) rather than silently recorded as $0, until true streaming accounting ships. The exception carries the full verdict and run state:

try:
    with fuse.run("job-42") as state:
        agent_loop(client)
except FuseTripped as trip:
    print(trip.verdict)        # [hard] max_cost_usd: spent $5.0012 >= $5.0000
    print(state.steps)         # 37
    print(state.cost_usd)      # 5.0012

See it trip

No API key needed:

git clone https://github.com/akshat333-debug/RunFuse && cd RunFuse
uv sync --all-extras
uv run runfuse demo                     # scripted runaway, fuse trips
uv run runfuse report                   # per-run cost/step/trip summary
uv run runfuse dashboard                # live web UI (localhost:8321)

The dashboard's abort button writes a flag the fuse checks at the run's next interception point — same honest semantics as any other trip. Approve is implicit: a soft-warned run keeps going unless you kill it.

How it works

fuse.wrap(client) → every LLM call emits an Event
                       │
                       ▼
        RunState (cost, steps, errors, tool-call counts)
                       │
                       ▼
        PolicyEngine → ok | soft trip (warn) | hard trip (raise)
                       │
                       ▼
        SQLite event store → `runfuse report` / dashboard

Tool calls are parsed from LLM response payloads (tool_use blocks / tool_calls), so loop detection works from the SDK wrapper alone — no framework hooks required. Runs are scoped via contextvars, so concurrent async runs stay independent.

Roadmap

  • Anthropic + OpenAI SDK wrappers
  • Cost / step / time / error / loop fuses, soft thresholds
  • SQLite event store + CLI report
  • Burn-rate fuse (trailing-window $/min cap)
  • Fuzzy loop detection (near-identical args)
  • runfuse demo one-command demo
  • Live dashboard (FastAPI + HTMX) with abort — a warned run continues unless you kill it
  • LangGraph/LangChain callback integration
  • guard_tool decorator — loop/abort enforcement for MCP handlers and framework-executed tools
  • PyPI release

Development

uv sync --all-extras
uv run pytest
uv run mypy src
uv run ruff check src tests

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

runfuse-0.1.0.tar.gz (21.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

runfuse-0.1.0-py3-none-any.whl (30.8 kB view details)

Uploaded Python 3

File details

Details for the file runfuse-0.1.0.tar.gz.

File metadata

  • Download URL: runfuse-0.1.0.tar.gz
  • Upload date:
  • Size: 21.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for runfuse-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1ab5276054b6f4c3fd65af1c8d001fb36d4f31e028352c15aa82165b769c33f5
MD5 f41d49e5829f2ad664129540605d0954
BLAKE2b-256 a5accd820382b8a6006e0071365dd8eb1f74b6f0b140e4e94a48f3be2121cfc6

See more details on using hashes here.

Provenance

The following attestation bundles were made for runfuse-0.1.0.tar.gz:

Publisher: release.yml on akshat333-debug/RunFuse

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file runfuse-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: runfuse-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 30.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for runfuse-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f079fe5341e2c24f1768ee1c6da5deb05281fccf208d59813bcbd601931176d3
MD5 71e35d21c5d3e67493d55b6651c825fd
BLAKE2b-256 016dcf42d82461a4faf3f6164b015a7a7f1d9f82f6d3697cd26aa2a92e1dc9eb

See more details on using hashes here.

Provenance

The following attestation bundles were made for runfuse-0.1.0-py3-none-any.whl:

Publisher: release.yml on akshat333-debug/RunFuse

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page