An emergency brake for AI agents — budget caps, loop detection, and state-stasis guards in three lines of code.
Project description
MagicRails
An emergency brake for AI agents.
⚠️ Alpha (v0.1). API may change before v1.0. Not yet recommended for production.
Budget caps, loop detection, and state-stasis guards — in three lines of code.
from magicrails import guard
@guard(budget_usd=10.0, max_repeats=3, stasis_steps=5)
def my_agent(task): ...
That's it. If your agent loops, stalls, or runs past its budget, Magicrails halts it before you get the bill.
Why
Every agent developer has either had — or is one bug away from — a $500 overnight invoice from a runaway agent. The agent retries the same tool forever, the cost graph goes vertical, and you find out at breakfast.
Observability tools show you the disaster. Magicrails stops it.
Install
pip install magicrails
Zero required dependencies. Pure Python. Python 3.10+.
The three guards
1. Budget ceiling — a hard dollar cap
Tokens are counted, priced against a community-maintained table, and summed per session. When the session crosses the limit, the agent is halted.
from magicrails import Magicrails
with Magicrails(budget_usd=10.0) as session:
resp = client.messages.create(model="claude-opus-4-7", messages=[...])
session.record_tokens(
model="claude-opus-4-7",
input=resp.usage.input_tokens,
output=resp.usage.output_tokens,
)
Or use an adapter and let Magicrails instrument your client automatically — no manual record_tokens calls:
from anthropic import Anthropic
from magicrails import Magicrails
from magicrails.adapters import anthropic as magicrails_anthropic
client = magicrails_anthropic.instrument(Anthropic())
with Magicrails(budget_usd=10.0):
client.messages.create(model="claude-opus-4-7", messages=[...]) # auto-counted
Same shape for OpenAI:
from openai import OpenAI
from magicrails import Magicrails
from magicrails.adapters import openai as magicrails_openai
client = magicrails_openai.instrument(OpenAI())
with Magicrails(budget_usd=10.0):
client.chat.completions.create(model="gpt-4o", messages=[...]) # auto-counted
2. Repeat-call guard — stop tool loops dead
If the agent calls the same tool with the same arguments N times, it's probably stuck. Magicrails halts it.
with Magicrails(max_repeats=3) as session:
session.record_call("list_files", {"path": "/tmp"}) # ok
session.record_call("list_files", {"path": "/tmp"}) # ok
session.record_call("list_files", {"path": "/tmp"}) # 🛑 TripError
3. State-stasis guard — catch reasoning loops
Hash the agent's state after each step. If it hasn't moved in K steps, the agent is thinking in circles.
with Magicrails(stasis_steps=5) as session:
for step in agent.run():
session.record_state(agent.state)
On-trip actions
By default, a trip raises magicrails.TripError. You can override:
from magicrails import guard, actions
# Ask a human in the terminal
@guard(budget_usd=10.0, on_trip=actions.prompt_human)
def my_agent(task): ...
# Send to Slack / PagerDuty / anywhere
@guard(budget_usd=10.0, on_trip=actions.webhook("https://hooks.slack.com/..."))
def my_agent(task): ...
# Custom handler
def alert(reason):
logger.critical(f"Agent tripped: {reason}")
@guard(budget_usd=10.0, on_trip=alert)
def my_agent(task): ...
Framework adapters
| Framework | Import | Status |
|---|---|---|
| OpenAI SDK | magicrails.adapters.openai |
✅ v0.1 |
| Anthropic SDK | magicrails.adapters.anthropic |
✅ v0.1 |
| LangChain | magicrails.adapters.langchain |
🚧 v0.2 |
| LangGraph | magicrails.adapters.langgraph |
🚧 v0.2 |
| CrewAI | magicrails.adapters.crewai |
🚧 v0.2 |
| AutoGen | magicrails.adapters.autogen |
🚧 v0.2 |
| OpenTelemetry | magicrails.adapters.otel |
🚧 v0.2 |
Adapters are one-file. PRs welcome.
Pricing table
magicrails/models.json ships with reasonable defaults for the major current models (per 1M tokens, USD). Override or extend:
my_pricing = {
"my-internal-model": {"input": 0.10, "output": 0.30},
}
with Magicrails(budget_usd=5.0, pricing=my_pricing) as session:
...
Unknown models are counted as $0 and emit a WARNING on the magicrails logger (once per model per process). Add the model to your pricing dict to enforce a real budget.
Philosophy
Magicrails is not an observability platform. It is not a tracing tool. It does one thing: stop an agent that is about to cost you money or time you cannot get back.
- Three lines of code. Anything more is too much friction.
- In-process. No server, no daemon, no cloud account.
- Zero required deps. Drop it into any project.
- Framework agnostic. Adapters, not lock-in.
If you want traces, use Langfuse or Arize Phoenix. If you want routing, use LiteLLM. Magicrails composes with those; it doesn't replace them.
Examples
- examples/basic.py — minimal integration
- examples/budget_demo.py — watch Magicrails halt a token-burning loop
- examples/repeat_loop_demo.py — catch a stuck tool loop
Run any of them:
pip install -e .
python examples/budget_demo.py
Roadmap
- v0.1 — three detectors, OpenAI & Anthropic adapters (you are here)
- v0.2 — LangChain / LangGraph / CrewAI adapters, OpenTelemetry span processor, per-tool sub-budgets
- v0.3 — adaptive thresholds, multi-session dashboards
- v1.0 — optional local dashboard (Tauri) for live sessions, Slack/Discord control surface
Contributing
PRs for adapters, pricing updates, and new detectors are very welcome. See CONTRIBUTING.md for the full guide — including the recipe for adding a new framework adapter.
Quick start:
pip install -e '.[dev]'
pytest
ruff check magicrails tests
License
MIT. See LICENSE.
Save your agent from itself. pip install magicrails.
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 magicrails-0.1.0.tar.gz.
File metadata
- Download URL: magicrails-0.1.0.tar.gz
- Upload date:
- Size: 16.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d96c736361584b1c1c2e47fce6d0b67bb5113c1ba4a00b15b2f7b8112f71fa4e
|
|
| MD5 |
3f8b0c2da09b285a855aa5432e757c68
|
|
| BLAKE2b-256 |
450b98befa81be98f15dadc38691c3390f3ac9718c11a3272dbf29295383e4a9
|
Provenance
The following attestation bundles were made for magicrails-0.1.0.tar.gz:
Publisher:
release.yml on magicrails/magicrails
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
magicrails-0.1.0.tar.gz -
Subject digest:
d96c736361584b1c1c2e47fce6d0b67bb5113c1ba4a00b15b2f7b8112f71fa4e - Sigstore transparency entry: 1404688581
- Sigstore integration time:
-
Permalink:
magicrails/magicrails@e42a313319ec4f0fd22b0e792e1012370f45c533 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/magicrails
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e42a313319ec4f0fd22b0e792e1012370f45c533 -
Trigger Event:
push
-
Statement type:
File details
Details for the file magicrails-0.1.0-py3-none-any.whl.
File metadata
- Download URL: magicrails-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b78b5b5ccc3f8aecf17a64c39b693c157455eb3b6fc83747fc2eb8def7e9649e
|
|
| MD5 |
7e5e47ffff05e6e6dc633df4d4dfd597
|
|
| BLAKE2b-256 |
b1b42c266d3433eef323b1e6bd6ef621318319b43eac43db55c1383bb7e7ab80
|
Provenance
The following attestation bundles were made for magicrails-0.1.0-py3-none-any.whl:
Publisher:
release.yml on magicrails/magicrails
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
magicrails-0.1.0-py3-none-any.whl -
Subject digest:
b78b5b5ccc3f8aecf17a64c39b693c157455eb3b6fc83747fc2eb8def7e9649e - Sigstore transparency entry: 1404688681
- Sigstore integration time:
-
Permalink:
magicrails/magicrails@e42a313319ec4f0fd22b0e792e1012370f45c533 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/magicrails
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e42a313319ec4f0fd22b0e792e1012370f45c533 -
Trigger Event:
push
-
Statement type: