I published two articles three weeks apart. They were not about the same thing.
The first was about agent guardrails that read the command string and miss the money. A refund for £40 and a refund for £40,000 are the same shape, and a hook that pattern-matches arguments cannot tell you which one is a catastrophe.
The second was about guardrails nobody checks the liveness of. A lint rule that stopped running looks exactly like a lint rule that found nothing.
Different subjects. Different threads. As far as I can tell, no overlap at all in who turned up to argue.
Both threads ended up building the same object. Nobody involved said the word "payments".
What the first thread built
Three weeks ago @peterbuildssecure left this on the guardrails post:
Preview resolves the proposed effect and returns an operation ID plus the subject, target, amount, currency, relevant state version and expiry. Human approval produces a single-use mandate bound to that exact operation. Execute consumes it atomically while rechecking the authoritative state and idempotency key.
Then the part I think is the most important sentence in either thread:
the server should require the mandate whether or not the client noticed that metadata. Otherwise the convention protects careful clients while direct callers retain the bypass.
What the second thread built
Yesterday, on a completely different post, @anp2network argued that you should stop trying to carry a reachability guarantee across a process boundary, because it cannot survive the trip:
The service that performs the effect refuses anything that does not arrive carrying evidence the guard ran, and it re-checks that evidence itself rather than trusting the caller was well behaved. Caller discipline buys nothing once there is a second caller you did not write. The evidence has to be bound to the specific instruction, and it has to name the policy revision it was checked against.
Read those two next to each other.
Single-use token, bound to one specific operation, issued by the side that owns the state, presented at the point of effect, re-verified there rather than trusted, rejected when stale.
That is one design, described twice, by two people who have never spoken to each other, three weeks apart, on two posts about different problems.
It is also a payment authorisation. It has been a payment authorisation since roughly 1979.
The other two got further than I did
@jon_at_backboardio went straight past the single-operation model to its weak point:
five refunds of £8,000 each, every one under every cap, same payee, four minutes. every individual call is boring. the mandate model catches that only if mandates are scoped to a window rather than to an operation
He also settled the sync-versus-async argument in one observation: the calls where blocking hurts are high-volume and low-value, and the calls where blocking is fine are the ones you want to stop. Those two sets barely overlap, so you pay the latency on a rounding error of your traffic.
And @max_quimby, who started the whole thing, put his finger on why none of this is really an AI problem:
None of that is agent-specific, which I think is the point — the agent just removes the human who used to eyeball the number.
Payments did not design this. It lost money until it existed.
Every piece these four independently derived is something the card networks were forced into, usually after being robbed.
Bound to the instruction. An authorisation is for an amount and a payee. You cannot get one for £40 and present it for £4,000. That is not elegance, it is scar tissue from people doing exactly that.
Verified by the side holding the state. The merchant does not decide whether an authorisation is good. The issuer does, because the issuer is the only party that knows the balance. Peter's "the server should require the mandate whether or not the client noticed" is that rule, stated fresh, forty years later.
Single-use. Replay is the oldest attack there is.
Expiry. This one gets skipped in every clean-room design and it is load-bearing. Without it your set of still-valid mandates only grows, and a list that only grows becomes a list nobody audits. Expiry is what keeps that set small enough to reason about. You trade a list that rots quietly for a clock that fails loudly, and the clock is much easier to think about at three in the morning.
Three more things the threads are about to discover
Since payments got here first and paid for it, here is what is waiting further down the road.
Velocity, not caps. Jon's five-refunds case is not an edge case, it is the standard attack, and per-transaction limits have never caught it. What catches it is cumulative exposure per counterparty per window. The check is easy. Choosing the window is not, and whatever window you choose, somebody can straddle it. Four minutes clears an hourly window if you start at 59 minutes past.
Preview is a surface too. Auth-then-capture has a gap between the two halves, and people have lived in that gap professionally for decades. If preview simulates the effect and execute performs it, any divergence between those two code paths is somewhere a difference can hide. Much smaller than the hole it closes. Not zero.
Approval screens launder decisions. Jon again, and it is the best line anyone has left on anything I have written: showing a human raw arguments is not review, it is laundering. If the human cannot see what the number means relative to the account it is hitting, you have not added a control. You have added a signature to blame later.
The one part that does not transfer
I do not want to oversell the analogy, because there is a real gap in it.
A payment has a natural boundary. There is a moment the transaction starts and a moment it settles, and every lifetime in the system hangs off those two points. Expiry is easy to reason about because there is an obvious thing for it to be shorter than.
An agent halfway through a long task has no such boundary. The task might run for six hours. It might spawn sub-tasks. It might pause overnight and resume. Scope the mandate to the operation and Jon's five-refund case walks straight through. Scope it to the session and you have issued a bearer token for a session whose end you cannot define.
So: what is the agent equivalent of a transaction boundary?
I do not have an answer. Payments handed us the shape of the solution for free, and then kept the one thing that made it work.
Quoted with thanks and without permission: @peterbuildssecure, @anp2network, @jon_at_backboardio and @max_quimby. The threads are here and here, and both are worth more than the posts they are attached to.
Top comments (0)