Dispute verifiability: from he-said-she-said to provable
A dispute on the AIOUM network is settled on evidence, not on competing stories: when your agent
says a call was paid for and never answered, the question is checkable, not argued. The canonical
Merkle transcript format ships with cross-implementation pinned vectors
(evidence-format.md); both node runtimes record durable
transcripts and anchor the root on-chain before settling; ServiceRegistry carries the
commitRequired service flag, snapshotted per session, so a flagged session cannot settle paid
work without an anchored root, enforced in PaymentManager; @aioum/sdk sessions record their own
transcript and voucher log and assemble evidence packages (session.evidencePackage(),
session.verifyAnchoredRoot()); and the arbitrator-side verify-evidence CLI checks anchored
roots, voucher authorization (EOA + ERC-1271), optional deterministic re-execution (--exec) and
pluggable attestations.
Verifiability is a spectrum
raiseDispute(sessionId, evidenceURI) carries an off-chain evidence package, on IPFS for
example, that an arbitrator inspects. How much of a ruling can be mechanical depends on the
service class:
| Service class | Example | What can be proven |
|---|---|---|
| Deterministic compute | hash, transcode, query over pinned data | everything — re-execute and compare |
| Attestable compute | model inference on fixed weights | TEE attestation / zkML (heavy) |
| Non-deterministic but bounded | web search, live-data feed | delivery + well-formedness, not "quality" |
| Subjective quality | "is this LLM answer good?" | essentially nothing — judgment forever |
The goal is to move every dispute as far left as its service class allows and keep the subjective residue small, cheap and clearly labeled. Your agent may buy a hash one minute and an LLM answer the next; the first can be re-executed, the second only proven delivered and paid for, and the system is honest about which is which.
The anchor: commitResults
PaymentManager.commitResults(sessionId, resultsRoot)
lets the operator commit a write-once hash over the calls it served, only while the
session is Open, before settlement and before any dispute, so a faulty operator cannot
rewrite history after a challenge; the commit doubles as the node's on-chain liveness ping.
Every tier builds on this anchor, and the node runtimes commit it automatically before
settling.
Tier 0: structured evidence on the anchor
The first tier needs no contract changes, only two conventions. On the node side, the results root is a Merkle root over per-call leaves:
leaf_i = keccak256(abi.encode(i, keccak256(request_i), keccak256(response_i), keccak256(toolId_i)))
The node keeps the raw transcript, the request and response bytes per call, in its
VoucherStore-adjacent storage for the dispute window. On the client side, the evidence
package that evidenceURI points to is a JSON document pinned to IPFS:
{
"formatVersion": 1,
"sessionId": "42",
"chainId": 31337,
"paymentManager": "0x..",
"claim": "non-delivery | wrong-result | quality",
"calls": [ { "index": 3, "toolId": "wordcount", "request": "...", "response": "..." } ],
"vouchers": [ { "cumulativeAmount": "5000000", "signature": "0x.." } ],
"resultsRoot": "0x..",
"narrative": "free text for the subjective residue"
}
(calls is the full index-ordered transcript; the arbitrator recomputes the root from it and
compares against the anchored sessionResultsRoot, so no per-call proof travels in the
package.)
An arbitrator then checks mechanically that the vouchers are signed by the session client
(payment happened), that the transcript recomputes to the on-chain sessionResultsRoot (the
node committed to exactly these responses), or that the node committed nothing while
vouchers show payment (delivery unproven). The judgment left over is only the quality of a
proven response.
Tier 1: mandatory commitment where it's cheap
The node runtime commits the results root once per session, immediately before it settles:
one transaction per session, priced in. ServiceRegistry carries a service-class flag,
commitRequired, and for flagged services an operator with no committed root and
client-signed vouchers in hand loses non-delivery disputes by default. Absence of evidence
becomes evidence of absence, but only where the service class opted in; elsewhere commitment
is optional and absence proves nothing. On your side, the SDK stores its own copy of requests,
responses and vouchers per session and assembles the evidence package automatically, so your
agent never has to remember to keep receipts.
Tier 2: objective wrong-result disputes for deterministic services
Service metadata declares determinism as a pinned docker image digest and entrypoint in the
service's descriptor card (see evidence-format.md §6). An
arbitrator, or anyone who can reproduce the environment, re-executes the disputed call with
verify-evidence --exec and compares against the Merkle-proven committed response. The vote
becomes a machine check.
Tier 3: attestations and proofs
Attestations ride in the same evidence package (type: tee-nitro, tee-sgx-dcap, zkml,
…) and dispatch through the verifier registry in verify-evidence
(VerifyHooks.attestationVerifiers). A TEE quote attesting "this image produced this
output", or a zkML proof for a small model, is simply a better checker for the same committed
root. An attestation with no registered verifier is reported UNVERIFIED, not trusted.
What stays judgment
Subjective quality is never provable. For "the LLM answer was bad", the system proves what
was delivered and that it was paid for; the rest is human judgment, reserved for exactly
that residue. Committed roots prove commitment, not correctness: a node can commit garbage,
and Tier 2 re-execution and Tier 3 attestations turn the anchor into a correctness check for
eligible classes. Storage is bounded: transcripts are retained through
settleChallengeWindow, bounded by session, and pruned after finality, following the node's
durable VoucherStore pattern.
All of it makes the record of a call more legible, to a juror and to anyone else who looks.
Next: Privacy — how the SDK, the node and a bonded relay make your agent's public spend uninformative.