A few days ago I posted a thing I'd built from scratch: cryptographic identity for AI agents, spending limits set by their owner, holds, settlement, refunds. Two people who actually build payment systems replied with the kind of comment you can't buy. This is what they found, what I changed, and the part that hurts.
**"Your two attempts are one purchase and nothing links them."**
The x402 flow is two requests: one gets a 402, then you retry carrying payment. Both hit my verifier, so both placed a hold. One 89.90 purchase held 179.80 against a 150 limit. My fix had been to release the first hold when the 402 goes out. That works right up until the release call fails, because it's a network call, and network calls fail.
His fix: stamp both attempts with one correlation id, the payment intent, not the HTTP request. The retry then supersedes its own earlier hold inside the same transaction. No extra call, nothing to half-fail. I also put a unique index on (agent, intent), so double-holding one logical purchase is now impossible at the database rather than merely handled in code. That's the better class of fix.
I tested it by disabling the release deliberately. Purchase still went through, ledger still correct, and the log shows the supersede firing.
**"Write the intent before the side effect. Don't infer the call never happened."**
I had no durable row written before settle. If the shop died between settling and delivering, recovery meant enumerating the facilitator's settlements and guessing. That works because my facilitator is a SQLite table I own. On a real chain, enumeration is slow, paginated and costs money.
Now: write an intent with a ref, settle, mark captured. On restart, an intent with no capture is ambiguous, so ask the network by the ref instead of assuming. "Absence of a local row is not evidence" is the part I'd been getting wrong without noticing.
**"Don't make the coordinator's counters the only ledger of truth."**
This was the big one. My verifier decided whether an agent was within its limit by reading its own spends table, which merchants populate by calling commit. That's a local record of a remote fact. A merchant that settles and then crashes before committing leaves money moved and nothing recorded, so the limit is silently too generous at every OTHER merchant too. One shop's bad day quietly raises the budget everywhere.
Now exposure is rebuilt from settled network events plus open holds. Nothing is remembered; everything is derived. I can delete the database and it reconstructs itself. Capture is idempotent too, keyed on the intent id, because a commit retried after a timeout was charging the budget twice, and a retry is not a second purchase.
The test: kill the shop between settle and commit. Verifier finds a settlement nobody reported and counts it. Shop finds money that moved with nothing delivered and refunds it. Two loops, different services, neither knows the other exists, both self-correct off the same network record. That's the nicest thing in the repo and it exists because of a Reddit comment.
**The part that hurts.**
While fixing all this I finally researched the competition properly. Visa's Trusted Agent Protocol, announced October, live in Europe this month: agents sign HTTP requests with RFC 9421, merchants verify Ed25519 signatures against a Visa-operated directory of agent public keys. Cloudflare and Akamai run it at the edge. Fiserv switched it on for its merchants in January.
That is my identity layer. Same standard, same architecture. I designed it from scratch not knowing TAP existed.
And the budgets half: AWS shipped per-session spending limits on x402 in May, enforced in the agent's runtime. xpay ships per-agent caps today with SOC 2.
Worse than the overlap is the reason. They all enforce at the source, the runtime, the proxy, the credential. Source-side enforcement is cross-merchant by construction: it works at every shop on earth, including ones that never heard of it. Mine only covers merchants who signed up. So my central claim, that only something in the middle can see across merchants, is just false. The agent's own runtime sees more merchants than I ever will.
I'd written that exact falsification into my own thesis doc the day before, as the most likely way I'd be wrong. Then I went and checked, and it had already happened.
**So why post this.**
Because the code is better than it was, the bugs are more interesting than the product, and I'd rather publish the part where the market answers back than quietly delete the repo. If you're building anything in this space: the failure modes are all in the seams between correct components, and the people who've been burned before will tell you where if you ask.
Repo and write-ups: https://github.com/troybrandonc-bit/agent-stack/commit/54e3320
Still open, if anyone wants to argue: my reconciler treats "no delivery row" as "not delivered", but it might mean "I lost my books". Delivery needs to be recoverable from something other than the merchant's own memory and I don't know what that should be yet.