Inspiration

Every agentic-payments demo eventually goes to prod and blows up the founder's bank account. The reason is always the same: there is no leash. The agent holds the full API key, with no per-call budget, no host allowlist, and no audit. I kept seeing the same failure across data-to-AI pipelines where an agent drives real spend, so I built the small library that refuses to let the agent do something expensive or off-policy, and writes down what happened.

What it does

agentleash wraps an agent's tool calls in a single context manager and enforces four independent guards. A USD cap stops the run when projected spend would exceed the budget. A call cap stops it after N tool calls. An egress allowlist refuses any host not on the list (wildcards like *.stripe.com supported). A per-call JSON schema rejects malformed tool args before the handler runs. Every event, allowed or denied, is appended to an audit JSONL with one line per event. If the agent tries to overspend, over-call, reach an unlisted host, or pass bad args, the session raises a typed exception, the agent stops, and the denial is logged. Args are hashed by default so the audit log is safe to share without leaking PII.

How I built it

Pure Python with a tiny dependency surface. The core installs with effectively zero runtime deps beyond a token-budget helper; tool-arg validation pulls in jsonschema as an extra, and the egress check ships as an optional httpx transport so it slots into an existing client. Each guard lives behind one context manager and composes independently, so you use whichever subset you need. The audit log shape is a closed set of event kinds (tool_ok, tool_denied, budget_denied, session_close) so a reviewer or SIEM can parse it without guessing. 11 tests cover the budget, egress, schema, and audit paths.

Challenges I ran into

The hard part was keeping the guards genuinely independent while still letting them share one session and one audit sink. Early versions coupled the budget check to the egress check and the API got confusing fast. I split each into its own primitive with its own typed exception (BudgetExceededError, EgressDeniedError, ToolArgsInvalidError) so a caller can reason about exactly which guard fired. The second challenge was making the audit log safe to share: logging raw tool args leaks customer IDs and amounts, so I hash args by default and gate verbatim logging behind an explicit env flag.

Accomplishments that I'm proud of

The whole thing is two lines to adopt and you cannot accidentally bypass a guard once the session wraps the call. The typed exceptions mean failure is legible, not a generic stack trace. The audit log is a real artifact a security team can replay, not a print statement. And it stays honest about scope: it is not a model wrapper or a payment SDK, just the leash around calls the agent already makes.

What I learned

Safety for spend-capable agents is mostly a composition problem, not a model problem. The model will try whatever it tries; the value is in the deterministic layer that refuses and records. Keeping each guard a single-responsibility primitive with its own typed error made the library easier to trust and far easier to test. Hashing args by default turned the audit log from a liability into something shareable.

What's next for agentleash

A rate-limit guard to sit alongside the budget and call caps. First-class adapters for the major agent frameworks so the session wraps their tool-call loop directly. An IP-range allowlist on top of the hostname allowlist. Optional pairing with GeminiLens so per-token model cost flows straight into the USD cap instead of being passed in by the caller.

Built With

  • agent-governance
  • ai-agents
  • audit-trail
  • cost-control
  • egress-allowlist
  • httpx
  • jsonschema
  • llm
  • mit-license
  • observability
  • python
Share this project:

Updates