Core ConceptsAudit Trail

The Audit Trail is the system of record for everything an agent does. It is append-only, cryptographically hashed, and periodically anchored to Solana — meaning records cannot be altered, deleted, or forged after the fact without detection.

Event model

Every interaction produces an audit event:

{
  "event_id": "trade-2234146-d41d57ed",
  "agent_id": "agent_b1c59d23...",
  "event_type": "trade.executed",
  "payload": {
    "exchange": "binance-testnet",
    "symbol": "BTCUSDT",
    "side": "BUY",
    "price": "80947.93",
    "amount_usd": "49.38",
    "btc_qty": "0.00061",
    "order_id": "12345",
    "authorization_jti": "848373a9-a016-46..."
  }
}

Standard event types include:

  • agent.registered, agent.revoked
  • mandate.created, mandate.activated, mandate.revoked
  • payment.authorized, payment.rejected
  • trade.executed, trade.rejected (or any application-specific events emitted by the agent)
  • kyc.completed

Applications are free to emit additional event types; the protocol stores them all as part of the agent’s tamper-evident timeline.

The pipeline

Each stage updates the event’s status field:

StatusMeaning
receivedEvent ingested, payload hashed, stored in PostgreSQL
batchedEvent included in a sealed Merkle batch, batch ID assigned
anchoredThe batch root is on Solana — appended to the audit chain, seq assigned

Merkle-batching is what makes anchoring economical: hundreds of events share a single on-chain write while each keeps its own proof of inclusion.

The chain is what proves nothing is missing

A batch root on its own proves “this event is in that batch”. It says nothing about whether a whole batch was quietly dropped. So every batch root is appended to one audit chain account on Solana, which keeps a running head:

head = sha256(head_previous ‖ batch_root ‖ seq)

seq increases by exactly one per batch, and the program refuses anything else. Removing, reordering or altering a batch changes every head after it, and a gap in the sequence is rejected on chain rather than noticed later. Completeness and order become checkable facts, not promises.

Periodically — once a day, or every 1 000 batches — an epoch records a Merkle root over a contiguous range of batch roots in its own small account, so proving one event is two short Merkle paths rather than a replay of the whole chain. Each epoch root is also submitted to OpenTimestamps, which lands it in a Bitcoin block: a second, independent clock that does not depend on Regent or on Solana.

Proof of inclusion

Once batched, every event has a Merkle proof — a small list of sibling hashes that lets anyone verify “this event is in this batch” without downloading the whole batch.

The proof is returned by the API:

{
  "event_id": "trade-2234146-d41d57ed",
  "payload_hash": "0x44d926713d6d15...",
  "batch_id": "78422d51-6248-...",
  "merkle_index": 14,
  "merkle_proof": ["0xabc...", "0xdef...", "..."],
  "anchored_at": "2026-05-11T14:27:05Z"
}

GET /v1/audit/events/{event_id}/proof returns the whole bundle — the batch proof, the batch’s position in the chain, and the epoch proof with its on-chain account.

To verify without trusting us:

  1. Hash the event payload (canonical JSON) → payload_hash
  2. Walk the batch proof → the batch root
  3. Walk the epoch proof → the epoch root
  4. Read the Epoch account from Solana and compare the root, and check that the epoch’s sequence range contains this batch
  5. Read the audit chain account and confirm its sequence has reached this batch

There is a script that does all five against the public API and a public Solana RPC, and derives the account addresses itself:

python3 ops/audit/verify_event.py <event_id>

See Verifying on-chain for the step-by-step version and the exact hashing rules.

Why this design

GoalHow the design supports it
ImmutabilityOnce anchored, modifying an event would change the Merkle root, which is fixed on Solana
CompactnessOne Solana TX per batch (hundreds of events), not per event
VerifiabilityAnyone with the batch root + proof can audit a single event without trusting Regent
PerformanceIngestion is async; the agent’s /audit call returns in <50ms even when the chain is slow

What gets logged

The protocol enforces audit-event ingestion at every protocol boundary. Specifically, every:

  • Agent registration and revocation
  • Mandate authorize call — approved or rejected
  • Mandate creation, suspension, revocation
  • KYC milestone (submission, approval, DID issuance)

The agent itself is also expected to emit audit events for its own actions — trade fills, contract calls, payments, etc. The SDK’s ingest_event() method takes a single async call.