Yesterday a program I wrote spent my money without asking me.
That was the point. It needed a piece of data, the server answered 402 Payment Required with a price, the program checked that price against the limits I had set, decided it was allowed, signed a payment, and got its answer. Cost: $0.005. There is a transaction hash on Base you could look up right now.
Letting an agent pay for things turns out to be the easy half. The half worth writing about is the part that tells you what it was not allowed to do.
Why a spending agent is a different kind of scary
Most AI safety conversations are about what a model says. This is about what it spends, and the failure mode is much simpler: a loop that should have run four times runs forty thousand, or a provider quietly raises its price by a factor of a thousand, or a compromised endpoint asks for everything in the wallet.
None of those need malice. A retry loop and a bad assumption will do it.
So before the agent can pay for anything, four things have to be true, and they are checked in code rather than promised in a README:
- A per-call ceiling. Set just above the real price, not comfortably above it. A generous ceiling silently approves a provider that raises its price; a tight one turns that into a refusal with the amount that caused it.
- A daily cap and a lifetime cap. The wallet holds more than the lifetime cap on purpose. Most of the balance is unreachable by design rather than by care.
- An endpoint allowlist. Default deny. The protocol has no buyer-side check on who receives the money, so this is the control that stops a hijacked agent paying an attacker.
- An append-only log of every decision, refusals included.
That last one is the whole thing, and it is the one people skip.
Why the refusals are the product
A log that only contains successful payments cannot tell you whether the guard ever worked.
Think about what you would see if the spend cap had been deleted entirely: payments going through, money moving, a page full of green. Exactly what you see when it is working perfectly. The two states are indistinguishable from the outside, which means the log is decoration.
Put the refusals in and it inverts. Now the record answers the question a finance person actually asks — what was it stopped from doing? — and answers it with evidence rather than reassurance. Anybody can tell you their agent has a budget. Very few can show you the thing it was refused.
That is the difference between a control and a promise, and it is worth more than the payment rail it sits on.
Three bugs, none of them in the guardrails
The safety layer worked first time. Everything around it had rotted.
A dead import demanded an account nothing used. The client insisted on a Coinbase Developer Platform account before it would run live. It imported their SDK, never called it, and even carried a linter suppression on the line admitting the import was unused. The actual signing had always been done with a plain local key. That one import sent me off to create an account, generate an API key, and wonder why the thing still would not run.
An error message named a file nothing read. "Set your key in .env," it said. Nothing in the project ever opened .env. Following the instruction produced the identical failure to ignoring it — which is worse than no instruction at all, because it makes you doubt the step you did correctly.
The SDK had moved two major versions. The payment call was written against an API that no longer exists, and the function that replaced it is deprecated with the reason stated in its own docstring: event hooks cannot modify responses, so a hook can never swap the 402 for the paid 200. The working path is a transport wrapper.
Three failures, and the pattern is the same in all three: the code was correct about the world as it was when someone wrote it. Nothing had gone wrong. The world moved.
The one that would have fooled me
Once it was live I built a dashboard panel to watch the spend. It immediately reported four times more money spent than had actually left the wallet.
The dry-run mode — the safe rehearsal, the one you run precisely because it does not spend anything — was writing its decisions to the same log as the real payments, in the same shape. So the spend total counted rehearsals as money.
That was not a display bug. The guard reads that same log to decide whether there is room under the cap. Which means rehearsing the demo consumed the real budget. Run the safe version enough times and the guard starts refusing genuine payments, having spent nothing at all — and that refusal looks exactly like a legitimate one. You would conclude you were out of money, or that the guard was broken, and both conclusions would be wrong.
The fix is one field: record whether a decision was a rehearsal, and skip those when counting. The decision still goes in the log — the record of what was authorised is the entire point — it just stops pretending to be money that moved.
I only found it because the panel put a number next to something I could check independently. A dashboard that agrees with itself is not evidence.
Is any of this worth doing yet?
Honestly: as a business, not yet, and I would rather say so than sell you a trend.
73% of the endpoints in this ecosystem have exactly one paying user in a month — the developer testing their own. It is early. Anyone showing you revenue projections is counting listings and calling them customers.
What it is worth is the shape of the problem. Agents that take real actions — spending, sending, posting, buying — are arriving whether or not the payment rail matters, and every one of them needs the same thing: a boundary decided in code, by rules a person can read, with a record of every time the boundary was hit. That problem is not speculative and it is not going away.
The rail was a good excuse to build the boundary.
If you are pointing an agent at anything that matters
Ask one question of whatever guard you have around it: if this were completely broken, what would I see?
If the answer looks the same as working — silence, green ticks, a log of successes — then it is not a guard, it is a decoration, and you will find out which the expensive way.
If you want a second pair of eyes on something you have already shipped, the audit tool on this site is free and takes about thirty seconds — no email, it just loads your page the way a phone would and tells you what it finds.
And if you have an AI feature in production that nobody has tried to break yet, that is the conversation I would rather have.




