v0.1.0 - policy enforcement for agent spending
First tagged release of bouncer — a policy enforcement point that sits between an AI agent and a payment rail, blocks transactions that violate a declarative policy, and writes a tamper-evident, signed audit log of every decision.
It never custodies funds.
What works
Pure policy engine. evaluate(intent, policy, history, now=...) performs no I/O, reads no clock and calls no model, so the same inputs always produce the same decision. Deny-by-default throughout: an unnamed agent cannot spend, a misspelled rule name is a load error rather than a silently-absent restriction, and an empty or malformed policy denies everything. Per-transaction caps, rolling windows, merchant and category allow/denylists, and time windows with real timezones.
Tamper-evident audit log. Every decision is one SQLite row, SHA-256 hashed over canonical JSON, chained to its predecessor and Ed25519-signed. bouncer verify walks the chain and names the first broken row, distinguishing a sequence gap from a broken link, altered content and a bad signature. Exports to line-delimited JSON that re-verifies standalone from the operator's public key.
Signed mandates. Scoped to one merchant and one ceiling, short TTL, single use. Replay protection rests on an atomic primary-key insert rather than a check-then-write, so two concurrent redemptions cannot both succeed.
Human-in-the-loop approvals. Over-threshold decisions park in a queue tagged with the role that may resolve them. Approve and deny run the identical role check — no asymmetric authority where vetoing is easier than approving — and resolution is once-only. A blocking /authorize times out into a deny, never into an allow.
Client library. Client.spend() is a context manager: a denial raises and the guarded block never runs. A returned verdict can be ignored by forgetting to check it, and an ignored denial is an unenforced policy.
Interfaces. A CLI with 12 commands and distinct exit codes, a FastAPI sidecar, and an HTTP forward proxy for plaintext traffic.
Trust boundary
bouncer is the policy decision point; your network is the enforcement point.
- It never custodies funds — it authorizes, something else settles.
- It cannot stop an agent that bypasses it. Containment requires egress control at the network or container layer.
- It does not replace your payment provider's controls. Run both.
- Nothing here has been security audited. The Stripe adapter refuses live-mode keys on purpose.
See SECURITY.md for the disclosure process and for what does and does not count as a vulnerability.
Known limits
- Tail truncation of the audit log is not detectable from the log alone. Record the head hash externally and pass it back with
bouncer verify --expect-head. - CONNECT tunnels cannot be policed, so they are denied by default. With
--allow-connectthe traffic inside them is unenforced. Enforcing HTTPS payment traffic needs TLS termination, which this release does not do. - The API authenticates nobody.
agent_idis an assertion by the caller. Bind to loopback and treat network reachability as the boundary. - x402 over the proxy is not fully enforceable — a follow-up payment header names an amount in atomic units without asset decimals, and bouncer denies what it cannot price.
docs/v01-audit.md records the remaining gaps against the v0.1 scope, including four open architectural decisions.
Verification
246 tests, 88% statement coverage, mypy --strict clean across 37 source files. CI runs the suite, the type checker and the demo end to end on Ubuntu and Windows against Python 3.11 and 3.12.
Install
pip install agent-bouncer
bouncer initThe distribution is agent-bouncer because bouncer on PyPI is an unrelated 2014 authorization library. The import is import bouncer either way.