A Mandate is an AI agent’s spending rulebook. It defines the limits within which the agent can act autonomously — anything outside those limits is denied at the protocol level.
Think of a Mandate as a corporate spending policy that the network enforces, not the agent’s own code.
Three limit types
Every Mandate carries three limit fields:
| Field | Example | What it bounds |
|---|---|---|
| Per-transaction | $50 | Single action’s amount cannot exceed this |
| Daily | $300 | Cumulative spend in a UTC day cannot exceed this |
| Monthly | $5,000 | Cumulative spend in a UTC month cannot exceed this |
Each is independently optional. A mandate with only a monthly limit allows any single transaction up to the monthly cap.
Authorization flow
When an agent wants to perform an action, it calls Authorize first, and the mandate decides yes or no. Regent confirms the agent is active and the mandate is active, then evaluates the requested amount against the per-transaction, daily, and monthly limits.
- Within limits → the agent receives a one-time authorization JWT — a token proving the mandate consented to this specific amount at this moment.
- Over a limit → a structured rejection (
MANDATE_LIMIT_EXCEEDED), with no token issued.
The jti (JWT ID) is recorded in every subsequent audit event so the authorization trail is observable end-to-end.
What is on-chain, and what is not
The mandate’s existence, agent binding and revocation are written to Solana via the MandateRegistry program: a PDA per mandate id with registered_at, revoked and revoked_at. The ceilings are not on the chain — publishing an owner’s spending caps on a public ledger would leak their business, so the program never stored them.
The limits are still provable, by a different route:
- Every change to the terms creates an immutable version with a salted SHA-256 commitment of the canonical terms (
mandate_version,mandate_hash). - Every allow token, every completion receipt and every audit event carries that version and hash — and the audit events are Merkle-batched and anchored on Solana.
- The owner reveals a version to an auditor or insurer with
GET /v1/organizations/{org}/mandates/{id}/versions/{version}(snapshot + salt); the verifier recomputes the hash and matches it to the receipt. The receipt verify page does this recomputation in the browser.
So a compromised database cannot widen a limit after the fact: the receipt names the version that was in force, and that version’s hash is in an anchored audit trail.
Concurrency-safe enforcement
Spend counters are enforced atomically, server-side, so concurrent authorize calls cannot race past a limit. Two simultaneous authorizations that would each fit alone but exceed the cap together are handled correctly — only one succeeds.
Lifecycle
Created
The responsible party creates the mandate (dashboard, API or Admin MCP). It is active at once — the owner’s credential is the approval — and version 1 of its terms is committed.
Registered on Solana
blockchain-worker registers the mandate id and agent binding in MandateRegistry asynchronously; authorizations do not wait for the transaction.
Active
The agent can call /authorize against this mandate. Each call returns a JWT or a structured rejection code.
Suspended (automatic)
If the agent is revoked, all its active mandates are immediately suspended via the agent.revoked event consumer.
Revoked (explicit)
Responsible party can revoke a mandate explicitly while keeping the agent active.
Standard rejection codes
| Code | Meaning |
|---|---|
MANDATE_LIMIT_EXCEEDED | Action would exceed per-tx, daily, or monthly limit |
MANDATE_SUSPENDED | Mandate suspended (typically because agent was revoked) |
MANDATE_NOT_FOUND | Mandate ID is unknown |
AGENT_NOT_ACTIVE | Underlying agent is revoked or never reached active |
CURRENCY_MISMATCH | Request currency doesn’t match mandate currency |
The full error format is documented in REST API → Errors.