Not a checklist — a failure-mode map. Each way an agent overspends, why naive limits don't catch it, and the control that does.
"My agent spent too much" is the symptom. The causes are a small set of repeatable failure modes — and most of them slip past a single dollar cap. Preventing overspending means understanding each mode and putting the right control at the right boundary.
| Failure mode | Why naive limits fail | Required control |
|---|---|---|
| 1. Retry storms a failing request retried until it succeeds or the budget dies |
Every retry is individually under the cap; the sequence isn't. | Cumulative budget window (per agent, per day), checked atomically outside the agent loop. A retry that would exceed the window is denied even if the cap allows it. |
| 2. Parallel execution many agents/actions spending at once |
Each request reads "budget remaining" and all of them pass. | Reserve-and-commit: the decision updates the budget in the same atomic step as the check. Concurrency can't race a counter that is decremented at decision time. |
| 3. Transaction splitting one large payment divided into cap-sized pieces |
A per-transaction cap counts each piece, not the total intent. | Budget windows measure cumulative totals; splitting just spends the window faster and gets caught at the boundary like any other spending. |
| 4. Wrong recipient amount is fine, destination is not (typo, tampering, new address) |
Limits never ask who gets paid. | Merchant/recipient policy: allow and block lists, deny-by-default for unknown destinations, and a human line for new recipients. |
| 5. Prompt injection / malicious instructions the model "believes" it should pay |
The model is the attacked party; telling it to be careful is asking the victim to enforce policy. | Deterministic policy code the agent cannot argue with or override, with new-recipient and over-line payments routed to a human. The decision never comes from the model. |
| 6. Approval replay one human yes executed twice (retry, crash recovery, second agent) |
Approval stored as a message or a flag can be used any number of times. | Signed single-use grant: approval binds the exact request, the executor consumes it once, and the same grant again is refused. |
Each failure mode above is stopped by at least one of these layers — and most need the last one, because a control the execution path can ignore is an opinion, not a gate.
agent proposes payment (amount, recipient)
│
▼
authorize() ── atomic decision outside the agent loop
│ budget window · per-tx cap · recipient policy · approval line · agent identity
├── DENY → stops modes 1-4, 6
├── APPROVAL → human, re-checked against live policy → stops 5-6
└── ALLOW → signed single-use grant
│
▼
executor.verify(grant) ← consumes once; replay refused (mode 6)
│
▼
rail executes
# policy.yaml
version: "2.1.0"
policy:
budget: { daily: 100 } # stops retry storms & splitting
transaction: { max: 50 }
merchants:
allowed: [openai.com, mcdonalds.com]
blocked: [scam-vip.com] # stops wrong recipient
approval: { over: 30 } # stops injection on big spends
agents:
research-agent:
budget: { daily: 50 }
rate_limit: { window_s: 3600, max_calls: 10 }
from spendshield import SpendShield
shield = SpendShield(dry_run=False)
shield.load_policy("policy.yaml")
res = shield.authorize(agent="research-agent", amount=45, to="scam-vip.com")
# → DENY: blocked merchant — no grant, nothing executes
SpendShield is an open-source implementation of this failure-mode map: budgets and caps, merchant policy, bounded approval, signed single-use grants, replay protection and execution-boundary enforcement — Python + MCP, MIT. See the full Agent Payment Failure Map or test the rules in the playground.
pip install spendshielduvx --from spendshield spendshield-mcp --policy policy.yaml