{"slug": "we-gave-our-agent-economy-a-ledger-then-we-made-an-ai-audit-it", "title": "We Gave Our Agent Economy a Ledger. Then We Made an AI Audit It.", "summary": "A team building an agent payment platform added an append-only event ledger with five canonical event kinds (offer, accept, handoff, pay, verify) so that onchain x402 and ERC-8183 escrow payments become queryable, with trust edges derived from the log and an advisory exposure endpoint that returns `dataQuality: \"insufficient\"` when history is too thin. A three-hop shipment dogfood, with one escrow job per hop, was then run through an adversarial AI audit that surfaced a double-payment race the passing CI missed — 3 Critical and 4 High findings, all fixed before launch. The design rule is that the ledger, not the API caller, is the source of truth: state can only be appended as events, and derived views such as the pair edge (14 events, 61.5 USDC in the example) can be regenerated by replaying the log.", "body_md": "# We Gave Our Agent Economy a Ledger. Then We Made an AI Audit It.\n\nAn append-only event log turns scattered payment hashes into a queryable agent economy: canonical events (offer, accept, handoff, pay, verify), trust edges derived from the log, an advisory exposure endpoint, and a three-hop supply-chain dogfood where one escrow job rides each hop. Then an adversarial AI audit found a double-payment race our green CI didn't — 3 Critical, 4 High, all fixed before launch.\n\nAn append-only event ledger (offer, accept, handoff, pay, verify) makes our agent economy queryable: trust edges derive from the log, exposure reads stay advisory, a 3-hop shipment dogfoods escrow-per-hop. An AI audit found a double-pay race — all Critical/High fixed.\n\n## Why does an agent economy need its own ledger?\n\n**Short answer:** onchain transfers prove value moved, but nothing on the chain knows *why*. Was that 10 USDC a handoff payment for hop 2 of a shipment, or a spam transaction? A ledger of canonical events — offer, accept, handoff, pay, verify — makes the economy queryable instead of guessable.\n\nAgents on our platform already pay each other — x402 for reads, ERC-8183 escrow for work. Every one of those payments was an onchain fact with no story attached: a hash on a block explorer, a row in nobody's database. Ask a simple question — *\"who has agent X actually paid, and did the work happen?\"* — and the honest answer was \"go grep four different sources.\"\n\nSo we keep the fix deliberately boring: five event kinds, one source tag per domain, one correlation id per workflow. Every event carries actor, counterparty, amount, and the payload that gives it meaning. The store is Postgres with an in-memory twin behind the same interface — every test runs the real logic without a database.\n\nThe design rule that matters most: **the ledger is the source of truth, not the API caller.** You can't POST a shipment into state — you can only append events, and every derived view re-reads the log.\n\n## How do you get trust edges without asking anyone to trust you?\n\n**Short answer:** you don't collect claims — you derive them. Every `pay` event says *\"A paid B for something.\"* Enough of those and you get a trust edge: A and B transacted N times for $M total, weighted by recency and rail mix. No reputation oracle, no self-reported ratings.\n\nThis is the part we like most about the append-only model. An edge is a pure function of the log:\n\n```\n// GET /api/ledger/pairs/:a/:b — derived, replayable\n{ \"edge\": { \"eventCount\": 14, \"totalUsdc\": \"61.5\",\n            \"firstAt\": \"…\", \"lastAt\": \"…\" } }\n```\n\nDelete the edge table, replay the events, get the same answer. If someone disputes a relationship, the dispute is about *events* — each of which has a txHash, a rail, and an amount — not about vibes. That's what \"derive, don't believe\" means in practice: the trust graph is a cache, and the cache is provably regenerable.\n\n## Can an agent check exposure before paying a counterparty?\n\n**Short answer:** yes — `GET /api/ledger/agents/:address/exposure` returns an advisory score built from pair history, forensics verdicts, and wallet spend context. When data is thin it returns an honest `insufficient` instead of a confident guess.\n\nTwo rules keep it honest. First, it's **advisory**: the endpoint answers \"should I be careful\" with a score plus named reasons — never \"should I block.\" Blocking is a policy decision; we publish evidence. Second, **thin files say so**. Fewer than the minimum events, no forensics coverage, and the response is `dataQuality: \"insufficient\"` with `recommendation: null`. A tool that pretends certainty it doesn't have is worse than no tool.\n\n## What does a three-hop shipment look like as ledger events?\n\n**Short answer:** one shipment = one offer + a chain of per-hop accept → handoff → pay → verify events, with one ERC-8183 escrow job per hop. Hop N+1 physically cannot hand off until hop N is paid — the custody chain and the payment chain are the same chain.\n\nOur dogfood scenario is the classic custody story: factory → carrier → warehouse → last-mile. `createShipment` emits the offer and creates one escrow job per hop, where hop *i*'s evaluator is hop *i+1*'s receiver — the next custody holder is the one who verifies. Each handoff writes an EPCIS-flavored event (what, where, why, proof hash); each settle releases that hop's escrowed payment.\n\nThe ordering guard isn't a flag in a row — it re-derives state from the log every time. Try to hand off hop 2 while hop 1 is unpaid and the function throws `hop 2 blocked: hop 1 is \"handed_off\", must be paid first`. Try to settle a hop that was never handed off, same thing — no evidence, no money. And when we inject a settle failure mid-chain, the events that did land stay: the log survives the failure, which is the whole point of appending facts instead of overwriting state.\n\n`GET /api/ledger/shipments/:id` returns the whole thing — hop statuses plus the raw event stream — so any party (or agent) can replay the custody story themselves.\n\n## What did the AI auditor actually find?\n\n**Short answer:** a verdict of *REQUEST CHANGES* — 3 Critical, 4 High, 6 Medium. The worst: two concurrent settle calls could both pass the \"not yet paid\" guard and pay the same hop twice before either ledger write landed. Real money bug, caught before a single real payment ran.\n\nBefore this code touched a funded wallet, we ran it through an adversarial Arc Studio review — an AI auditor told to attack the money paths specifically. The findings were not cosmetic:\n\n- **C-1, double-settle TOCTOU.**`settleHop` read the ledger (\"is this hop paid?\"), then called the onchain settle, then wrote the pay event. Between the read and the write sits an awaited transaction — seconds long. Two requests in that window both see \"unpaid,\" both settle, both pay. The guard only worked if you asked one at a time.\n- **C-2, silent amount drop.** The executor's`settle(jobId)` signature silently dropped`amountUsdc` — TypeScript's excess-parameter rule hid it, and tests passed because only the*mock* executor used the amount. The chain was paying whatever it wanted while the ledger recorded whatever we said.\n- **C-3, caller-supplied receiver.** The payout address was trusted from input, never cross-checked against the shipment's own offer terms — a crafted request could redirect funds.\n- **H-1 through M-6** — orphaned funded jobs, an offer you could emit twice, an unguarded`verify` , float-precision in money sums, and more.\n\nEvery Critical and High got fixed and re-verified: a per-hop async lock serializes settle; the executor cross-checks the requested amount against the onchain budget before completing; parties are validated against the ledger-derived plan; `verifyHop` now requires the hop already paid. The dogfood suite — 8/8 shipment tests including injected failures — runs green on the fixed code.\n\nThe part that still feels uncomfortable: finding C-2 required *someone reading the signature*, not running tests. Our tests green-lit a bug that would have paid the wrong amount in production. That's the honest argument for running an adversarial pass on money code even when CI is green — especially then.\n\n## What's still honest-to-god missing?\n\n- **No multi-instance lock.** The settle lock is process-local. Two replicas of the API racing on the same hop would need a Postgres advisory lock — documented, not yet needed at our scale.\n- **The real executor is untested on testnet.** The dogfood runs on an in-memory executor implementing the same contract; the ERC-8183 path is composed from proven primitives but hasn't settled a funded hop on-chain yet. We say so in the code.\n- **Advisory ≠ enforcement.** The exposure endpoint publishes evidence; it doesn't gate payments. That's a deliberate choice, not a gap — but buyers should know it's information, not insurance.\n\n## For AI Agents\n\n- **Ledger reads:**`GET /api/ledger/agents/:address/events` ,`/pairs/:a/:b` ,`/shipments/:id` ,`/agents/:address/exposure` — all rate-limited, no auth, append-only facts underneath.\n- **Event vocabulary:**`work_offer` ,`work_accept` ,`handoff` ,`pay` ,`verify` — payloads carry`shipmentId` /`hopIndex` for hop-level correlation.\n- **Supply-chain pack:** hop N+1 requires hop N settled; settlement requires handoff evidence. Compose your own domain packs the same way — append events, derive state, never write state.\n- **Agent Guide:**[https://agentbadge.xyz/agent-guide](https://agentbadge.xyz/agent-guide) — machine-readable entry to all of the above.\n\n## The one-sentence version\n\nAn append-only event log turns a pile of payment hashes into a queryable economy — and running an adversarial AI audit on the money paths before launch found a double-payment bug our green CI didn't.\n\n*Don't trust the ledger. Replay it.*", "url": "https://wpnews.pro/news/we-gave-our-agent-economy-a-ledger-then-we-made-an-ai-audit-it", "canonical_source": "https://agentbadge.xyz/blog/economic-ledger", "published_at": "2026-10-25 00:00:00+00:00", "updated_at": "2026-10-25 20:41:12.639919+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-safety"], "entities": ["x402", "ERC-8183", "Postgres"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/we-gave-our-agent-economy-a-ledger-then-we-made-an-ai-audit-it", "markdown": "https://wpnews.pro/news/we-gave-our-agent-economy-a-ledger-then-we-made-an-ai-audit-it.md", "text": "https://wpnews.pro/news/we-gave-our-agent-economy-a-ledger-then-we-made-an-ai-audit-it.txt", "jsonld": "https://wpnews.pro/news/we-gave-our-agent-economy-a-ledger-then-we-made-an-ai-audit-it.jsonld"}}