Skip to main content
Glama
README.md
# AgentGuard

Stop runaway agents with runtime checks in Python.

[![PyPI version](https://img.shields.io/pypi/v/agentguard47)](https://pypi.org/project/agentguard47/)
[![Python versions](https://img.shields.io/pypi/pyversions/agentguard47)](https://pypi.org/project/agentguard47/)
[![CI](https://github.com/bmdhodl/agent47/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/bmdhodl/agent47/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/github/license/bmdhodl/agent47)](LICENSE)

AgentGuard checks budgets, repeated tool calls, retries, and elapsed time in
instrumented Python code. Guards raise exceptions so your application can stop
the next operation. The base SDK has no runtime dependencies and needs no account.

**Names:** this repository is `agent47`, the PyPI package is `agentguard47`,
and the Python import is `agentguard`. AgentGuard 2.0.0 requires Python 3.11 or newer.
On Python 3.9 or 3.10, pin `agentguard47==1.4.0`. See the
[Python migration guide](docs/guides/python-311-migration.md) before upgrading.

## Getting started

Install in a virtual environment, then run the offline checks:

```bash
python -m pip install agentguard47
agentguard doctor
agentguard demo
```

`doctor` checks the installation and local trace writing. `demo` exercises
budget, loop, and retry stops without provider keys or network access. Follow
the trace path printed by the command to inspect its output.
`agentguard demo --feedback` prints a local redacted report; nothing is sent.

For a fresh PowerShell install and a runnable raw starter, follow the
[published-release walkthrough](docs/guides/try-release.md#windows-powershell).

### Share a stop receipt (new in 2.0.0)

`agentguard receipt agentguard_demo_traces.jsonl` prints a receipt of each stop
with the trace's SHA-256 drawn as a barcode. Add `--format markdown` to paste it
into a PR or issue. The hash identifies the trace file; it is not a signature.
This **stop receipt** records a covered guard refusal. Anthropic's official
[`/receipts` plugin](https://github.com/anthropics/claude-plugins-official/blob/main/plugins/receipts/README.md)
summarizes past Claude Code work (30 days by default). Neither output proves
prevented spend or provides a signed attestation. The `agentguard receipt`
command stays the same.

### Guard a Claude Code session (new in 2.0.0)

```bash
agentguard hook claude-code --install --write
```

This installs a Claude Code hook that refuses the third identical tool call in
a row and a call that already failed twice. "Identical" ignores JSON key order
and preserves case and whitespace in values. The optional `no_progress_max`
rule refuses a tool after repeated empty or same-error results even when input
changes; it is off by default. Refusals omit raw input and go to
`.agentguard/claude-code/trace.jsonl`. It
checks tool calls, not tokens or subscription quota. See the
[Claude Code hook guide](docs/guides/claude-code-hook.md).

### Guard a script without editing it (new in 2.0.0)

```bash
agentguard run --budget-usd 5 agent.py
```

This patches the OpenAI and Anthropic clients, then runs `agent.py` in the same
interpreter. Settings come from flags, then environment variables, then
`.agentguard.json`. A guard stop exits 1. Every run ends with the trace path on
stderr, ready for `agentguard receipt`. `agentguard run python -m mypkg` works too. The bounds are
the same as patching the client yourself; see
[enforcement boundary](docs/enforcement-boundary.md).

Subprocesses start a separate interpreter and are not patched by the parent.
A detached, uninstrumented child can keep running after the guarded script
exits. The [offline reproduction](docs/enforcement-boundary.md#remaining-exposure)
demonstrates that boundary with two simulated calls and no network access.

`agentguard run` prices every patched call at paid rates, including a local
model behind an OpenAI-compatible URL such as Ollama. A dollar budget can then
stop a model that costs nothing. For a local model, patch the client in the
script with `free_local_clients=[client]` and a `BudgetGuard` token or call
cap, then run the script with `python`; see
[free local clients](docs/guides/free-local-clients.md).

### Stop before a third call

Save this as `budget_demo.py` and run `python budget_demo.py`. It makes no
network requests.

```python
from agentguard import BudgetExceeded, BudgetGuard

budget = BudgetGuard(max_calls=2)
completed = 0

for _ in range(3):
    try:
        budget.check()  # Check before the operation.
        # Put your provider or tool call here.
        completed += 1
        budget.consume(calls=1)  # Record the completed operation.
    except BudgetExceeded:
        print(f"Stopped before call {completed + 1}")

assert completed == 2
```

Expected output: `Stopped before call 3`.

### Stop a reworded loop

A stuck agent often rewords each retry, so no call repeats exactly.
`max_no_progress` counts results instead: the same tool coming back empty, or
with the same error, N times in a row stops the run whatever the arguments.
`normalize=True` canonicalizes containers while preserving case and whitespace
in argument values. Both options are off by default.

```python
from agentguard import LoopDetected, LoopGuard

loop = LoopGuard(normalize=True, max_no_progress=3)

for pattern in ["retry_count", "retryCount", "retries"]:
    loop.check("grep", {"pattern": pattern})
    matches = []  # Put your search here. This one finds nothing.
    try:
        loop.record_result("grep", matches, tool_args={"pattern": pattern})
    except LoopDetected as stop:
        print(stop)
```

Expected output: `No progress: grep came back empty 3 times in a row (limit:
3). Change the approach instead of rewording
the call.` Functions wrapped with `trace_tool` feed results to the guard
automatically. Any other result in between starts the count over, so
pagination and edit-then-test cycles do not trip it.

### Connect a provider

Install the provider's client separately. For OpenAI:

```bash
python -m pip install openai
```

```python
from agentguard import BudgetGuard, JsonlFileSink, Tracer, patch_openai

budget = BudgetGuard(max_cost_usd=5.00)
tracer = Tracer(
    service="my-agent",
    sink=JsonlFileSink(".agentguard/traces.jsonl"),
)
patch_openai(tracer, budget_guard=budget)
# Make your OpenAI chat.completions.create or responses.create calls after this setup.
```

Standard OpenAI clients created earlier are covered too, including resource
objects obtained before the patch. Activate the patch before sending requests
or saving bound methods and raw/streaming response helpers. Those saved
references can retain the original callable; recreate them after activation.
`patch_openai_async()` covers async clients, and `agentguard.init()` enables
both. See the [enforcement boundary](docs/enforcement-boundary.md).

The patch checks recorded usage before dispatch and records response usage
afterward, including streamed calls once the final usage arrives. A response
can exceed the remaining cost or token allowance. Concurrent requests do not
reserve capacity. Chat Completions streams request `include_usage` unless the
caller already set it.

The OpenAI Agents SDK runs on `responses.create`, so `agentguard.init()` before
the `Runner` puts every model call under the budget
([example](examples/openai_agents_sdk_budget.py)). Hosted tools and
`background=True` responses are not covered. See the [getting started guide](docs/guides/getting-started.md)
for setup, traces, and framework starters.

## How enforcement works

```mermaid
flowchart TD
    accTitle: AgentGuard operation checks
    accDescr: Check a limit before an operation, then record usage.
    A[Instrumented operation] --> B{Guard check}
    B -->|Limit reached| C[Raise exception]
    B -->|Allowed| D[Run operation]
    D --> E[Record usage and trace]
    E --> A
```

Text equivalent: check before an operation, run it if allowed, then record
usage. A guard exception returns control to your application's error handler.

| Guard | Checks | Raises |
| --- | --- | --- |
| `BudgetGuard` | Recorded calls, tokens, or estimated cost | `BudgetExceeded` |
| `LoopGuard` | Repeated tool calls; opt in to `normalize=True` and `max_no_progress=N` for reworded ones | `LoopDetected` |
| `FuzzyLoopGuard` | Tool frequency and alternating patterns | `LoopDetected` |
| `RetryGuard` | Retries per tool | `RetryLimitExceeded` |
| `TimeoutGuard` | Elapsed time when checked | `TimeoutExceeded` |
| `RateLimitGuard` | Calls within a sliding minute | `BudgetExceeded` |
| `X402SpendGuard` | Payment amounts before the payment callback | `BudgetExceeded` |

For task budgets, use `BudgetGuard.goal(...)`. For signatures and defaults,
read the [guard source](sdk/agentguard/guards.py) and
[public exports](sdk/agentguard/__init__.py).

Budgets can also persist across runs: configure `JsonFileStateStore`, a shared
`key`, and `period="day"` for a daily counter. Set `warn_at_pct=0.8` and an
`on_warning` callback for an 80% warning. Workers must explicitly share the
store and key; child-agent budget inheritance is not automatic. Monthly SDK
limits are not implemented. See the [capability matrix](docs/enforcement-boundary.md#budget-scope-in-published-200).

## Limits and security

- Guards cover operations you instrument. Installing the package does not
  intercept every action in Cursor, Claude Code, or another agent.
- A guard is not a sandbox or permission system. A permitted operation can
  still be destructive.
- Timeout checks do not interrupt an already blocked function or cancel an
  agent running on a provider's server.
- Cost estimates are not invoices. Supply reported cost or use strict cost
  resolution when an estimate is insufficient.
- Recorded-budget preflight refuses the next instrumented call when stored
  usage is already at a cap. It does not reserve concurrent in-flight
  requests, predict the next response, or cap a provider subscription.
  See the [enforcement boundary](docs/enforcement-boundary.md).
- The base SDK uses the standard library. Optional framework extras install
  third-party dependencies and need their own security review.
- The optional `[crewai]` extra pulls ChromaDB. The
  [2026-09-12 audit](proof/audit-20260912/README.md) found four unresolved
  advisories, including [PYSEC-2026-311 / CVE-2026-45829](https://github.com/advisories/GHSA-f4j7-r4q5-qw2c).
  Review that exposure before installing the extra. Base SDK installs do not
  include ChromaDB.
- Trace content can contain application data. Review it before sharing or
  configuring a remote sink.

See [security reporting](SECURITY.md), the
[dated dependency audit](proof/audit-20260912/README.md), and
[release notes](CHANGELOG.md). Audit results describe their recorded date,
not a permanent clean bill of health.

## Local traces and optional hosted ingest

The SDK is the free local proof path. Start local. Add hosted ingest only
when you need retained history, alerts, team visibility, spend trends,
hosted decision history, or dashboard-managed remote kill signals.

Local guards remain authoritative. `HttpSink` mirrors trace and decision events;
it does not execute remote kill signals by itself. See the
[dashboard contract](docs/guides/dashboard-contract.md) before configuring it.

Local use has no hosted event quota, retention period, or API-key allocation.
Network egress requires an integration you configure, such as `HttpSink` or
an OpenTelemetry exporter.

Nothing in the local SDK phones home. The
[AgentGuard website](https://bmdpat.com/tools/agentguard?utm_source=agentguard47&utm_medium=readme&utm_campaign=touchpoints)
describes the optional hosted service.

## Documentation

| You want to | Start here |
| --- | --- |
| See which paths actually stop a call | [Enforcement boundary](docs/enforcement-boundary.md) |
| Install and trace a first run | [Getting started](docs/guides/getting-started.md) |
| Find guides and source references | [Documentation index](docs/README.md) |
| Try a runnable example | [Examples](examples/) |
| Connect LangChain, LangGraph, or CrewAI | [Integration guides](docs/integrations/) |
| Inspect hosted data through MCP | [Read-only TypeScript MCP server](mcp-server/) |
| Use local budget tools through MCP | [Python budget MCP server](agentguard-mcp/) |
| Navigate with an AI assistant | [AI documentation index](llms.txt) |
| Contribute a fix | [Contributing](CONTRIBUTING.md) |
| Check what changed | [Changelog](CHANGELOG.md) |

## Help and maintenance

Maintained by [Patrick Hughes](https://github.com/bmdhodl).
[Report a bug](https://github.com/bmdhodl/agent47/issues) with the package
version, a minimal reproduction, and the expected result. Report vulnerabilities
through [SECURITY.md](SECURITY.md).

The source metadata defines the branch version. The PyPI badge links to the
published version. Documentation examples and local links are tested in CI.
The PyPI README is generated from this README and the changelog.

[MIT license](LICENSE).

TDQS

A4.2/5.0

Scored across 7 tools

Disambiguation5/5

Each tool targets a distinct read-only aspect: trace search, full trace retrieval, decision events, alerts, usage, costs, and budget health check. The descriptions clearly separate them even where they overlap (e.g., usage vs. costs are explicitly differentiated).

Naming Consistency4/5

All tools follow a verb_noun pattern with mostly 'get_' prefixes; query_traces and check_budget deviate slightly but are still predictable. The naming is consistent enough that an agent would not be confused.

Tool Count5/5

Seven tools is well-scoped for a read-only monitoring API. Each tool has a clear, non-redundant purpose, covering search, detail, decisions, alerts, usage, costs, and budget—neither too few nor too many.

Completeness5/5

For a read-only trace and monitoring surface, the tool set is complete: it covers listing/searching traces, retrieving full traces, filtering decisions, and accessing alerts, usage, costs, and budget status. No obvious read operations are missing for the stated purpose.

Maintenance

ActivityActive
ResponsivenessWithin a week