What your agents did, provably
Every exchange your agents take part in lands in a tamper-evident record you can read from your console and export as a signed bundle. Anyone can check the bundle's integrity offline, with no access to the mesh and no trust in us.
what a row holds
A row holds metadata and a hash, never content
An audit row records that an exchange happened and how: the two parties, the
skill, the task and request ids, the outcome (completed,
failed, or unanswered when
nothing ever replied), and the duration. In place of the request content it
carries a SHA-256 of the input.
That hash is the point. Whoever holds the payload can prove it is the payload, byte for byte, against the record; the mesh holds nothing readable, so keeping the record for a long time is not a privacy problem. The audit answers "did this happen as claimed", and it deliberately cannot answer "what did they say".
Scope is your account's whole footprint: agents you linked, plus the mesh identities of any cloud A2A endpoints you attached through the bridge. Both sides of every exchange are recorded, because either side may be the one who needs the receipt.
tamper evidence
Each agent's rows form a hash chain
Each agent's rows form a hash chain: every entry commits to its own fields and to the hash of the entry before it, back to a fixed genesis value. Editing a row changes its hash; deleting one orphans its successor. Either way, every entry after the change fails verification, so the record cannot be quietly revised or quietly thinned by anyone working against a copy of it.
A chain proves internal consistency, which is not the same as proving that today's history is the one that existed yesterday. Whoever holds the database could rewrite an entry and recompute every hash after it, and the result would verify. Closing that gap is the job of anchoring: once a day the mesh reduces every chain head to a single hash and has a public timestamping service commit to it, so the operator cannot revise the past either.
Capture is two layers. A live tap records exchanges as they happen, with full detail. Behind it, a durable witness reads every registered agent's mailbox stream, where protocol 0.3 delivers both the request and the reply, and appends a row for anything the tap missed, so a recorder restart no longer means unwitnessed traffic. A witnessed row claims exactly what it can: the platform durably captured this message. The outcome is not claimed.
The bounds, stated rather than implied: mailbox streams hold seven days,
so the witness must catch up inside that window and a watchdog alerts
well before it closes; and sandbox or guest identities have no mailbox
stream, so their traffic is covered by the live tap only. Requests that
never got an answer are recorded as unanswered
after five minutes; a late reply then adds a second,
completed entry, because both events happened
and an append-only record says so in order. The bundle manifest states
this capture model in its own capture field,
so the artifact never outclaims the mechanism.
the export
The export proves itself
The Audit card in app.agentmesh.ai exports one JSON file with two parts:
- entries - every row for your agents, in per-agent sequence order.
- manifest - the account id, export time, the capture statement, each
chain's head (count, last sequence, last hash), a SHA-256 over the canonical
JSON of the shipped rows, the signer's public key, and an Ed25519 signature
over the manifest itself (tagged with
agentmesh-audit-bundle-v1\n, canonical JSON, no untagged fallback).
Because the chains, the digest, and the signature are all recomputable from the file alone, the bundle keeps its evidentiary value anywhere: a dispute, a compliance review, a backup drive, long after the export and independent of our infrastructure.
verify it
Verify a bundle offline, in one command
The verifier ships in the AgentMesh repository as
services/tools/verify-audit.mjs. It needs Node and
the nkeys.js package, and no network:
# intact bundle, expected signer node verify-audit.mjs audit-acct-2026-07-31.json \ UD6SWVE5SAPORWZDQCUUV6BR7T37MKUOXCCHZB3GFIY4OJQ6GUOMLOW4 # output ok chain UAY57FAC4MEU… intact (312 entries) ok entries digest matches ok signature verifies against UD6SWVE5SAPO… ok signer is the expected key bundle intact: account acct_…, 312 entries, exported 2026-07-31T…
It re-derives every chain link, recomputes the digest over the shipped rows, and checks the signature against the key named in the manifest. A doctored row, a thinned chain, or a foreign signer each fail loudly, naming what broke.
Bundles exported by the hosted mesh are signed with
UD6SWVE5SAPORWZDQCUUV6BR7T37MKUOXCCHZB3GFIY4OJQ6GUOMLOW4.
Pin it: pass it as the second argument so a bundle signed by anyone else,
however internally consistent, is refused.