Zero-dependency observability and runtime guards for AI agents — loop detection, budget enforcement, cost tracking, and structured tracing
Project description
AgentGuard
Your AI agent just burned $200 in one run. AgentGuard would have stopped it at $5.
Set a dollar budget. Get warnings at 80%. Kill the agent when it exceeds the limit. Zero dependencies, works with any framework.
pip install agentguard47
Verify your install
Before wiring a real agent, validate the local SDK path:
agentguard doctor
doctor makes no network calls. It verifies local trace writing, confirms the
SDK can initialize in local-only mode, detects optional integrations already
installed in your environment, and prints the smallest correct next-step snippet.
Generate a starter
When you know the stack you want to wire, print the exact starter snippet:
agentguard quickstart --framework raw
agentguard quickstart --framework openai
agentguard quickstart --framework langgraph --json
quickstart is designed for both humans and coding agents. It prints the
install command, the smallest credible starter file, and the next commands to
run after you validate the SDK locally.
Coding-Agent Defaults
If you want humans and coding agents to share the same safe local defaults, add
a tiny .agentguard.json file to the repo:
{
"profile": "coding-agent",
"service": "support-agent",
"trace_file": ".agentguard/traces.jsonl",
"budget_usd": 5.0
}
agentguard.init(local_only=True) and agentguard doctor will pick this up
automatically. Keep it local and static: no secrets, no API keys, no dashboard
settings.
Every agentguard quickstart --framework ... payload also has a matching
runnable file under examples/starters/. Those starter
files live in the repo for copy-paste onboarding and coding-agent setup; they
are not shipped inside the PyPI wheel.
For the repo-first onboarding flow, see
docs/guides/coding-agents.md.
Try it in 60 seconds
No API keys. No dashboard. No network calls. Just run it:
pip install agentguard47
agentguard demo
AgentGuard offline demo
No API keys. No dashboard. No network calls.
1. BudgetGuard: stopping runaway spend
warning fired at $0.84
stopped on call 9: cost $1.08 exceeded $1.00
2. LoopGuard: stopping repeated tool calls
stopped on repeated tool call: Loop detected ...
3. RetryGuard: stopping retry storms
stopped retry storm: Retry limit exceeded ...
Local proof complete.
Prefer the example script instead of the CLI? This does the same local demo:
python examples/try_it_now.py
Quickstart: Stop Runaway Costs in 4 Lines
from agentguard import Tracer, BudgetGuard, patch_openai
tracer = Tracer(guards=[BudgetGuard(max_cost_usd=5.00, warn_at_pct=0.8)])
patch_openai(tracer) # auto-tracks every OpenAI call
# Use OpenAI normally — AgentGuard tracks cost and kills the agent at $5
That's it. Every ChatCompletion call is tracked. When accumulated cost hits $4 (80%), your warning fires. At $5, BudgetExceeded is raised and the agent stops.
No config files. No dashboard required. No dependencies.
For a deterministic local proof before wiring a real agent, run:
agentguard doctor
agentguard quickstart --framework raw
agentguard demo
agentguard doctor verifies the install path. agentguard quickstart prints
the copy-paste starter for your stack. agentguard demo then proves SDK-only
enforcement with a realistic local run. The dashboard remains the control plane
for alerts, retained history, and remote controls.
The Problem
AI agents are expensive and unpredictable:
- Cost overruns average 340% on autonomous agent tasks (source)
- A single stuck loop can burn through your entire OpenAI budget in minutes
- Existing tools (LangSmith, Langfuse, Portkey) show you the damage after it happens
AgentGuard is the only tool that kills agents mid-run when they exceed spend limits.
| AgentGuard | LangSmith | Langfuse | Portkey | |
|---|---|---|---|---|
| Hard budget enforcement | Yes | No | No | No |
| Kill agent mid-run | Yes | No | No | No |
| Loop detection | Yes | No | No | No |
| Cost tracking | Yes | Yes | Yes | Yes |
| Zero dependencies | Yes | No | No | No |
| Self-hosted option | Yes | No | Yes | No |
| Price | Free (MIT) | $2.50/1k traces | $59/mo | $49/mo |
Guards
Guards are runtime checks that raise exceptions when limits are hit. The agent stops immediately.
| Guard | What it stops | Example |
|---|---|---|
BudgetGuard |
Dollar/token/call overruns | BudgetGuard(max_cost_usd=5.00) |
LoopGuard |
Exact repeated tool calls | LoopGuard(max_repeats=3) |
FuzzyLoopGuard |
Similar tool calls, A-B-A-B patterns | FuzzyLoopGuard(max_tool_repeats=5) |
TimeoutGuard |
Wall-clock time limits | TimeoutGuard(max_seconds=300) |
RateLimitGuard |
Calls-per-minute throttling | RateLimitGuard(max_calls_per_minute=60) |
RetryGuard |
Retry storms on the same flaky tool | RetryGuard(max_retries=3) |
from agentguard import BudgetGuard, BudgetExceeded
budget = BudgetGuard(
max_cost_usd=10.00,
warn_at_pct=0.8,
on_warning=lambda msg: print(f"WARNING: {msg}"),
)
# In your agent loop:
budget.consume(tokens=1500, calls=1, cost_usd=0.03)
# At 80% → warning callback fires
# At 100% → BudgetExceeded raised, agent stops
from agentguard import RetryGuard, RetryLimitExceeded, Tracer
retry_guard = RetryGuard(max_retries=3)
tracer = Tracer(guards=[retry_guard])
with tracer.trace("agent.run") as span:
try:
span.event("tool.retry", data={"tool_name": "search", "attempt": 1})
span.event("tool.retry", data={"tool_name": "search", "attempt": 2})
span.event("tool.retry", data={"tool_name": "search", "attempt": 3})
span.event("tool.retry", data={"tool_name": "search", "attempt": 4})
except RetryLimitExceeded:
# Retry storm stopped
pass
Integrations
LangChain
pip install agentguard47[langchain]
from agentguard import Tracer, BudgetGuard
from agentguard.integrations.langchain import AgentGuardCallbackHandler
tracer = Tracer(guards=[BudgetGuard(max_cost_usd=5.00)])
handler = AgentGuardCallbackHandler(
tracer=tracer,
budget_guard=BudgetGuard(max_cost_usd=5.00),
)
# Pass to any LangChain component
llm = ChatOpenAI(callbacks=[handler])
LangGraph
pip install agentguard47[langgraph]
from agentguard.integrations.langgraph import guarded_node
@guarded_node(tracer=tracer, budget_guard=BudgetGuard(max_cost_usd=5.00))
def research_node(state):
return {"messages": state["messages"] + [result]}
CrewAI
pip install agentguard47[crewai]
from agentguard.integrations.crewai import AgentGuardCrewHandler
handler = AgentGuardCrewHandler(
tracer=tracer,
budget_guard=BudgetGuard(max_cost_usd=5.00),
)
agent = Agent(role="researcher", step_callback=handler.step_callback)
OpenAI / Anthropic Auto-Instrumentation
from agentguard import Tracer, BudgetGuard, patch_openai, patch_anthropic
tracer = Tracer(guards=[BudgetGuard(max_cost_usd=5.00)])
patch_openai(tracer) # auto-tracks all ChatCompletion calls
patch_anthropic(tracer) # auto-tracks all Messages calls
Cost Tracking
Built-in pricing for OpenAI, Anthropic, Google, Mistral, and Meta models. Updated monthly.
from agentguard import estimate_cost
# Single call estimate
cost = estimate_cost("gpt-4o", input_tokens=1000, output_tokens=500)
# → $0.00625
# Track across a trace — cost is auto-accumulated per span
with tracer.trace("agent.run") as span:
span.cost.add("gpt-4o", input_tokens=1200, output_tokens=450)
span.cost.add("claude-sonnet-4-5-20250929", input_tokens=800, output_tokens=300)
# cost_usd included in trace end event
Tracing
Full structured tracing with zero dependencies — JSONL output, spans, events, and cost data.
from agentguard import Tracer, JsonlFileSink, BudgetGuard
tracer = Tracer(
sink=JsonlFileSink("traces.jsonl"),
guards=[BudgetGuard(max_cost_usd=5.00)],
)
with tracer.trace("agent.run") as span:
span.event("reasoning", data={"thought": "search docs"})
with span.span("tool.search", data={"query": "quantum computing"}):
pass # your tool logic
span.cost.add("gpt-4o", input_tokens=1200, output_tokens=450)
$ agentguard report traces.jsonl
AgentGuard report
Total events: 9
Spans: 6 Events: 3
Estimated cost: $0.01
Savings ledger: exact 800 tokens / $0.0010, estimated 1500 tokens / $0.0075
When a run trips a guard or needs escalation, render a shareable incident report:
agentguard incident traces.jsonl
agentguard incident traces.jsonl --format html > incident.html
The incident report summarizes guard triggers, exact-vs-estimated savings, and the dashboard upgrade path for retained alerts and remote kill switch.
Evaluation
Assert properties of your traces in tests or CI.
from agentguard import EvalSuite
result = (
EvalSuite("traces.jsonl")
.assert_no_loops()
.assert_budget_under(tokens=50_000)
.assert_completes_within(seconds=30)
.assert_total_events_under(500)
.assert_no_budget_exceeded()
.assert_no_errors()
.run()
)
agentguard eval traces.jsonl --ci # exits non-zero on failure
CI Cost Gates
Fail your CI pipeline if an agent run exceeds a cost budget. No competitor offers this.
# .github/workflows/cost-gate.yml (simplified)
- name: Run agent with budget guard
run: |
python3 -c "
from agentguard import Tracer, BudgetGuard, JsonlFileSink
tracer = Tracer(
sink=JsonlFileSink('ci_traces.jsonl'),
guards=[BudgetGuard(max_cost_usd=5.00)],
)
# ... your agent run here ...
"
- name: Evaluate traces
uses: bmdhodl/agent47/.github/actions/agentguard-eval@main
with:
trace-file: ci_traces.jsonl
assertions: "no_errors,max_cost:5.00"
Full workflow: docs/ci/cost-gate-workflow.yml
Incident Reports
Turn a trace into a postmortem-style incident summary:
agentguard incident traces.jsonl --format markdown
agentguard incident traces.jsonl --format html > incident.html
Use this when a run hits guard.budget_warning, guard.budget_exceeded,
guard.loop_detected, or a fatal error. AgentGuard will summarize the run,
separate exact and estimated savings, and suggest the next control-plane step.
Async Support
Full async API mirrors the sync API.
from agentguard import AsyncTracer, BudgetGuard, patch_openai_async
tracer = AsyncTracer(guards=[BudgetGuard(max_cost_usd=5.00)])
patch_openai_async(tracer)
# All async OpenAI calls are now tracked and budget-enforced
Production: Dashboard + Kill Switch
For teams that need centralized monitoring, alerts, and remote kill switch:
from agentguard import Tracer, HttpSink, BudgetGuard
tracer = Tracer(
sink=HttpSink(
url="https://app.agentguard47.com/api/ingest",
api_key="ag_...",
batch_size=20,
flush_interval=10.0,
compress=True,
),
guards=[BudgetGuard(max_cost_usd=50.00)],
metadata={"env": "prod"},
sampling_rate=0.1, # 10% of traces
)
| Trial (14d free) | Pro ($39/mo) | Team ($79/mo) | |
|---|---|---|---|
| SDK + local guards | Unlimited | Unlimited | Unlimited |
| Dashboard events | 500K/mo | 500K/mo | 5M/mo |
| Budget alerts (email/webhook) | Yes | Yes | Yes |
| Remote kill switch | Yes | Yes | Yes |
| Team members | 1 | 1 | 10 |
Start free trial | View pricing
Architecture
Your Agent Code
│
▼
┌─────────────────────────────────────┐
│ Tracer / AsyncTracer │ ← trace(), span(), event()
│ ┌───────────┐ ┌────────────────┐ │
│ │ Guards │ │ CostTracker │ │ ← runtime intervention
│ └───────────┘ └────────────────┘ │
└──────────┬──────────────────────────┘
│ emit(event)
┌──────┼──────────┬───────────┐
▼ ▼ ▼ ▼
JsonlFile HttpSink OtelTrace Stdout
Sink (gzip, Sink Sink
retry)
What's in this repo
| Directory | Description | License |
|---|---|---|
sdk/ |
Python SDK — guards, tracing, evaluation, integrations | MIT |
mcp-server/ |
MCP server — agents query their own traces | MIT |
site/ |
Landing page | MIT |
Dashboard is in a separate private repo (agent47-dashboard).
Security
- Zero runtime dependencies — one package, nothing to audit, no supply chain risk
- OpenSSF Scorecard — automated security analysis on every push
- CodeQL scanning — GitHub's semantic code analysis on every PR
- Bandit security linting — Python-specific security checks in CI
Contributing
See CONTRIBUTING.md for dev setup, test commands, and PR guidelines.
License
MIT (BMD PAT LLC)
Latest Release Notes (1.2.4)
Coding-Agent Onboarding
- Added repo-local
.agentguard.jsonsupport so humans and coding agents can share static SDK defaults without dashboard coupling. - Added the built-in
coding-agentprofile with tighter loop and retry defaults for repo automation and coding workflows. - Added executable starter files under
examples/starters/and alignedagentguard doctor/agentguard quickstartaround.agentguard/traces.jsonl. - Added the
docs/guides/coding-agents.mdonboarding guide plus doc updates across the README, SDK README, examples, architecture doc, roadmap, and generated PyPI README.
SDK Hardening
JsonlFileSinknow creates parent directories automatically so repo-local trace paths like.agentguard/traces.jsonlwork out of the box.- Repo-config parsing now rejects boolean values in numeric fields to keep local defaults deterministic and auditable.
init()now still honors repo-level profile defaults when service, budget, or trace path are passed explicitly but guard-profile values are left implicit.- Invalid
AGENTGUARD_BUDGET_USDvalues now fall back to a valid repo-localbudget_usdinstead of silently dropping budget enforcement.
Full changelog: CHANGELOG.md
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 agentguard47-1.2.4.tar.gz.
File metadata
- Download URL: agentguard47-1.2.4.tar.gz
- Upload date:
- Size: 124.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9eee7469bda8021666f4bae2a83b45d776e1271a9c1ed3bb1b0940c274c28ee7
|
|
| MD5 |
14a87462f16d7f51c0ed20b46aafb09f
|
|
| BLAKE2b-256 |
4f2075322e5f8c7b7ef37dd4813e3ef88e3bb80b421f7c71f5ba188b7fc1f1f3
|
File details
Details for the file agentguard47-1.2.4-py3-none-any.whl.
File metadata
- Download URL: agentguard47-1.2.4-py3-none-any.whl
- Upload date:
- Size: 66.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
62d71955b5e418a490f95c513e740c3d285b2b8eabb58353175c0298f6610b22
|
|
| MD5 |
46133bd852d7cdf289ee297e327df76d
|
|
| BLAKE2b-256 |
1aed84190d324efade204edbdc8dd43719fbd1c9fba7011bcd8164c63c80adea
|