# Regent Protocol Documentation
> Solana-native trust and auditability infrastructure for AI agents in regulated finance. Regent gives an AI agent a verifiable identity (AgentID) bound to a KYC-verified responsible party, bounds its authority with on-chain spending mandates, and anchors every action to Solana as a tamper-evident audit trail.
---
source: https://docs.regentprotocol.org/index.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Regent Protocol
**Solana-native trust and auditability infrastructure for AI agents in regulated finance.**
Regent Protocol gives AI agents a verifiable identity, binds them to accountable parties, and anchors every action on the Solana blockchain — making AI deployment credible in regulated markets.
Live on Solana devnet today. Agent registration, mandate registration, and audit anchoring are all on-chain.
## Why Regent
AI agents that move money, sign contracts, or trade on behalf of humans need three things that don't exist out of the box:
1. **A verifiable identity** — so the world can prove *this* agent did *that* action, not someone else's.
2. **Bounded authority** — so the agent can act autonomously *within rules* its operator has set.
3. **An audit trail** — so regulators, partners, and auditors can later verify what happened.
Regent provides all three as composable building blocks.
## The four primitives
- **[AgentID](/concepts/agent-id)** — Machine-verifiable identity, KYC-bound to a responsible party, anchored on Solana.
- **[Mandate](/concepts/mandate)** — Spending rulebook — per-transaction, daily and monthly limits enforced before every action; registered and revocable on Solana, with the terms committed by hash in every decision.
- **[Audit Trail](/concepts/audit-trail)** — Every meaningful action hashed, Merkle-batched, and anchored on Solana. Tamper-evident by construction.
- **[Guardian](/concepts/guardian)** — Real-time anomaly detection with explainable risk factors — catch agents going off-script.
## Who this is for
- **AI / fintech teams** — Deploy autonomous agents in regulated environments — payments, trading, compliance — with cryptographic proof of accountability.
- **Auditors and regulators** — Independently verify what an agent did, when, and under whose authority — without trusting the operator.
- **Platform builders** — Integrate identity, mandates, and audit anchoring into your AI agent platform via SDK or REST.
- **Compliance engineers** — Map agent activity to EU AI Act, GDPR, SOC 2, and ISO 27001 requirements with explainable evidence.
## Where to next
- **[Quick Start](/quickstart/three-steps)** — From zero to a first authorized agent action in three steps.
- **[Core Concepts](/concepts/agent-id)** — Read the four primitives in order.
- **[Python SDK](/sdk-python/guide/introduction)** — Drop Regent into a trading agent in a few lines of code.
- **[See it run](/use-cases/ai-trading-agent)** — Walk through a live Binance trading agent governed by Regent end-to-end.
---
source: https://docs.regentprotocol.org/concepts/agent-id.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# AgentID
> Machine-verifiable identity for AI agents, KYC-bound to a responsible party and anchored on Solana.
An **AgentID** is the cryptographic identity assigned to an AI agent at the moment it is registered. It is the answer to *"who did this?"* for every subsequent action the agent takes.
## Anatomy
Every AgentID has four parts:
| Part | Example | What it does |
|---|---|---|
| **Agent ID** | `agent_b1c59d23a4b07165f80a48ae861e20924594042cf5e10130` | Stable string identifier used in all APIs and audit records |
| **DID** | `did:regent:solana:agent_b1c59d23...` | W3C-compliant Decentralized Identifier, publicly resolvable |
| **Public key** | RSA-2048 PEM | KMS-signed; lets third parties verify identity payloads |
| **On-chain anchor** | Solana TX on `AgentRegistry` program | The agent's hash is stored on-chain; the existence and revocation status of the agent are tamper-evident |
## The trust chain
```mermaid
flowchart LR
KMS[Regent KMS
root signing key] -->|signs| PAYLOAD[Identity payload]
PAYLOAD -->|hashed| HASH[did_hash]
HASH -->|stored on-chain| SOL[Solana
AgentRegistry]
RESPONSIBLE[Responsible Party
KYC-verified user] -->|owns| PAYLOAD
```
To verify an agent identity, a third party:
1. Reads the agent's DID document from `https://api.regentprotocol.org/v1/did/{did}`
2. Checks the embedded signature against Regent's published KMS root key
3. Compares the `did_hash` against the on-chain `AgentRegistry` record
If all three match and the on-chain status is `active`, the agent is verified.
## Lifecycle
### Register
A KYC-verified responsible party calls `POST /v1/agents`. Identity payload is generated, signed by Regent's KMS, and queued for Solana anchoring.
### Anchor
The `blockchain-worker` submits the agent's `did_hash` to the `AgentRegistry` program on Solana. Status becomes `anchored`. Typical latency: a few seconds on devnet.
### Active
The agent can now hold mandates, request authorizations, and emit audit events. All actions reference its AgentID.
### Revoke
The responsible party (or an automated security workflow) calls `POST /v1/organizations/{org}/agents/{id}/revoke`. An on-chain revocation transaction follows. All active mandates owned by the agent are automatically suspended.
## Why KMS-signed
The agent identity payload is signed by Regent's KMS root key, not by the agent itself. This means:
- The agent cannot forge its own identity (it has no private key for the root)
- The responsible party cannot forge the agent's identity (same)
- Verification only requires the public root key, which is published
This separation is what makes Regent identity credible to a regulator. The agent operator cannot self-attest.
## Why on-chain
The `did_hash` stored in `AgentRegistry` makes the existence and status of every agent **tamper-evident**. Even if Regent's database were compromised, an auditor could:
1. Read the agent record from Regent's API
2. Hash the identity payload independently
3. Compare against the on-chain anchor
If Regent's database had been quietly modified (e.g., to re-activate a revoked agent), the comparison would fail. On-chain anchoring eliminates the need to trust Regent's storage layer.
## Related
- [Mandate](/concepts/mandate) — Mandates are scoped to a specific AgentID. An agent can hold one or more mandates.
- [Responsible Party](/concepts/responsible-party) — Every AgentID is bound to a KYC-verified human or entity.
---
source: https://docs.regentprotocol.org/concepts/mandate.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Mandate
> Spending rules that bound what an agent can do without per-action human approval — enforced before every action, registered on Solana, terms committed by hash in every decision.
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](https://control-api.regentprotocol.org/v1/control/receipts/verify) 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](/rest-api/errors).
## Related
- [AgentID](/concepts/agent-id) — Every mandate is owned by exactly one agent.
- [Audit Trail](/concepts/audit-trail) — Authorize calls (both approvals and rejections) are recorded in the audit trail.
---
source: https://docs.regentprotocol.org/concepts/audit-trail.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Audit Trail
> Every meaningful agent action hashed, Merkle-batched, and anchored on Solana. Tamper-evident by construction.
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**:
```json
{
"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
```mermaid
flowchart LR
INGEST[Event ingest
POST /audit/events]
HASH[payload_hash
SHA-256 of canonical JSON]
BATCH[Merkle batch
periodic seal]
CHAIN[Audit chain
one running head]
EPOCH[Epoch
root over a range of batches]
INGEST --> HASH --> BATCH --> CHAIN --> EPOCH
```
Each stage updates the event's `status` field:
| Status | Meaning |
|---|---|
| `received` | Event ingested, payload hashed, stored in PostgreSQL |
| `batched` | Event included in a sealed Merkle batch, batch ID assigned |
| `anchored` | The 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](https://opentimestamps.org), 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:
```json
{
"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:
```bash
python3 ops/audit/verify_event.py
```
See [Verifying on-chain](/solana-programs/verification) for the step-by-step version and
the exact hashing rules.
## Why this design
| Goal | How the design supports it |
|---|---|
| **Immutability** | Once anchored, modifying an event would change the Merkle root, which is fixed on Solana |
| **Compactness** | One Solana TX per batch (hundreds of events), not per event |
| **Verifiability** | Anyone with the batch root + proof can audit a single event without trusting Regent |
| **Performance** | Ingestion 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.
## Related
- [Mandate](/concepts/mandate) — Authorize calls — both approvals and rejections — are part of the audit trail.
- [Guardian](/concepts/guardian) — Guardian consumes audit events to score agents in real time.
---
source: https://docs.regentprotocol.org/concepts/guardian.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Guardian
> Real-time anomaly detection on agent behavior, with explainable risk scores and threshold alerts.
**Guardian** is Regent's ML layer. It watches every agent's audit stream in real time and produces a risk score between 0 (normal) and 1 (highly anomalous). When the score crosses a configured threshold an alert is created — and the gate can deny a high-risk action even when every individual rule passes.
## Why ML, not just rules
Mandate enforcement (per-tx limits, daily caps) is rule-based and deterministic — it catches *known* violations. Guardian catches the *unknown* ones: agents whose individual actions are all within limits but whose *pattern* is suspicious.
Examples Guardian catches that mandates miss:
- An agent suddenly making many tiny trades after weeks of normal pacing
- An agent triggering many rejections in a short window (probing limits)
- An agent active at an unusual time of day for its profile
- An agent whose action types shift dramatically (e.g., started buying, now only selling)
## How scoring works
Every audit event triggers a fresh score for that agent. Guardian derives behavioral signals from the agent's recent activity, evaluates them with an unsupervised anomaly-detection model, and emits a risk score together with a set of explanatory factors. Scoring runs in-line with audit ingestion and is fast enough to inform authorization in real time.
## Explainable by design
Every score comes with per-signal **contributing factors** — so an alert can be defended with a concrete attribution rather than "the model said so." This supports the **EU AI Act**'s explainability expectations: each alert carries a human-readable reason for *why* the behavior was flagged.
## Alerts
When a score crosses the alert threshold (configurable per environment), Guardian creates an alert with status `open`, surfaced on the dashboard; operators acknowledge or resolve it via the API. A separate drift signal flags a sudden jump in an agent's score between consecutive events.
## A second layer of denial
When `api-payment` authorizes an action, it consults Guardian's latest score as a **soft dependency**: if Guardian is unavailable, authorization still proceeds. If the score is high enough, the action is denied with `RISK_THRESHOLD_EXCEEDED` — so an agent whose behavioral risk has spiked is blocked even when its individual action is within the rulebook.
## The behavioural layer in the gate
Guardian's score is one input. The gate also computes two deterministic signals per decision, in the request path, from its own counters:
- **Velocity** — how many decisions this agent asked for in the last minute and hour, allowed or refused. The starter policy escalates at 60 per minute (`context.velocity_1m`); an organization's own Cedar policy can escalate or deny at any figure.
- **Near-duplicate** — a money request identical to one allowed in the last 60 seconds (same canonical request hash) with no `idempotency_key`. The starter policy escalates it (`context.duplicate`). A request that carries an `idempotency_key` is a deliberate repeat and is never flagged; a voided or failed earlier attempt reopens the window so a retry is not flagged either.
Both signals are recorded in the audit event (`velocity_1m`, `velocity_1h`, `duplicate_of`) and never in tokens or receipts. Refusals surface as `VELOCITY_EXCEEDED` / `DUPLICATE_REQUEST` when a policy forbids, or as `ESCALATION_REQUIRED` when it asks for a human.
## Related
- [Audit Trail](/concepts/audit-trail) — Guardian consumes the audit stream — no events, no scoring.
- [Mandate](/concepts/mandate) — Hard-reject from Guardian augments mandate enforcement at authorize time.
---
source: https://docs.regentprotocol.org/concepts/responsible-party.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Responsible Party
> The human or entity legally accountable for an agent's actions. Every AgentID is bound to one.
A **Responsible Party** is the KYC-verified human or legal entity that an AI agent is bound to. They are the answer to *"who is accountable for what this agent did?"*
This binding is what makes Regent credible to regulators, auditors, and counterparties. Identity without accountability is just a name; identity with a responsible party is **legally meaningful**.
## Who can be a responsible party
- An individual who has completed Regent's KYC flow (email verification + identity verification)
- An organization, represented through its KYC-verified administrator
In the protocol, both are represented as a `responsible_party_id` — a UUID assigned at signup.
## How the binding is created
When an agent is registered, the request includes the responsible party's UUID. The protocol does three things:
### Verifies KYC
The request is rejected unless the responsible party has both `email_verified=true` and `kyc_status='verified'`.
### Records the binding
The agent record carries `responsible_party_id`. Every API response surfaces this; every audit event implicitly inherits it.
### Anchors the binding on-chain
The agent's identity payload (signed by Regent's KMS, hashed, and submitted to `AgentRegistry`) includes the responsible party hash. The binding is tamper-evident.
## What the responsible party can do
| Action | API | When |
|---|---|---|
| Register an agent | `POST /v1/agents` | After KYC verification |
| Create a mandate | `POST /v1/mandates` | Bounded to one of their agents |
| Suspend or revoke an agent | `POST /v1/organizations/{org}/agents/{id}/revoke` | Anytime — the kill switch |
| Revoke a mandate | `POST /v1/organizations/{org}/mandates/{id}/revoke` | Anytime |
| Acknowledge a Guardian alert | `POST /v1/guardian/agents/{id}/alerts/{alert_id}/acknowledge` | When an anomaly fires |
| Read the full audit trail for their agents | `GET /v1/audit/agents/{id}/events` | Anytime |
The responsible party cannot, by design:
- Modify a past audit event (immutable once hashed)
- Modify on-chain anchors (non-upgradeable Solana programs)
- Transfer accountability to another party retroactively (the binding is fixed at registration)
## Why this design
Without an accountable party, an AI agent is a black box. Regulators and counterparties cannot route legal liability, dispute resolution, or enforcement action to anyone.
By making the responsible party a first-class field — KYC-gated, on-chain anchored, immutable after registration — Regent gives every agent a verifiable trail back to a human or legal entity. The agent is *autonomous in its actions* but *not autonomous in its accountability*.
This satisfies:
- **EU AI Act** Article 14 (human oversight)
- **GDPR** Article 22 (right to know who's behind automated decisions)
- **AIFC/DIFC** governance documentation requirements
- **SOC 2** Trust Services Criteria for access control
## Multi-party organizations
A single organization can have multiple members (Owner, Admin, Developer, Viewer) — but agents are bound to **one** responsible party. Members of the same org can collaborate on managing agents through the dashboard or API, but the cryptographic accountability binding remains 1:1.
## Related
- [AgentID](/concepts/agent-id) — The identity layer that the responsible party owns and controls.
- [Audit trail](/concepts/audit-trail) — The anchored record that ties every agent action back to its responsible party.
---
source: https://docs.regentprotocol.org/quickstart/three-steps.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Quick Start
> From zero to a first authorized agent action in three steps.
In about ten minutes you'll have a registered, KYC-bound AI agent under a spending mandate, executing its first authorized action with a full on-chain audit trail.
> You'll need a Regent account, KYC verification, and an API key. All free during the demo phase.
## Step 1 — Sign up and complete KYC
### Create your account
Sign up at [web.regentprotocol.org](https://web.regentprotocol.org) with email and password. Verify your email via the link you receive.
### Complete identity verification
Open the **Settings** page → **Start Identity Verification**. Identity verification is real (a government document and a selfie, through Regent's verification providers) and takes a few minutes. A self-serve signup starts in a **sandbox** that does not need it: you can register agents, set limits and test decisions on Regent's test rail first, and verify when you go live.
### Get a DID
On KYC completion, your user DID is issued and anchored on Solana. You'll see it on the Settings page under "Your Decentralized Identifier."
Once your status shows **Identity Verified** in green, move on.
## Step 2 — Register an agent and create a mandate
### Register the agent
On the **Agents** page click **Register Agent**, give it a name (e.g. `demo-bot`), and submit. Regent generates the AgentID, signs the identity payload with its KMS root key, and queues a Solana anchoring transaction.
Within a few seconds the agent's status will flip from `pending` to `active` and on-chain status to `anchored`. You'll get an `agent_id` like:
```
agent_b1c59d23a4b07165f80a48ae861e20924594042cf5e10130
```
### Create a mandate
On the **Mandates** page click **Create Mandate**. Bind it to your new agent, set:
- **Per-transaction**: $50
- **Daily**: $300
- **Monthly**: $5,000
- **Currency**: USD
Submit. The mandate is registered on Solana via the `MandateRegistry` program. Once anchored its status flips to `active`.
### Create an API key
On the **API Keys** page click **Create API Key**. Copy the `rgnt_…` value — this is shown once.
## Step 3 — Run your first authorized action
Set your env vars:
```bash
export REGENT_API_KEY="rgnt_..."
export REGENT_ORG_ID="..." # from Settings page
export REGENT_AGENT_ID="agent_..." # from Agents page
export REGENT_MANDATE_ID="..." # from Mandates page
```
Install the Python SDK:
```bash
pip install "git+https://github.com/abay94/regent-sdk-python.git"
```
Authorize a $25 payment, log the action, and shut everything down:
```python
import asyncio
from decimal import Decimal
from regent import RegentClient, AuthorizeRequest, IngestEventRequest
async def main():
async with RegentClient(
base_url="https://api.regentprotocol.org",
api_key="",
) as r:
# 1. Ask the mandate for permission
auth = await r.payment.authorize(
mandate_id="",
request=AuthorizeRequest(amount=Decimal("25"), currency="USD"),
)
print(f"Authorized: jti={auth.jti}")
# 2. (Your business logic happens here — call an exchange, sign a tx, etc.)
# 3. Log the action to the audit trail
await r.audit.ingest_event(IngestEventRequest(
event_id=f"action-{auth.jti}",
agent_id="",
event_type="action.completed",
payload={
"amount": "25",
"currency": "USD",
"authorization_jti": auth.jti,
},
))
print("Logged to audit trail")
asyncio.run(main())
```
You should see:
```
Authorized: jti=abc12345-...
Logged to audit trail
```
## What just happened
On the **dashboard** open your agent's detail page and the **Audit Log**. You'll see:
- A `payment.authorized` event with the `jti`
- An `action.completed` event you just emitted, with its `payload_hash`
- Both events queued for Merkle batching → Solana anchoring (status: `received` → `batched` → `anchored` over the next few minutes)
On the **Solana Explorer** the agent registration and mandate registration are already searchable by their TX signatures (shown on the agent + mandate detail pages).
## Try the kill switch
While the script is still running, go to **Agents** → your agent → **Revoke**.
The next time the script calls `/authorize` it will fail with:
```
AGENT_NOT_ACTIVE
```
or
```
MANDATE_SUSPENDED
```
(All active mandates for a revoked agent are automatically suspended within milliseconds via the `agent.revoked` event consumer.)
That's the full lifecycle: identity → mandate → authorize → action → audit → revoke. Everything else in the docs is detail on top of these primitives.
## Where to next
- [Python SDK guide](/sdk-python/guide/introduction) — Full walkthrough of every SDK method with code examples.
- [Binance trading agent](/use-cases/ai-trading-agent) — A complete real-world example: an AI agent trading BTC on Binance under Regent's authorization.
- [Solana programs](/solana-programs/overview) — Inspect the Anchor programs that anchor agents, mandates, and audit batches on-chain.
- [REST API](/rest-api/authentication) — Use Regent directly over HTTP — no SDK required.
---
source: https://docs.regentprotocol.org/control.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Regent Control
Regent Control sits **in the data path** in front of an AI agent's actions. Every tool call
— a refund, a wire, a DB write, an API request — is authorized first (**allow / deny /
escalate**), the real credential is injected by a sidecar so the agent never holds a key,
and an immutable, attributable audit record is written.
> Unlike an out-of-band token service, Regent Control is **in-path** — so it can enforce on
> transaction *amount* (mandates), redact sensitive response content, pause an action for
> human approval, and contain a hijacked agent. An authorizer that steps out of the path can't.
## How it works
1. The agent points its tool calls at the **sidecar** (HTTP proxy `/{tool}/{path}` or MCP) — it holds no provider credentials.
2. The sidecar asks the **gate** to authorize the call. The gate evaluates identity, mandate/spend limits, behavioral risk, and policy, and returns **allow / deny / escalate** — **deny-by-default** and **fail-closed** (any internal error denies).
3. On **allow**, the sidecar injects the vaulted credential and makes the real call; on **deny**, the provider is never touched; on **escalate**, the action is parked for a human.
## What you can enforce
| Capability | What it does |
|---|---|
| **Policy** (Cedar) | allow / deny / escalate on role, operation, resource, amount, account facts |
| **Mandates** | spend caps — flat, per-entity (per customer/ticket), and relational (refund ≤ original charge) |
| **Delegation** | verify the human's OIDC token, derive the role, record "on behalf of" |
| **Human-in-the-loop** | escalate to an approver; resume on approval |
| **Provider auth** | static bearer · OAuth2 · AWS SigV4 · mTLS — vaulted, the agent never sees it |
| **Redaction** | mask sensitive fields/patterns in responses before the agent sees them |
| **Audit + SIEM** | immutable, anchored audit log + a real-time webhook to your SIEM |
| **Containment** | egress lockdown — a hijacked agent can reach nothing but the sidecar |
## Get started
The decision API lives on its own host, `control-api.regentprotocol.org`, and authenticates with a **Control key**
(`rgnt_ctrl_…`, issued under **Control → Onboarding**) — not the org API key used on `api.regentprotocol.org`.
The [authentication page](/rest-api/authentication) has the host-by-host table.
- [Onboarding Guide](/control/onboarding) — The admin path: keys, tools, policy, mandates, delegation, approvals, SIEM, deploy.
- [Run the Sidecar](/control/run-sidecar) — Bring it up with Docker — a zero-prod demo (`docker compose up`) and a real deployment.
- [Walkthrough: Refund Agent](/control/refund-agent) — A full end-to-end example — role, spend caps, delegation, and human approval.
- [SDK & Integration](/control/integration) — The developer path: Python / TypeScript SDKs, MCP, error contract, token verifier.
---
source: https://docs.regentprotocol.org/control/onboarding.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Onboarding Guide
The admin path to put Regent Control in front of an agent for one org. Everything below is
configured under **Control** in the dashboard and published to the sidecar/gate within ~15s
(no redeploy) — except the sidecar deploy + egress lockdown, which are one-time infra steps.
> The agent holds **no** provider credentials — only the env vars the sidecar reads from the
> vault. The catalog of tools *is* the egress allowlist.
## 1. Bootstrap — keys, license, agent
### Issue a Control API key
`rgnt_ctrl_…` — the org credential the sidecar uses to reach the Decision API. Shown once.
### Issue a license
`rcl_…` — enables enforcement (fail-closed if invalid).
### Register the agent
The identity the gate resolves + checks active/revoked on every decision.
The **Control → Onboarding** wizard issues these and hands you a ready `.env` /
docker-compose for the sidecar.
## 2. Tools — the catalog
Each tool = a provider base-URL + how to authenticate. The catalog is the **egress
allowlist** — the agent can only ever reach catalogued tools. Per tool:
- **Auth type** (the credential the sidecar injects — the agent never holds it):
- **Static** — a vaulted bearer (`secret_env`).
- **OAuth2** — client-credentials: token endpoint + vaulted client id/secret env (+ scope). The sidecar fetches, caches, and refreshes the token.
- **SigV4** — AWS Signature V4 per-request signing (region/service + vaulted access/secret key env).
- **mTLS** — a client certificate on the egress connection (vaulted cert/key paths).
- **operation_map** *(optional)* — derive a semantic `op` + `resource_type` so policy can say "deny delete on Contact" instead of matching a raw path.
- **Mandate binding** *(optional)* — attach a `mandate_id` + amount field so money calls run the spend check.
- **Redaction** *(optional)* — mask sensitive response fields/patterns before the agent sees them.
## 3. Policy — Cedar (allow / deny / escalate)
Author the org's Cedar policy (a starter pack is provided). Policies match on the decision
**context** — role, op, resource_type, amount, account_status. A `forbid` whose `@id`
contains `escalate` maps to **escalate** (human approval) rather than a hard deny. Validate,
then publish.
## 4. Mandates — spend control
- **Flat** — per-transaction / daily / monthly.
- **Per-entity sub-budget** — cap total spend per customer / ticket / vendor.
- **Relational cap** — bound an action (and the cumulative amount against the same reference) to a referenced anchor — e.g. *a refund must not exceed the original charge*.
## 5. Delegation — your IdP (on-behalf-of)
Point Regent at your IdP's OIDC discovery/JWKS. When the agent forwards a human's
`id_token`, the gate **verifies it** and derives the principal + role itself (an *asserted*
role is never trusted) — recorded as `on_behalf_of`. This lets policy say "a `support_agent`
may refund ≤ $200."
## 6. Enforcement mode
- **observe** — log what enforcement *would* do, allow everything (safe rollout).
- **enforce** — block on deny.
## 7. Human-in-the-loop — Approvals
When a policy returns `escalate`, the action is parked under **Control → Approvals** for a
reviewer to approve/deny. The agent resumes on its next retry (same idempotency key).
Optionally set an approver **webhook** so Slack/Teams/Telegram is pinged.
## 8. Audit export — SIEM/GRC
Forward every decision to your SIEM webhook (HMAC-signed), in addition to Regent's durable,
anchored audit log. Best-effort; never blocks a decision.
## 9. Deploy the sidecar
The sidecar runs next to the agent; the agent points its tool calls at it (HTTP proxy or
MCP). Use the wizard's compose, or the gate Helm chart for HA.
## 10. Hardening (optional)
- **Open Hosts** — the few hosts the agent may reach **directly**, bypassing the sidecar (e.g. the LLM endpoint). Admin-only; bypasses policy/audit/redaction, so non-regulated destinations only.
- **Egress lockdown** — init-container iptables so *all* other agent egress must traverse the sidecar. Turns "governs cooperative" into "contains hostile." Apply on a canary first.
## Verify
### Observe a decision
In **observe** mode, make a real agent call → see it in **Control → Decisions** (with intent + on-behalf-of).
### Trip a deny
Exceed a mandate cap → blocked, recorded.
### Trip an escalate
Appears in **Approvals**; approve → the agent's retry succeeds.
### Enforce
Flip to **enforce**.
---
source: https://docs.regentprotocol.org/control/run-sidecar.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Run the Sidecar
The sidecar runs next to your agent. The agent makes its tool calls to the sidecar over
loopback (HTTP proxy `/{tool}/{path}` or MCP) — it holds **no** provider credentials. The
sidecar authorizes with the gate, injects the vaulted credential, and originates the real
call.
> There is no iptables redirect, no CA cert, no TLS interception. The one agent-side change is
> pointing a tool's base-URL (or the MCP client) at the sidecar — for MCP-native agents that's
> zero-code.
## Try it locally — zero prod, one command
The repo ships a self-contained demo: a mock control plane + a mock provider + the sidecar +
an unmodified agent. It proves the whole credential-broker flow **without touching any real
service or audit trail**.
```sh
cd sidecar/demo
docker compose up --build
docker compose logs -f agent
```
You'll see the agent make three calls through the sidecar:
```text
=== ALLOWED call (amount under limit) ===
{"received_authorization":"Bearer vault-secret-xyz","path":"/charge"} # ← sidecar injected the vaulted key
=== DENIED call (amount=999 over the $50 mandate) ===
{"decision":"deny","code":"MANDATE_LIMIT_EXCEEDED",...} # ← provider never called
=== UNCATALOGED tool (egress allowlist) ===
{"decision":"deny","code":"TOOL_NOT_ALLOWED",...} # ← only catalogued tools are reachable
```
The provider echoes the `Authorization` it received — proving the sidecar injected a
credential the **agent never held**.
## Run it for real
Drop the sidecar next to your agent (same pod / compose project). It pulls its tool catalog
+ enforcement mode live from the control plane using its `CONTROL_API_KEY`, so dashboard
changes take effect within ~15s — no redeploy.
> The sidecar image is published to **GHCR** by CI from `regent-control/sidecar`:
> `ghcr.io/abay94/control-sidecar:latest`. (Or build it yourself:
> `docker build -t control-sidecar regent-control/sidecar`.) For unauthenticated `docker pull`,
> the GHCR package must be **public** — otherwise `docker login ghcr.io` first.
```yaml
services:
agent:
image: yourorg/your-agent:latest
environment:
# The ONLY agent change: point each tool at the local sidecar.
PAYMENTS_BASE_URL: "http://sidecar:8080/payments"
# (or, for an MCP-native agent, set the MCP server to http://sidecar:8080/mcp)
depends_on: [sidecar]
sidecar:
image: ghcr.io/abay94/control-sidecar:latest
ports: ["8080:8080"]
environment:
CONTROL_API_URL: "https://api.regentprotocol.org"
CONTROL_API_KEY: "${CONTROL_API_KEY}" # rgnt_ctrl_… (issued in the dashboard)
CONTROL_LICENSE_KEY: "${CONTROL_LICENSE_KEY}" # rcl_…
CONTROL_AGENT_ID: "agent_refund_bot"
CONTROL_FAIL_MODE: "closed" # deny if the control plane is unreachable
# The vaulted provider credential(s) — read by the sidecar, never the agent:
PAYMENTS_TOKEN: "${PAYMENTS_TOKEN}"
```
> Vaulted credentials must live only in the sidecar's environment (KMS-backed), never in the
> agent container or Regent's cloud.
## Environment reference
| Variable | Purpose |
|---|---|
| `CONTROL_API_URL` | the Decision API base URL |
| `CONTROL_API_KEY` | the org Control key (`rgnt_ctrl_…`) — also pulls the live catalog + mode |
| `CONTROL_LICENSE_KEY` | enables enforcement (fail-closed if invalid) |
| `CONTROL_AGENT_ID` | the agent this sidecar governs |
| `CONTROL_FAIL_MODE` | `closed` (deny on control-plane error) or `open` (allow + warn) |
| `CONTROL_TOOLS` | a JSON catalog — the fallback/base if config-pull is off |
| `CONTROL_SIGN_REQUESTS` | HMAC-sign decision requests (`X-Agent-Signature`) |
| `` | each tool's vaulted credential, named by the catalog's `secret_env` |
## Verify it's live
### Health
`curl http://localhost:8080/healthz` → `{"status":"ok",...}`.
### First call
Make a tool call through the sidecar; it appears in **Control → Decisions** in the dashboard.
### Observe → enforce
Start in **observe** mode (logs would-be denials, allows everything), then flip to **enforce**.
Next: the full [Refund Agent walkthrough](/control/refund-agent) wires a real policy, mandate,
and delegation end-to-end.
---
source: https://docs.regentprotocol.org/control/refund-agent.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Walkthrough: a refund agent
A worked, end-to-end example: a bank's support **refund agent** that a `support_agent` (a
human rep) drives. We'll enforce, on every refund:
- **Who** — only a `support_agent`, acting on a verified human's behalf (delegation).
- **What** — only when the account is **active** and the refund goes **to the original card**.
- **How much** — within the rep's per-transaction cap, and **never more than the original charge**.
- **When to ask** — accounts under **review** escalate to a manager.
| Scenario | Outcome | Enforced by |
|---|---|---|
| $50 refund · active · to original | **allow** | policy + mandate |
| Refund on an account under **review** | **escalate** → manager approves | policy (Cedar) |
| Refund on a **frozen** account | **deny** | policy (Cedar) |
| $250 (over the $200 per-tx cap) | **deny** `MANDATE_LIMIT_EXCEEDED` | mandate |
| Refund > the original charge | **deny** `EXCEEDS_REFERENCE` | mandate (relational cap) |
## 1. Configure (dashboard, ~10 min)
### Add the tool *(Control → Tools)*
A `payments` tool pointing at your refunds API, with the vaulted credential (static / OAuth2 / SigV4 / mTLS). Bind it to the mandate below and set `operation_map` so a `POST /refunds` becomes `op: refund.create`.
### Create the mandate *(Control → Mandates)*
`mnd_support_refunds` — **per-transaction cap $200** + a **relational cap** (refund ≤ the original charge, cumulatively). Optionally a **per-customer** monthly sub-budget.
### Publish the policy *(Control → Policies)*
```cedar
// A support_agent may refund on an active account, to the original card.
permit (principal, action, resource)
when { context.agent_active == true && context.user_role == "support_agent" };
@id("acct-frozen") // frozen account → hard deny
forbid (principal, action, resource)
when { context.op == "refund.create" && context.account_status == "frozen" };
@id("to-original") // must return to the original payment method
forbid (principal, action, resource)
when { context.op == "refund.create" && context.refund_to_original == false };
@id("escalate-review") // account under review → needs a manager (escalate)
forbid (principal, action, resource)
when { context.op == "refund.create" && context.account_status == "review" };
```
### Connect your IdP *(Control → OIDC)*
Point Regent at your IdP's issuer + JWKS. The rep's `id_token` is then **verified by the gate**, which derives `user_role` itself — an *asserted* role is never trusted.
## 2. The agent
The whole refund action is one gated call: **authorize → act → report**. The agent holds no
payments key (the sidecar injects it); it forwards the rep's token + the verified facts.
```python
from regent_control import RegentControl, Escalated, ControlDenied
control = RegentControl(api_key="rgnt_ctrl_…", agent_id="agent_refund_bot")
def handle_refund(*, charge_id, amount_usd, ticket, rep_id_token, account_status):
d = control.authorize(
tool="payments", action="refund.create", op="refund.create",
resource=f"charges/{charge_id}",
amount_usd=amount_usd, mandate_id="mnd_support_refunds",
reference=charge_id, reference_amount=original_charge_amount(charge_id), # relational cap
idempotency_key=f"{ticket}:{charge_id}", # a retry never double-refunds
user_token=rep_id_token, # gate-verified → user_role
intent=f"refund the duplicate charge on ticket {ticket}",
facts={"account_status": account_status, "refund_to_original": True},
)
if d.decision == "escalate":
notify_rep(f"Sent to a manager — retry ticket {ticket} once approved")
raise Escalated(d.reason, decision_id=d.decision_id, escalation=d.escalation)
d.raise_for_status() # deny → MandateExceeded / PolicyDenied / …
refund_id = payments.refund(charge_id, amount_usd, token=d.token) # scoped token
control.complete(d.decision_id, status="success", downstream_ref=refund_id)
return refund_id
```
> Prefer the **sidecar-routing** model? Use `SidecarSession(...).call("payments", "refunds", …)`
> instead — same enforcement, but the agent points at the sidecar and never holds the payments
> key at all.
## 3. Run it — the three outcomes
```text
$50 · active · to original → ALLOW → refund re_ch_aaa (Control → Decisions shows it,
"on behalf of alice@bank (support_agent)")
account under review → ESCALATE → appears in Control → Approvals; a manager approves;
the agent's retry (same idempotency key) now allows
frozen account → DENY → PolicyDenied (acct-frozen)
$250 → DENY → MandateExceeded (over the $200 cap)
refund > original charge → DENY → EXCEEDS_REFERENCE (relational cap)
```
Every decision — allow, deny, escalate — is in the immutable audit log with the agent, the
human it acted for, the intent, and the amount. Wire **Control → Audit Export** to stream them
to your SIEM.
## 4. Run it locally — zero setup
The Python SDK ships this as a runnable example against an in-process mock gate (no account,
no services):
```sh
pip install regent-control
python examples/refund_agent.py # walks allow / deny / escalate (examples/ ships in the sdist)
```
Or run the mock gate yourself and point any client at it:
```sh
regent-control dev --deny-over 200 --escalate-over 1000
# RegentControl(api_key="dev", agent_id="agent_refund_bot", base_url="http://localhost:8009")
```
For the full provider-credential flow with the real sidecar (mock control plane + mock
provider, still zero prod), see [Run the Sidecar](/control/run-sidecar) → *Try it locally*.
---
source: https://docs.regentprotocol.org/control/integration.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# SDK & Integration
Three ways to integrate, one decision contract. Goal: **integrated in under 30 minutes, or
zero-code via MCP.**
| Surface | Who holds the provider key | Use when |
|---|---|---|
| **Python SDK** (`regent-control`) | your code, or the sidecar | Python agents/services |
| **TypeScript SDK** (`@regent-protocol/control-sdk`) | your code | Node/TS agents |
| **MCP** (sidecar `/mcp`) | the sidecar only | MCP-native agents — zero code |
> **Packages.** Python: `pip install regent-control` ([PyPI](https://pypi.org/project/regent-control/), Apache-2.0;
> `pip install "regent-control[verify]"` adds the token verifier). TypeScript: `npm install @regent-protocol/control-sdk`
> ([npm](https://www.npmjs.com/package/@regent-protocol/control-sdk), Apache-2.0, published with provenance from the public
> repository). Sidecar image: `ghcr.io/abay94/control-sidecar:latest`.
## Python — gate-direct
```python
from regent_control import RegentControl
control = RegentControl(api_key="rgnt_ctrl_…", agent_id="agent_refund_bot")
d = control.authorize(
tool="payments", action="refund.create",
amount_usd=50, mandate_id="mnd_support_refunds",
idempotency_key="T-8842:ch_aaa", # a retry won't double-refund
user_token=rep_id_token, # the human's OIDC token — gate-verified
intent="refund the duplicate charge on ticket 8842",
facts={"account_status": "active", "refund_to_original": True},
)
if not d.allowed:
raise RuntimeError(f"{d.code}: {d.reason}")
do_refund(scoped_token=d.token)
control.complete(d.decision_id, status="success", downstream_ref=refund_id)
```
A `deny` is a **return value**, not an exception — call `d.raise_for_status()` (or
`control.authorize_or_raise(...)`) to turn a non-allow into a typed error
(`MandateExceeded`, `PolicyDenied`, `Escalated`, …).
> Develop with zero prod traffic: `regent-control dev --deny-over 200 --escalate-over 1000`,
> then point the client at `base_url="http://localhost:8009"`. In tests,
> `from regent_control.dev import run_mock_control`.
## Python — sidecar-routing (the agent holds no key)
```python
from regent_control import SidecarSession, MandateExceeded
sc = SidecarSession(base_url="http://localhost:8080",
user_token=rep_id_token, intent="refund duplicate charge")
try:
resp = sc.call("payments", "refunds", method="POST",
json={"charge": "ch_aaa", "amount": 50},
facts={"account_status": "active", "refund_to_original": True})
except MandateExceeded as e:
... # the sidecar injected the real credential; you never saw it
```
## TypeScript — gate-direct
```ts
import { RegentControl, ControlDenied } from '@regent-protocol/control-sdk';
const control = new RegentControl({ apiKey, agentId: 'agent_refund_bot' });
const d = await control.authorize({
tool: 'payments', action: 'refund.create',
context: { amountUsd: 50, mandateId: 'mnd_support_refunds',
idempotencyKey: 'T-8842:ch_aaa', userToken: repIdToken,
intent: 'refund duplicate charge',
accountStatus: 'active', refundToOriginal: true },
});
if (!d.allowed) throw new ControlDenied(d.code, d.reason, d.decisionId);
await control.complete(d.decisionId, { status: 'success', downstreamRef: refundId });
```
## MCP — zero code
Point the MCP-native agent at the sidecar's `/mcp` endpoint. Each catalogued tool is an MCP
tool whose `inputSchema` accepts `path`, `method`, `body`, and the control fields `intent`,
`user_token`, and `facts`. A deny comes back as an MCP tool error (`isError: true`).
## Verify the scoped token at your service edge
The complement to the client: your downstream service validates the gate-issued scoped JWT
against Regent's JWKS and enforces the scope itself — least privilege at the edge.
```python
from regent_control.verify import TokenVerifier, ScopeError # pip install "regent-control[verify]"
verifier = TokenVerifier(jwks_url="https://control-api.regentprotocol.org/v1/control/.well-known/jwks.json")
scoped = verifier.verify(token, expected_tool="payments", expected_action="refund.create")
# TokenError → 401, ScopeError → 403; scoped.agent_id / scoped.decision_id are audit-ready
```
## Human-in-the-loop (escalation)
Any policy can return **`escalate`**. The action is parked for a human; the **idempotency
key is the resume handle**: the agent retries the same action with the same key, and once
approved it returns `allow` (denied → a terminal deny). Set the sidecar's
`CONTROL_ESCALATION_WAIT_SECONDS` > 0 to briefly poll inline; default `0` is the async/retry
model. Approve/deny in **Control → Approvals**.
## Error-code catalog
Every non-allow decision carries a stable `code`, mapped to a typed error by the SDKs.
| `code` | Meaning |
|---|---|
| `IDENTITY_NOT_RESOLVED` | agent id unknown |
| `AGENT_NOT_ACTIVE` | agent suspended/revoked |
| `POLICY_DENIED` | a Cedar rule forbade it |
| `TOOL_NOT_ALLOWED` | tool not in the catalog |
| `MANDATE_NOT_FOUND` | money action without a mandate |
| `MANDATE_LIMIT_EXCEEDED` | over a spend cap |
| `RISK_THRESHOLD_EXCEEDED` | risk score too high |
| `VELOCITY_EXCEEDED` | too many decisions for this agent in the window — back off, do not retry in a loop |
| `DUPLICATE_REQUEST` | an identical money request was allowed moments ago — check that decision; pass an `idempotency_key` for a deliberate repeat |
| `ESCALATION_REQUIRED` | needs a human approval |
| `INTERNAL_ERROR` | transport/plane failure — retry |
Transport failures (timeout, DNS, 5xx, bad key) are always raised; the gate fails **closed**.
## The agent contract — checklist
- **agent_id** + **tool/action** (the SDK attaches the agent id).
- **amount_usd + mandate_id** for any money action.
- **user_token** — the human's OIDC id_token when acting on someone's behalf (gate-verified).
- **intent** — why the agent is doing this (compliance, never trusted as enforcement).
- **facts** — verified context the policy keys on (`account_status`, `refund_to_original`).
- **idempotency_key** — a stable key per logical action; a retry replays the prior decision.
- **handle deny + escalate**; for `ESCALATION_REQUIRED`, park and resume on approval.
- **call `complete(...)`** to close the audit and reconcile counters.
---
source: https://docs.regentprotocol.org/control/enrol-agent.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Enrol an agent
An agent that runs on a machine you do not sit at — a cloud agent, a server, a teammate's
laptop — should not hold your organization's key. Enrolment gives it two things of its own:
- **its own signing key** (Ed25519), generated on that machine; Regent keeps only the public half;
- **a credential bound to this one agent** (`rgnt_agt_…`): the gate refuses it for any other
agent, and it cannot manage the organization, create mandates or approve its own escalations.
The owner mints a one-time code in the console; the machine turns it into both. Nothing
organization-wide is ever typed on the agent's side, and the code can travel through a chat:
it is single-use and expires.
### Mint the code in the console
Open the agent (**Agents → the agent**) and find the **Enrolment** card. Choose whether this
machine's requests must be signed (the default, and the right choice when the agent will use the
SDK or the hooks), then **New invite**. The console shows the code — `rgnt_inv_…`, shown once,
valid for 24 hours — together with the exact command to run.
### Run the command on the agent's machine
```bash
pip install regent-control
regent-control enroll rgnt_inv_… --label "where this runs"
```
The command generates the agent's key if the machine has none, registers the public half with a
proof of possession, and writes `~/.regent/agent-key.json` and `~/.regent/credentials.json`
(both mode 600). Its last lines name the registered key id and the agent. Options: `--key-file`
to reuse an existing key, `--credentials` to write elsewhere, `--platform-url` for another
Regent host.
### Use the credentials
The SDK and the hooks pick the files up by themselves:
```python
from regent_control import RegentControl
rc = RegentControl.from_credentials() # ~/.regent/credentials.json + agent-key.json
d = rc.authorize("stripe", "pay", resource="acct_supplier", amount_usd=12.5,
intent="pay the supplier invoice", idempotency_key="inv-4411-try-1")
if d.decision == "allow":
... # do the thing
rc.complete(d.decision_id, status="executed", downstream_ref="pi_…")
```
`d.decision` is `allow`, `deny` or `escalate`. An escalated request waits under **Approvals** in
the console; after a person approves, the agent repeats the same call with the same
`idempotency_key`. For Claude Code, Codex or Hermes, `regent-control hook install `
wires the same credentials into the runtime's hooks instead.
### Ask for a mandate
An enrolled agent holds a credential and, until a person sets limits, no mandate: every money
call is refused with `MANDATE_NOT_FOUND`. The agent may ask for one; it never grants one:
```python
req = rc.request_mandate(currency="USD", per_tx="10", daily="30", monthly="100",
purpose="office supplies")
print(req.request_id, req.status) # pending_approval
```
The organization's owners get the approval link by e-mail and a card under **Approvals**;
a person signs the limits (passkey in a production organization). `rc.mandate_request_status(req.request_id)`
reports `pending_approval`, `approved` (with the `mandate_id`), `denied` or `expired`; the
simpler way is to retry `authorize` later. One request may wait at a time, an agent that
already holds an active mandate in that currency is told so (`MANDATE_EXISTS`), and the
organization can switch agent requests off. The ask itself is filed in the agent's journal
as `mandate.requested`, next to the approval it led to.
## What the console shows afterwards
The Enrolment card lists every invite with its state (`pending`, `used`, `expired`, `revoked`),
which key used it, and the credential it produced. **Revoke** on a credential cuts that machine
off immediately; revoking the agent cuts off everything. Receipts for decisions made with an
enrolled key carry `key_custody: agent`, which is how a reader later tells "the agent held its
own key" from "Regent held a custodial key for it".
> Whoever runs the code first becomes that agent. Send it only to the machine or the party that
> should, and revoke it from the card if it goes astray before it is used.
## When not to enrol
- The agent lives in an app with custom MCP connectors (Claude, Claude Code): use
[Connect from your agent app](/gateway/connect-from-your-app) — no key to hold at all.
- The agent cannot run a command line but can send HTTP with a stored secret: the consent screen
gives it a bound key directly — see
[Without MCP](/gateway/tools-mcp#without-mcp-plain-http-with-a-key-bound-to-one-agent).
- The agent runs on your own infrastructure next to the sidecar: the sidecar holds the key and
the agent holds nothing — see [Run the Sidecar](/control/run-sidecar).
Verified on production: 2026-10-12 (`regent-control` 0.6.0).
---
source: https://docs.regentprotocol.org/control/ap2.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# AP2 Evidence
[AP2](https://ap2-protocol.org/) (Agent Payments Protocol, v0.2) is the Google/FIDO format for
proving that an agent's purchase was authorised: a **Checkout Mandate** bound to the merchant-signed
checkout and a **Payment Mandate** bound to the same checkout, each an *open* mandate (constraints
authorised on a trusted surface) plus a *closed* mandate the agent signs against it. Merchants and
payment processors verify the chains and answer with signed receipts.
Regent does not change how it decides. When a caller brings a merchant Checkout JWT, the gate adds
AP2 evidence to an allow.
## Roles
| AP2 role | Who | What it signs |
|---|---|---|
| Trusted Agent Provider / Trusted Surface | **Regent** — the owner authorised the agent on the console (KYC, mandate terms, policy); the gate checked this request | the open Checkout + Payment Mandates (ES256 provider key, in the control JWKS) |
| Shopping Agent | the agent, with a **custodial** key derived per agent and held by Regent | the closed mandates (key-binding hops) |
| Merchant | e.g. get4agent | the Checkout JWT and the Checkout Receipt |
| Merchant Payment Processor | e.g. get4agent's prepaid ledger | the Payment Receipt |
> The open mandates attest exactly what the gate allowed — this checkout's line items and merchant, this
> amount, this payee. The owner's ceilings never appear in AP2 artifacts; they stay under commit-and-reveal
> in Regent's own receipts.
## Asking for evidence
Add `ap2` to the decision context of a money action:
```json
{
"agent_id": "agent_…",
"tool": "market",
"action": "POST /call",
"args": { "listing_id": "…" },
"context": {
"amount": 0.95, "currency": "USD", "mandate_id": "…",
"ap2": {
"checkout_jwt": "",
"nonce": "",
"aud": "get4agent",
"instrument": { "id": "prepaid:agent_…", "type": "regent.prepaid_balance" }
}
}
}
```
An allow then carries `ap2`:
```json
{
"decision": "allow",
"decision_id": "dec_…",
"token": "…",
"ap2": {
"spec": "0.2",
"checkout_mandate": "~…~~~…",
"payment_mandate": "~…~~~…",
"transaction_id": "",
"checkout_reference": "",
"payment_reference": "",
"nonce": "…", "aud": "get4agent",
"provider_kid": "…", "agent_jwk": { "kty": "EC", "crv": "P-256", "…": "…" }
}
}
```
The `/complete` receipt of the same decision carries `ap2_transaction_id` and `ap2_payment_reference`,
so Regent's own evidence and the AP2 set point at each other.
## Verifying
Anyone holding the evidence can run the spec's dispute rules in one call:
```http
POST https://control-api.regentprotocol.org/v1/control/ap2/verify
{
"checkout_mandate": "…", "payment_mandate": "…",
"nonce": "…", "aud": "get4agent",
"checkout_receipt": "…", "payment_receipt": "…",
"receipt_keys": [ { "kty": "EC", "crv": "P-256", "kid": "…", "x": "…", "y": "…" } ]
}
```
The report lists every violation (chain signatures, constraint evaluation, `checkout_hash` versus
`transaction_id`, receipt signatures and references) and is `ok` only when there are none. The chains
also verify offline with Google's reference SDK against two public keys: the provider key in
`https://control-api.regentprotocol.org/v1/control/.well-known/jwks.json` and the merchant's key (for get4agent: `/v1/ap2/merchant.json`).
## On get4agent
Every governed prepaid call on [get4agent.com](https://get4agent.com) produces the full set: the
marketplace signs the checkout, the gate issues the chains, the marketplace verifies them before money
moves and signs both receipts after the charge. The set is available at
`GET https://get4agent.com/v1/ap2/evidence/{decision_id}` together with a ready-to-send verify body.
## What AP2 does not replace
AP2 is the evidence format. Enforcement — the mandate check, policy, behavioural signals, the kill
switch, the anchored audit trail — stays in the gate, before any money moves. See
[Concepts → Guardian](/concepts/guardian) and [Receipts](/control/integration).
---
source: https://docs.regentprotocol.org/control/settlement-confirmation.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Settlement confirmation
A completion receipt records what settled for a decision: status, amount, currency, payee.
Until October 2026 every one of those facts came from the caller of `/complete`, the agent or
its gateway, so a receipt attested to what the agent *said* happened. A settlement confirmation
lets the other side of the payment, the merchant, PSP, custodian or API provider, state the
outcome under its own signature. When it verifies, the receipt carries the counterparty's
figures, names the signer and embeds the signed statement, so anyone holding the receipt and
the two public key sets can check both signatures offline.
## Sending one
The counterparty signs a compact JWS and the caller passes it to `/complete`:
```http
POST https://control-api.regentprotocol.org/v1/control/decisions/{decision_id}/complete
Authorization: Bearer rgnt_ctrl_…
{
"status": "success",
"amount": 310.0, "currency": "KZT",
"confirmation": ""
}
```
The JWS must look like this:
| Part | Field | Rule |
|---|---|---|
| header | `alg` | `ES256`, `EdDSA`, `RS256` or `PS256` |
| header | `kid` | required; must appear in the issuer's JWKS |
| header | `typ` | `settlement-confirmation+jwt` |
| payload | `iss` | the issuer URL, trusted by the control plane (see below) |
| payload | `iat` | issued-at; may not lie in the future |
| payload | `exp` | optional; enforced when present |
| payload | `decision_id` | the decision being confirmed; must equal the one in the path |
| payload | `status` | `success` or `failed` |
| payload | `amount`, `currency` | optional; what actually settled (`currency` is required with `amount`) |
| payload | `payee` | optional; who was paid |
| payload | `ref` | optional; the counterparty's own reference, such as an order id or a transaction hash |
| payload | `kind` | optional; `merchant`, `psp`, `custodian` or `provider` |
## What the receipt says
| Claim | Meaning |
|---|---|
| `settlement_source` | `counterparty` when a confirmation verified, `agent` otherwise. Receipts minted before this claim existed carry none and are agent-reported. |
| `confirmed_by` | the confirmation's `iss` |
| `confirmation_kid` | the key that signed it |
| `confirmation_hash` | sha256 of the compact JWS, also cited by the audit trail |
| `confirmation` | the JWS itself, verbatim |
| `confirmation_ref`, `confirmation_kind` | copied from the confirmation when present |
| `agent_report_mismatch` | `true` when the caller's own status or amount differed from the counterparty's |
With a verified confirmation the receipt's `amount`, `currency`, `payee` and `status` are the
counterparty's. The caller's figures are kept beside them in the settlement audit event, so a
divergence is visible rather than overwritten. A `failed` confirmation without an amount settles
the decision at zero, which releases the mandate hold.
The `/complete` response reports what happened: `confirmation_accepted` is `true`, `false` (with
the reason in `confirmation_error`) or `null` when none was sent.
## Trust
The control plane verifies a confirmation against the issuer's published JWKS, fetched over
https and cached. Which issuers count is configured per deployment (`COUNTERPARTY_ISSUERS`) and
may be extended per organization. An issuer that is not trusted, a key that is not in the JWKS,
a confirmation for another decision, a future `iat` or an expired `exp` are all rejected.
A rejected confirmation never fails the completion. The mandate hold must reconcile whether or
not evidence arrived, so the completion is recorded from the caller's figures, the receipt says
`settlement_source: agent`, and the response carries the rejection code. A confirmation can only
add to a receipt what it proved.
## On get4agent
For prepaid purchases get4agent is the merchant of record: the buyer's balance is debited on the
marketplace, so get4agent signs the confirmation itself, with its Ed25519 issuer key published at
`https://get4agent.com/.well-known/jwks.json`, after the charge succeeds. The reference is the
listing, the supplier's order id for a delayed fulfilment, or the on-chain transaction for an
x402 purchase. If the charge fails after the supplier delivered, get4agent confirms a `failed`
settlement at zero, so the receipt never shows a purchase that was not paid for.
---
source: https://docs.regentprotocol.org/control/budgets.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Budget envelopes
A budget envelope lets an agent spend at a resource without a round trip to the control plane
on every call. The control plane slices an amount from the owner's active mandate and issues
an AAuth auth token (`typ: aa-auth+jwt`) carrying a `budget` claim. The resource verifies the
token, meters each request against the envelope and answers with an `AAuth-Budget` header.
When the envelope runs out, the resource refuses with a signed consumption record that the
agent brings back to the control plane for a fresh slice. The wire format is
`draft-hardt-aauth-budgets`; the token and signature rules are AAuth protocol -11, as published
on 25 September 2026. Regent Control acts as the person server (PS) in the three-party flow.
## Issuing an envelope
```http
POST https://control-api.regentprotocol.org/v1/control/budget-tokens
Authorization: Bearer rgnt_…
{
"agent_id": "agt_…",
"resource": "https://get4agent.com",
"cnf_jwk": { "kty": "OKP", "crv": "Ed25519", "x": "…", "alg": "Ed25519" },
"requested_amount_minor": 50000,
"budget_consumed": [ { "jti": "at_…", "consumed": 31000, "iat": 1760000000 } ]
}
```
The slice is denominated by the mandate it is carved from; a `unit` that disagrees is refused.
Records in `budget_consumed` settle earlier envelopes exactly once. The response carries the
token and its claims:
| Claim | Value |
|---|---|
| `iss` | `https://control-api.regentprotocol.org`, the PS identifier; its metadata lives at `/.well-known/aauth-access.json` |
| `dwk` | `aauth-access.json` |
| `ps` | the same identifier (three-party flow) |
| `aud` | the one resource the envelope is for |
| `sub` | the owner's agent id |
| `jti` | the envelope id; consumption is reported against it |
| `budget` | `{ "amount", "unit", "decimals" }` in minor units |
| `cnf.jwk` | the agent's registered public key, labelled with a fully-specified `alg` |
| `exp` | at most one hour after `iat` |
Tokens are signed `RS256`; the key is at `/v1/control/.well-known/jwks.json`.
## Spending it
The agent signs each request with the key in `cnf.jwk` and carries the token in
`Signature-Key` under the `jwt` scheme. The signature covers `@method`, `@authority`, `@path`
and `signature-key`, plus `content-digest` and `content-type` when the request has a body, and
names `created` within the resource's `signature_window` (120 seconds at get4agent). With
`regent-httpsig`:
```python
from regent_httpsig import EgressSigner
agent = EgressSigner(seed=SEED, signature_agent="https://myagent.example")
headers = agent.sign_aauth("POST", url, {"content-type": "application/json"},
token=auth_token, body=raw_body)
```
A served request answers with the remaining balance:
```http
AAuth-Budget: cost=300, remaining=49700, unit="KZT", decimals=2
```
## Refusals
| Situation | Status and headers |
|---|---|
| the envelope cannot cover the request | `401`, `AAuth-Requirement: requirement=auth-token; resource-token="…"; reason=insufficient-budget`, `AAuth-Budget: remaining=…, required=…` |
| the envelope is exhausted | the same with `reason=budget-exhausted` |
| the token has expired | `401`, `Signature-Error: error=expired_jwt`, `AAuth-Requirement: requirement=person-token` |
| the token was revoked | `401`, `Signature-Error: error=revoked_jwt`, `AAuth-Requirement: requirement=person-token` |
| the signature does not verify | `401`, `Signature-Error: error=` with `Accept-Signature-Scheme` or `Accept-Signature-Alg` where they apply |
The resource token in a budget refusal carries one consumption record, the presented token's
`{ "jti", "consumed" }`. An expired or revoked token gets no resource token: its final figure
reaches the control plane through the usage endpoint, not on the challenge. Error bodies are
`application/problem+json` with the `error` member.
## Revocation and usage
The control plane may revoke an envelope at the resource's `revocation_endpoint` with a signed
`POST { "jti", "exp" }`. It signs as a server, `Signature-Key: sig=jwks_uri; id="https://control-api.regentprotocol.org"; dwk="aauth-access.json"; kid="…"`, covering the body. The resource keys the
revocation by the caller's verified identity and the `jti`, answers an empty `200` whether or
not it has seen the token, and refuses later presentations of it with `revoked_jwt`.
The `usage_endpoint` answers a signed query from the PS with consumption counters per `sub`
and per key thumbprint, so settlement never depends on the agent carrying a record home.
## Where to look
- get4agent publishes its resource metadata at `https://get4agent.com/.well-known/aauth-resource.json`: `budget_units`, `usage_endpoint`, `revocation_endpoint`, `access_mode`, `signature_window`, `accept_signature_algs`.
- [`regent-httpsig`](https://github.com/regent-protocol/regent-httpsig) implements the resource side (verifier, meter, usage and revocation endpoints) and the signers.
- [Settlement confirmation](/control/settlement-confirmation) covers what the counterparty states after a payment; budgets cover what the agent may spend before one.
---
source: https://docs.regentprotocol.org/gateway.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Cloud Gateway & MCP
The **Cloud Gateway** is Regent Control delivered as a hosted service: your agent gets a
**URL and a key** — no container to deploy. Every tool call the agent makes (a Stripe charge,
a Slack message, an email) is authorized by the gate, and the real provider credential is
injected from **Regent's vault** — so the agent never holds it and physically can't bypass
the checks.
You configure it by **chatting over MCP** (or in the dashboard), and your agents **act** over
a second MCP endpoint.
## One brain, two MCPs
> There are **two** MCP servers. Don't mix them up: one is where a human *configures*, the other
> is where an agent *acts*.
| | **Admin MCP** (management) | **Tools MCP** (runtime) |
|---|---|---|
| **URL** | `https://api.regentprotocol.org/v1/mcp` | `https://gw.regentprotocol.org/gw/mcp` |
| **Purpose** | create agents, mandates, keys, tools | the agent calls tools + pays |
| **Auth** | `Authorization: Bearer ` | `Bearer ` + `X-Agent-Id` header |
| **When** | once, at onboarding | at runtime, every call |
| **Docs** | [Admin MCP →](/gateway/admin-mcp) | [Tools MCP →](/gateway/tools-mcp) |
## The three-sided rule
Every tool follows the same split — and it's deliberate:
| Part | Who does it | Where |
|---|---|---|
| **Configure** the tool (routing: base URL, encoding, spend limit) | you / your assistant | Admin MCP or dashboard |
| **Vault** the provider secret (the Stripe/Slack/AgentMail key) | **a human only** | dashboard `/connect/gateway` |
| **Call** the tool | the agent | Tools MCP `/gw/mcp` |
> The provider secret is the **only** step a human must do. Why can't the agent vault its own
> key? Because then the agent would have *seen* it — and the whole guarantee is that it never
> does. A secret must never pass through an agent's context.
## The flow
```text
Agent (Claude, LangChain, any)
│ MCP / HTTP + control key + X-Agent-Id
▼
gw.regentprotocol.org (Cloud Gateway, multi-tenant)
│ 1. key → org → your tool catalog
│ 2. intent → gate: identity ∥ mandate ∥ Guardian risk → Cedar policy
│ 3. ALLOW → inject the vaulted credential → call the provider
│ DENY → blocked, provider never touched
│ ESCALATE → parked for a human
▼
Stripe · Slack · AgentMail · custody wallet · any HTTP API
```
## Get started
---
source: https://docs.regentprotocol.org/gateway/connect-from-your-app.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Connect from your agent app
If your agent lives in an app you do not run — Claude, Claude Code, or any assistant that lets you
add a **custom MCP connector with OAuth** — it can use Regent without code and without keys to
paste. You add one URL, sign in, and approve a consent screen. From then on the app's agent
acts under a mandate you set, and every payment it attempts is decided first and leaves a
signed receipt.
- **Connector URL:** `https://gw.regentprotocol.org/gw/mcp`
- **Sign-in:** OAuth 2.1 with PKCE; the app registers itself, you never copy a client id or key
- **Verified with:** Claude.ai (web and desktop) and Claude Code. Other apps with the same
connector shape are listed below; we add each one here after running it ourselves.
> One connector is one agent. Connecting the same Regent organization from a second app creates a
> second agent with its own mandate, receipts and history. Disconnecting in the app revokes that
> agent's access.
## What happens when you connect
### Add the connector in the app
Paste the connector URL where the app asks for a custom MCP server. Choose the option that lets
the app register itself automatically; leave any client id or client secret field empty. The app
discovers Regent's sign-in through the standard metadata documents, so there is nothing else to
configure.
### Sign in to Regent
The app opens Regent's sign-in page in your browser. If you have no account yet, the self-serve
signup gives you a **sandbox** organization (test rail, no identity verification) — enough to
connect and see the whole loop. Verify your email before you start the connection; the app's
request waits ten minutes.
### Approve the consent screen
The screen says which app wants to act for you and asks for four things, once:
| Field | What it means |
|---|---|
| **Agent** | A name for this connection; it becomes an agent with its own passport and receipts. |
| **Allowance** | The amount a single payment may reach before a person is asked. The default is **ask me every time**. |
| **Ceiling** | The mandate: per-transaction, daily and monthly limits. A sandbox uses Regent's defaults; a production organization states its own. |
| **Scope** (optional) | What it may buy: tool names or resource prefixes, one per line. Empty means the limits alone apply. |
You can also say which model runs the agent (optional, declared, not verified) and what the
agent is for. In a production organization the terms are signed with your **passkey**; that
signature is what later proves a person agreed to exactly these limits.
### Make the first purchase
Back in the app, the agent lists Regent's tools and can act. With the default allowance, its first
payment is **escalated**: the agent is told a person must approve, you see the request under
**Approvals** in the console, and after you approve (passkey in production) the agent retries
the same request and it goes through. Raise the allowance on the approval card or in the console
and smaller payments stop asking.
## App by app
### Claude.ai
Settings → Connectors → **Add custom connector**. Give it a name, paste the URL, and leave the
OAuth client id and secret empty (Claude registers itself). Click **Connect**, sign in, approve
the consent screen. Claude reads the tool list at connect time: if you add a tool to your catalog
later, disconnect and connect again so Claude sees it.
### Claude Code
```bash
claude mcp add --transport http regent https://gw.regentprotocol.org/gw/mcp
```
Then run `/mcp` inside Claude Code and choose **Authenticate** for `regent`. The browser opens on
the consent screen; the local callback works without a fixed port. Claude Code identifies itself
with a published client metadata document, which Regent accepts as the client id.
### No custom connectors in your app?
An agent that can call HTTP with a stored secret but cannot add an MCP connector uses a key
bound to one agent instead — see
[Without MCP: plain HTTP with a key bound to one agent](/gateway/tools-mcp#without-mcp-plain-http-with-a-key-bound-to-one-agent).
### Other apps
Any app whose custom connectors take a remote MCP URL and sign in with OAuth 2.1 follows the same
four steps — ChatGPT's custom connectors and Grok Bot's custom connectors (paid tiers) have this
shape. An app that runs on its own cloud computer cannot reach a connector on your laptop, which
is why the URL above is a hosted one. We add an app to the verified list only after running the
full loop with it; tell us if you did.
## What the app receives
- An access token valid for **one hour**, bound to **this one agent**; the app refreshes it
without asking you again, and a refreshed token replaces the old one.
- The tools in your organization's catalog, each in two forms: `_read` for reads and
`` for writes, so the app can tell them apart and ask before writing.
- A decision on every money call before it reaches the provider: identity, mandate, your policy,
behavioral checks. Allowed calls run with a credential kept in Regent's vault; the app never
sees it. Refused calls come back with a code the agent can act on, and escalations say what to
do next.
- A signed receipt for every allowed payment, in your audit trail, verifiable offline.
The app itself is not asked to sign requests: agents that arrive through a connector hold no key
of their own, and their receipts say so (`key_custody`). Agents you run yourself can sign with
their own keys — see [SDK & Integration](/control/integration).
## Sandbox and production
A self-serve signup starts in a **sandbox**: Regent's own test rail (Stripe test mode), a hard
ceiling on amounts, real decisions and real receipts labelled `environment: sandbox` in every
token. Nothing in a sandbox is evidence of a production payment, and the label travels with the
receipt so nobody can present it as one. Going live needs identity verification and a passkey;
the connection, the agent and its history carry over.
## What this does not do
- It does not govern the app's **own** wallet. An assistant that pays through its vendor's wallet
keeps that vendor's approval flow; Regent governs the purchases the agent makes through
Regent's tools and rails.
- It does not read the app's conversation. Regent sees the tool calls, their amounts and the
intent the agent states — that is what the decision and the receipt are made of.
- It does not make a consumer agent an enterprise one. The mandate, the approvals and the receipts
are built for an organization with a named accountable owner.
## For implementers
The connector is a standard OAuth 2.1 protected resource; any MCP client that implements the
discovery below can connect without a Regent-specific integration.
| Document | URL |
|---|---|
| Protected resource metadata (RFC 9728) | `https://gw.regentprotocol.org/.well-known/oauth-protected-resource` |
| Authorization server metadata | `https://api.regentprotocol.org/.well-known/oauth-authorization-server` |
| Authorization / token / registration | `/oauth/authorize`, `/oauth/token`, `/oauth/register` on `api.regentprotocol.org` |
Supported: PKCE `S256` only; dynamic client registration (RFC 7591) and client id metadata
documents (a public `https` URL as the client id); `none`, `client_secret_post` and
`client_secret_basic` at the token endpoint; scope `tools`; the `iss` parameter in authorization
responses; loopback redirect URIs matched without the port for command-line clients. A client
metadata document is fetched over public `https` only, without redirects, and is refused if it
does not name itself.
Verified on production: 2026-10-12 (Claude.ai, Claude Code).
---
source: https://docs.regentprotocol.org/gateway/admin-mcp.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Admin MCP — set up by chatting
The **Admin MCP** is the management plane exposed over MCP. Connect Claude (or any MCP client)
and do everything the dashboard does — create agents, request spend mandates, issue keys, add
tools — with the same permissions and identity checks.
- **URL:** `https://api.regentprotocol.org/v1/mcp`
- **Auth:** `Authorization: Bearer ` (from the dashboard → **API keys**)
- **Protocol:** JSON-RPC 2.0 (`initialize`, `tools/list`, `tools/call`)
On connect the server returns usage **`instructions`** (clients like Claude read them as
guidance). Call **`get_started`** any time to see the flow *and where your org stands*.
## Client config
```json
{
"mcpServers": {
"regent-admin": {
"url": "https://api.regentprotocol.org/v1/mcp",
"headers": { "Authorization": "Bearer YOUR_API_KEY" }
}
}
}
```
## Tools
### Start
| Tool | What it does | Use case |
|---|---|---|
| `get_started` | Explains the flow + your org's current state | *"What do I need to do next?"* — the assistant reads it and guides you |
| `get_kyc_status` | Is the owner identity-verified? | Gate before creating agents/mandates |
### Agents & mandates
| Tool | What it does | Use case |
|---|---|---|
| `create_agent` | Register an agent (DID + audit trail) | *"Create a shopper-bot agent"* |
| `list_agents` · `get_agent` | Browse / inspect agents | Find an agent's id + status |
| `request_mandate` | Request a spend limit — **a human approves by email** | *"Give shopper-bot $10/tx, $50/day"* → owner clicks the emailed link |
| `list_mandates` | See an agent's mandates | Confirm a limit is active |
| `get_limit_templates` | Suggested spend-limit presets | Start from a safe "challenge test" limit |
> `request_mandate` never activates a budget by itself — a **human owner must approve** the emailed
> link. An agent can *request* spend authority but can never *grant* its own.
### Custody wallet
| Tool | What it does | Use case |
|---|---|---|
| `activate_wallet` | Create a custody wallet (Regent co-signs) | Give an agent its own on-chain account |
| `get_wallet` | Status, deposit address, balance | Check a wallet is funded before it pays |
### Cloud Gateway setup
| Tool | What it does | Use case |
|---|---|---|
| `create_control_key` | Issue the `rgnt_ctrl_…` key the agent presents to the gateway (**shown once**) | Put it in the agent's config as the Bearer token |
| `list_control_keys` | List issued keys (prefixes only) | Audit which agents are connected |
| `add_gateway_tool` | Add a provider to the catalog | *"Add Stripe (form, cents), bound to the mandate"* |
| `list_gateway_tools` | The org's tool catalog | See what's available to agents |
| `list_gateway_credentials` | Which provider keys are vaulted (fingerprints only) | Tell whether a tool is ready to call |
> The Admin MCP is **config + read only** — no runtime tool calls, no secret handling. To vault a
> provider secret, a human does it in the dashboard (`/connect/gateway`). To *use* the tools, the
> agent connects to the [Tools MCP](/gateway/tools-mcp).
## A full onboarding, in order
```text
get_kyc_status → owner verified?
create_agent {name:"shopper"} → registers the agent
request_mandate {agent, limits} → owner approves by email
create_control_key {name} → key shown once → agent config
add_gateway_tool {name, base_url} → add a provider (e.g. stripe)
── then a human vaults the key at /connect/gateway ──
── then the agent calls it via the Tools MCP ──
```
---
source: https://docs.regentprotocol.org/gateway/tools-mcp.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Tools MCP — where the agent acts
The **Tools MCP** is the Cloud Gateway's runtime endpoint. An MCP-native agent connects here
and calls tools; each call is authorized by the gate and executed with a **vaulted credential**
the agent never sees.
- **URL:** `https://gw.regentprotocol.org/gw/mcp`
- **Auth (every request):** `Authorization: Bearer ` **+** `X-Agent-Id: `,
or an OAuth access token from the connector sign-in (see below)
- **Protocol:** JSON-RPC 2.0 (`initialize`, `tools/list`, `tools/call`)
> Using Claude, Claude Code or another app with custom connectors? You need no headers at all:
> add the URL as a custom connector and sign in — [Connect from your agent app](/gateway/connect-from-your-app).
> The configuration below is for agents you run yourself.
## Client config
```json
{
"mcpServers": {
"regent-gateway": {
"url": "https://gw.regentprotocol.org/gw/mcp",
"headers": {
"Authorization": "Bearer YOUR_CONTROL_KEY",
"X-Agent-Id": "YOUR_AGENT_ID"
}
}
}
}
```
The control key comes from `create_control_key` (Admin MCP); the agent id from `create_agent`.
## Calling a tool
`tools/list` returns exactly the tools in **your catalog** (see the
[Tool Catalog](/gateway/tool-catalog)), plus the built-in `pay` tool. Each `tools/call` runs
the full enforcement flow: **gate → inject the vaulted credential → call the provider**.
```json
{
"method": "tools/call",
"params": {
"name": "stripe",
"arguments": {
"method": "POST",
"path": "v1/payment_intents",
"body": { "amount": 500, "currency": "usd" },
"intent": "pay the supplier invoice"
}
}
}
```
Arguments: `method` (default `GET`), `path`, `body?`, `intent?` (recorded in the audit),
`user_token?` (OIDC id_token when acting for a person), `facts?` (verified facts for policy).
## Without MCP: plain HTTP with a key bound to one agent
The same tools answer plain HTTP at `https://gw.regentprotocol.org/gw/{tool}/{path}`, and the
gate decides each call the same way. For an agent that cannot speak MCP or sign in through
OAuth — a browsing agent, Browser Use, Hermes, your own code — give it a key that can act as
**one agent only**:
1. In the console open **Connect → Connect an agent in one screen**. Name the agent, set the
allowance and the ceiling, approve. The screen shows a control key bound to that agent
**once**; it begins with `rgnt_agt_`. Put it wherever the agent keeps its secrets.
2. Call with the key alone. No `X-Agent-Id` header: the key names the agent, and it is refused
for any other agent.
```bash
curl https://gw.regentprotocol.org/gw/stripe/v1/balance \
-H "Authorization: Bearer rgnt_agt_..."
```
3. A call that moves money is decided first. With the default allowance the first one comes back
`escalated`; a person approves it under **Approvals** in the console, and the agent retries
with the same `idempotency_key`.
A machine that can run a command line has a better option: the owner mints an **invite** on the
agent's page, and `regent-control enroll ` generates the agent's own Ed25519 key, registers
the public half with a proof of possession and writes a bound key to `~/.regent/credentials.json`.
Requests are then signed (RFC 9421), the organization can require that signature, and the
receipts say the agent held its own key. See [Enrol an agent](/control/enrol-agent).
What a bound key cannot do: act as another agent (`403`), manage the organization, create
mandates, or approve its own escalations.
## The built-in `pay` tool
Every agent gets a built-in **`pay`** tool — a payment from the agent's **Custodian custody
wallet** (a second rail alongside API tools). It's gated the same way and **KMS co-signed** by
Regent.
```json
{ "name": "pay",
"arguments": { "to_address": "0x…", "amount": 5, "mandate_id": "…",
"intent": "supplier payout" } }
```
Returns `pending` (submitted, settles via webhook), `denied`, or `escalated`. Requires an active
wallet + a spend mandate. See [Custody](/concepts/mandate) and the Wallets page in the dashboard.
## What every call is checked against
The same gate decision runs on API tools **and** custody payments:
| Check | Blocks when |
|---|---|
| **Identity** | the `X-Agent-Id` isn't a registered, active agent |
| **Mandate** | a money tool exceeds its per-tx / daily / monthly limit |
| **Cedar policy** | your rule forbids it — by tool, action, `resource` (recipient), amount, role |
| **Guardian** | behavioral risk is too high (can escalate) |
Everything **fails closed**: no gate, no vaulted key, or a policy `deny` → the provider is never
called. A `deny` is a normal, readable outcome (the agent sees the code + reason).
## Per-agent tool control & modes
- **Disable a tool for one agent** — in the dashboard, the agent's page has a per-tool on/off
switch. A disabled tool disappears from that agent's `tools/list` and any call returns
`TOOL_DISABLED_FOR_AGENT`.
- **Enforcement mode** — `enforce` (a deny blocks) vs `observe` (shadow: records what it *would*
block but lets the call run). Check the mode before a live demo.
---
source: https://docs.regentprotocol.org/gateway/tool-catalog.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Tool Catalog
A **tool** is any HTTP API an agent reaches through the gateway. Pick from the curated
**connector library** (base URL + auth pre-filled) or add **any** HTTP API as a custom tool.
Each one is gated, and its key stays in Regent's vault.
> A tool is callable only when it has **both** a catalog entry (routing, added below) **and** a
> vaulted key (the secret, connected by a human at `/connect/gateway`). Matched by name.
## How to add a tool
1. **Dashboard** → Connect → **Set up Cloud Gateway** → *Add a tool* → pick a connector (or
*Custom API*) → **Add**. Then **Connect a key** and paste the secret.
2. **Over MCP** → `add_gateway_tool { "name": "slack", "base_url": "https://slack.com/api" }`
(Admin MCP), then a human vaults the key.
Money tools bind a **mandate** (`mandate_id`) so their spend is enforced; everything is still
subject to your **Cedar policy**.
## Built-in
| Tool | Use case |
|---|---|
| **`pay`** (custody) | Pay from the agent's Custodian wallet — on-chain, KMS co-signed by Regent, checked against the mandate. *"Pay this supplier 5 USDC."* Blocked over-limit or by policy before any signing. |
## Payments
| Tool | Use case |
|---|---|
| **Stripe** | Agent pays an invoice or issues a refund — capped by a `$X/tx` mandate; over-limit charges are **denied**, every charge is anchored with its decision id. |
| **Modern Treasury** | Initiate an ACH / bank payment — spend-limited + audited; escalate large transfers to a human. |
| **Bank Core (generic)** | Your internal bank-core API — instance-specific host; the credential is vaulted, egress is locked to it. |
## Comms
| Tool | Use case |
|---|---|
| **Slack** | Agent posts a status to `#ops` — Cedar can restrict *which channels*; rate limits stop a loop from flooding. |
| **Twilio** | Send an SMS/voice alert — rate-limited so a hijacked agent can't blast messages; token never leaves the vault. |
| **SendGrid** | Transactional email — gated + audited; policy can require an approved template. |
| **AgentMail** | Give the agent its own inbox (send + receive) — policy **allowlists recipient domains**, rate-limits sends, and audits every message. |
## CRM & Sales
| Tool | Use case |
|---|---|
| **Salesforce** | Read/write contacts, opportunities, cases — object-level policy: *"deny delete on Contact."* |
| **HubSpot** | Log an activity or update a deal — the OAuth token is vaulted; the agent only gets scoped writes. |
## Compliance
| Tool | Use case |
|---|---|
| **Chainalysis** | Screen a wallet/address for sanctions before a payout — the result is a verified fact your policy can key on. |
| **Persona** · **Onfido** | Run identity verification — every check is audited; keys never touch the agent. |
## Data
| Tool | Use case |
|---|---|
| **Supabase** (PostgREST) | Query/write your DB over HTTP — table-level control via policy; instance host is per-tenant. |
| **Hasura** | GraphQL over Postgres — the admin secret is vaulted; the agent can only run allowed operations. |
## Docs & Storage
| Tool | Use case |
|---|---|
| **DocuSign** | Send a document for signature — scoped, audited; escalate high-value envelopes. |
| **Box** · **Google Drive** | Fetch or store a file — scoped access, credential vaulted, responses can be redacted before the agent sees them. |
## Dev & Workflow
| Tool | Use case |
|---|---|
| **GitHub** | Open an issue or a PR — the token never touches the agent; policy can restrict repos. |
| **Linear** · **Notion** | Create issues / update docs — scoped writes, full audit trail. |
## AI providers
| Tool | Use case |
|---|---|
| **OpenAI** · **Anthropic** | Call an LLM with a vaulted key — rate-limited, and the key can't leak through the agent. |
## Observability
| Tool | Use case |
|---|---|
| **Datadog** | Push a metric or query monitors — read-only can be enforced; API key stays vaulted. |
## Custom tools
Any HTTP API works: give it a **base URL** and a **bearer token**. Use a **bare** base URL
(`https://api.example.com`, not `.../v1`) — the agent supplies the path. Add it as *Custom API*
in the dashboard or via `add_gateway_tool`.
> The tool name in the catalog and the vaulted-key name must **match exactly**. Add the tool
> first, then connect its key.
---
source: https://docs.regentprotocol.org/sdk-python/guide/introduction.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Introduction
> Async Python SDK for the Regent Protocol — agents, mandates, audit, guardian.
The Regent Python SDK is the canonical client for backend services and AI agents written in Python. It wraps the REST API with typed Pydantic models, an async HTTP layer, and structured error handling.
| | |
|---|---|
| **Package** | [`regent` on PyPI](https://pypi.org/project/regent/) |
| **Python** | 3.11+ (async/await throughout) |
| **HTTP** | `httpx` |
| **Models** | Pydantic v2 |
| **Source** | [`abay94/regent-sdk-python`](https://github.com/abay94/regent-sdk-python) |
| **License** | MIT |
## Design philosophy
- **Async-first.** Every method is a coroutine. No sync wrappers, no thread pools.
- **Typed end-to-end.** Request/response models are Pydantic; method signatures use precise types.
- **Composable, not monolithic.** Sub-clients (`identity`, `payment`, `audit`, `guardian`) expose a focused surface per protocol service.
- **Errors as data.** Every failure is a structured `RegentAPIError` with a code, message, request ID, and details — no string parsing required.
## Surface area
```python
from regent import RegentClient
async with RegentClient(base_url=..., api_key=...) as r:
r.identity # register/get/revoke agents, resolve DIDs
r.payment # create mandates, authorize payments
r.audit # ingest events, list events, fetch batches
r.guardian # latest score, score history, alerts
```
Each sub-client mirrors a backend service. Methods are 1:1 with REST endpoints — if you know the API, you know the SDK.
## Minimal example
```python
import asyncio
from decimal import Decimal
from regent import RegentClient, AuthorizeRequest, IngestEventRequest
async def main():
async with RegentClient(
base_url="https://api.regentprotocol.org",
api_key="rgnt_...",
) as r:
auth = await r.payment.authorize(
mandate_id="cab07ae3-...",
request=AuthorizeRequest(amount=Decimal("50"), currency="USD"),
)
await r.audit.ingest_event(IngestEventRequest(
event_id=f"action-{auth.jti}",
agent_id="agent_b1c59d23...",
event_type="action.completed",
payload={"amount": "50", "jti": auth.jti},
))
asyncio.run(main())
```
That's it — a full authorize-then-log cycle in ten lines.
## What to read next
- [Installation](/sdk-python/guide/installation) — pip install + a couple of optional extras.
- [Authentication](/sdk-python/guide/authentication) — How API keys, org scoping, and the gateway base URL fit together.
- [Binance trading agent example](/use-cases/ai-trading-agent) — A full real-world example: 850 lines of agent using every SDK surface.
---
source: https://docs.regentprotocol.org/sdk-python/guide/installation.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Installation
> Install the Regent Python SDK and verify the version.
## Requirements
- Python **3.11+**
- A Regent account, KYC verified, with an API key (`rgnt_…`)
## Install
The SDK is not on PyPI yet, and the PyPI names `regent` and `regent-sdk` belong to **unrelated projects** — do not `pip install regent`. Install straight from the public GitHub repository:
```bash
pip install "git+https://github.com/abay94/regent-sdk-python.git"
```
For local development from a checkout:
```bash
git clone git@github.com:abay94/regent-sdk-python.git
cd regent-sdk-python
pip install -e .
```
## Verify
```bash
python -c "import regent; print('regent SDK import OK')"
pip show regent-sdk # Name: regent-sdk, Version: 0.1.0
```
## Optional extras
For the included examples (Binance trading agent, etc.) you may also want:
```bash
pip install httpx pydantic rich
```
- `httpx` is a hard dependency of the SDK — installed automatically.
- `pydantic` is a hard dependency — installed automatically.
- `rich` is optional but recommended for the demo Binance agent's terminal UI.
## Async runtime
The SDK is async-only. Use it inside `asyncio.run(...)` or within an existing event loop. In Jupyter notebooks, `asyncio.run` may collide with the existing loop — use `await` directly at the top level instead.
```python
# Jupyter / IPython
import nest_asyncio; nest_asyncio.apply()
from regent import RegentClient
async with RegentClient(...) as r:
agent = await r.identity.get_agent("agent_b1c59d23...")
print(agent)
```
## What next
- [Authentication](/sdk-python/guide/authentication) — Configure the base URL and API key — and understand how org scoping works.
---
source: https://docs.regentprotocol.org/sdk-python/guide/authentication.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Authentication
> API keys, the gateway base URL, and how organization scoping works.
The SDK authenticates every request with a single bearer token: your **API key**.
## Get an API key
In the dashboard, go to **API Keys** → **Create API Key**. Name it (e.g. `trading-bot-prod`) and copy the `rgnt_…` value. The key is shown **once** — store it in a secrets manager.
Each API key is scoped to **one organization**. If you have multiple orgs, you'll have one key per org.
## Initialize the client
```python
from regent import RegentClient
async with RegentClient(
base_url="https://api.regentprotocol.org",
api_key="rgnt_...",
) as r:
# use r.identity, r.payment, r.audit, r.guardian
pass
```
The client is a context manager — `async with` guarantees the underlying `httpx` connection pool closes cleanly.
For long-lived services that hold a single client for the process lifetime:
```python
import asyncio
from regent import RegentClient
class TradingBot:
def __init__(self):
self.regent = RegentClient(
base_url="https://api.regentprotocol.org",
api_key=os.environ["REGENT_API_KEY"],
)
async def close(self):
await self.regent.aclose()
```
## Environment variables
Convention (used by the example Binance agent):
| Var | Used for |
|---|---|
| `REGENT_BASE_URL` | Override the gateway URL — defaults to `https://api.regentprotocol.org` |
| `REGENT_API_KEY` | Required — your `rgnt_…` API key |
| `REGENT_ORG_ID` | Your organization UUID — needed for org-scoped endpoints |
| `REGENT_AGENT_ID` | The agent ID this process represents |
| `REGENT_MANDATE_ID` | The mandate to authorize against |
## Org scoping
Some endpoints require an explicit org ID in the path (e.g. `GET /v1/organizations/{org_id}/agents`). The SDK accepts the org ID as a method argument for those:
```python
agents = await r.identity.list_agents(org_id="dbf24dc8-...")
```
Your API key must be scoped to the same org, or the request returns `403 NOT_A_MEMBER`.
## Gateway vs direct service URLs
Production: always use `https://api.regentprotocol.org` (the platform gateway). The gateway:
- Verifies your API key
- Enforces org membership
- Forwards to the right backend service
- Aggregates errors into the standard `{code, message, request_id, details}` shape
Local development (running `docker compose up` from `regent-protocol`):
```python
RegentClient(base_url="http://localhost:8005", api_key="rgnt_local_...")
```
…still hits the gateway, just on localhost.
You should not bypass the gateway and hit individual services in `8001`/`8002`/`8003`/`8004` — those are internal and don't enforce auth.
## What next
- [Register an agent](/rest-api/agents) — First real call: create an agent under your organization.
---
source: https://docs.regentprotocol.org/rest-api/authentication.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Authentication
> Authenticate REST API requests with a bearer token.
Regent has three production hosts and **two kinds of key**. Sending the wrong kind returns a 401 on every host, so check this table first:
| Host | What it is | Key to send | Where you get it |
|---|---|---|---|
| `api.regentprotocol.org` | Platform API — organizations, agents, mandates, audit, KYC | **Org API key** `rgnt_…` | Dashboard → **API Keys** → Create API key |
| `control-api.regentprotocol.org` | Regent Control decision API — `/v1/control/decisions`, `/complete`, receipts, JWKS | **Control key** `rgnt_ctrl_…` | Dashboard → **Control → Onboarding**, or Admin MCP `create_control_key` |
| `gw.regentprotocol.org` | Cloud Gateway — governed tool calls on the agent's behalf | **Control key** `rgnt_ctrl_…` + `X-Agent-Id` header | same as above |
An org key on `control-api` answers `401 {"code":"UNAUTHORIZED","message":"invalid API key"}`; a control key on
`api.regentprotocol.org` answers `401 {"code":"INVALID_API_KEY"}`. The two are different secrets scoped to the same
organization — the control key never reaches the platform API and never reads your org's data.
The rest of this page is about the **platform API**. Its requests authenticate with a single header:
```
Authorization: Bearer rgnt_
```
Or equivalently (some tools prefer the explicit header name):
```
X-API-Key: rgnt_
```
## Get an API key
From the dashboard: **API Keys** → **Create API Key**. The `rgnt_…` value is shown once.
## Smoke test
```bash
curl https://api.regentprotocol.org/v1/organizations//agents \
-H "Authorization: Bearer rgnt_..."
```
A 200 with a JSON array (possibly empty) means auth and org scoping are both working.
A 401 means the key is missing/expired. A 403 with `code: NOT_MEMBER` means the key is for a different org than the one in the path.
## Org scoping
Every `/v1/organizations/{org_id}/...` endpoint requires the org ID to match the org your API key is scoped to. The gateway enforces this server-side — you can't access another org's data even if you guess UUIDs.
## Rate limits
Current per-IP limits: **30 requests/second** (burst 20) on API routes; login and
signup are limited to 5/min. Rate-limited responses return `429 RATE_LIMITED` with a
`Retry-After` header.
## Error format
Every non-2xx returns the same shape:
```json
{
"detail": {
"code": "MANDATE_LIMIT_EXCEEDED",
"category": "PER_TX",
"message": "amount exceeds the per-transaction allowance",
"authorization_id": "abc..."
}
}
```
See the [error code reference](/rest-api/errors) for the full list.
## What next
- [Agents](/rest-api/agents) — Register, list, get, and revoke agents.
- [Mandates](/rest-api/mandates) — Create and revoke mandates, then authorize against them.
---
source: https://docs.regentprotocol.org/rest-api/agents.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Agents
> Register, list, get, and revoke agents over REST.
All routes are org-scoped: `https://api.regentprotocol.org/v1/organizations/{org_id}/…`
with your `rgnt_…` bearer key ([authentication](/rest-api/authentication)).
## Register an agent
Requires a verified email and, in a **production** organization, completed identity
verification on your account (`403 EMAIL_NOT_VERIFIED` / `403 KYC_NOT_VERIFIED` otherwise).
A **sandbox** organization skips the identity check: its agents can only use Regent's test
rail under a hard ceiling, and every token they get is labelled `environment: sandbox`.
The responsible party is always the authenticated user — it cannot be supplied by the client.
```bash
curl -X POST https://api.regentprotocol.org/v1/organizations//agents \
-H "Authorization: Bearer rgnt_..." -H "content-type: application/json" \
-d '{"settlement_chain": "solana",
"metadata": {"bot_name": "demo-bot", "model_vendor": "anthropic", "framework": "claude-code"}}'
```
`metadata` is yours to fill; three keys have a meaning to Regent:
| Key | Meaning |
|---|---|
| `bot_name` | The display name in the console. |
| `model_vendor` | Which model vendor runs the agent: `openai`, `anthropic`, `google`, `meta`, `mistral`, `xai`, `deepseek`, `other` or `unknown`. Declared by you and never verified; the record keeps `model_vendor_basis: declared` next to it so a reader knows that. Any other value is refused (`422 INVALID_RUNTIME`). |
| `framework` | Free text, at most 64 characters (for example `hermes`, `langgraph`). |
Agents created through the consent screen or an app connector get the same keys from the screen
or from the connector's name.
The response is the identity record. **Two identifiers come back — they are not
interchangeable:**
```json
{
"id": "6a29b7b2-…", // record UUID — internal reference
"agent_id": "agent_688b10bc62aef…", // canonical identifier — use THIS everywhere
"did": "did:regent:solana:agent_688b…",
"responsible_party_id": "…",
"status": "active",
"settlement_chain": "solana",
"identity_payload": "…", // KMS-signed at registration
"identity_signature": "…"
}
```
Every other endpoint — mandates, revocation, audit — takes the `agent_…` string.
Passing the record UUID returns `422 AGENT_ID_MALFORMED` with a message naming the
mistake.
Registration queues a Solana anchoring transaction; the agent's on-chain status flips
to `anchored` within a few seconds on devnet.
## List / get
```bash
GET /v1/organizations//agents # all agents in the org
GET /v1/organizations//agents/ # one agent (agent_… identifier)
```
## Revoke — the kill switch
```bash
POST /v1/organizations//agents//revoke
```
Requires the `admin` role. One-way: a revoked agent cannot be re-activated (register a
new key instead). All the agent's active mandates are suspended via the `agent.revoked`
event — in our benchmarks the first refused authorize follows within ~0.5 s. A revoked
agent's authorize calls fail with `402 MANDATE_SUSPENDED` (or `402 AGENT_NOT_ACTIVE`).
---
source: https://docs.regentprotocol.org/rest-api/mandates.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Mandates & Authorize
> Create spending mandates and authorize payments against them — the pre-execution gate.
A mandate is the on-record spending rulebook for one agent. Every payment is authorized
against it **before execution**; an amount outside the rules is rejected and no money
moves.
## Create a mandate
In a **production** organization this requires completed identity verification
(`403 KYC_NOT_VERIFIED` otherwise); a **sandbox** organization skips it. `agent_id` must be
the canonical `agent_…` identifier from registration — a record UUID gets an immediate
`422 AGENT_ID_MALFORMED`, an agent outside your organization a `404 AGENT_NOT_FOUND`.
**Who may set limits.** Limits are terms a person agrees to, so an API key never activates a
mandate by itself. A request made with an API key (or by a member below admin) returns
`202` with `status: pending_approval`; the organization's owner or admin approves it — in a
sandbox through the link e-mailed to them, in a production organization with their passkey
in the console — and only then is the mandate `active`. A request made from an owner or
admin session with a passkey is signed and activated in one step.
An enrolled agent may also ask for its own mandate with its agent credential
(`POST /v1/agent/mandates/request`: currency, limits, scope, purpose); the request lands under
**Approvals** and in the owners' inbox, and the agent only retries `authorize` once a person has
signed. See [Enrol an agent](/control/enrol-agent#ask-for-a-mandate).
```bash
curl -X POST https://api.regentprotocol.org/v1/organizations//mandates \
-H "Authorization: Bearer rgnt_..." -H "content-type: application/json" \
-d '{
"agent_id": "agent_688b10bc62aef…",
"owner_id": "",
"currency": "USD",
"limits": {
"per_tx_limit": "50",
"daily_limit": "300",
"monthly_limit": "5000"
}
}'
```
### Limit fields
| Field | Meaning |
|---|---|
| `per_tx_limit` | Maximum for a single authorization |
| `daily_limit` / `monthly_limit` | Rolling usage ceilings (settle adjusts them) |
| `entity_key` + `per_entity_limit` | Per-entity sub-budget (e.g. `entity_key: "customer"`, at most N per customer/month) — each authorize must then carry `entity_id` |
| `relational_cap` | Bound the action by a referenced anchor's amount (e.g. a refund must not exceed the original charge) — authorize must then carry `reference` + `reference_amount` |
Optional: `expires_at`, `metadata`. `settlement_chain` is `solana` (the only live anchor).
The response includes the mandate `id`, `status: active`, and a `terms_commitment` in
metadata — the on-chain anchor gets a commitment to the terms, never the ceilings
themselves.
## Authorize a payment
```bash
curl -X POST https://api.regentprotocol.org/v1/organizations//mandates//authorize \
-H "Authorization: Bearer rgnt_..." -H "content-type: application/json" \
-d '{"amount": "25", "currency": "USD"}'
```
Optional fields: `idempotency_key`, `entity_id` (per-entity budgets), `reference` +
`reference_amount` (relational cap), `caller_agent_id` (when set, must equal the
mandate's agent — stops one agent spending against another's mandate; the control
gate always sends it).
Success returns the authorization with a short-lived JWT proof:
```json
{
"id": "de35ead1-…",
"mandate_id": "…",
"agent_id": "agent_688b…",
"amount": "25.000000",
"currency": "USD",
"status": "authorized",
"jwt_token": "…",
"jti": "…",
"guardian_score": 0.12,
"authorized_at": "…"
}
```
A refusal is `402` with `detail.code` — the full ladder is in the
[error reference](/rest-api/errors). The two you must handle: `MANDATE_LIMIT_EXCEEDED`
(don't retry the same amount) and `MANDATE_SUSPENDED` (the kill switch — stop).
## Settle
After the real charge lands, reconcile the difference between approved and settled:
```bash
POST /v1/organizations//mandates//settle
{"delta": "-3.50"} // settled − approved; negative = came in lower / refund
```
Returns updated `daily_used` / `monthly_used`.
## Revoke a mandate
```bash
POST /v1/organizations//mandates//revoke
```
Independent of agent revocation: revoking the agent suspends all its mandates;
revoking one mandate leaves the agent (and other mandates) alone.
---
source: https://docs.regentprotocol.org/rest-api/audit.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Audit Events
> Read an agent's journal, ingest your own events, and verify Merkle proofs and anchors.
Every meaningful action gets an audit event: hashed on receipt, Merkle-batched, and
anchored on Solana. The lifecycle is visible on the record itself:
`received → batched → anchored`.
What lands in an agent's journal without you doing anything:
| Event type | Written by |
|---|---|
| `control.decision`, `control.decision.completed`, `control.decision.receipt` | the gate: every decision, its completion and the retained receipt |
| `payment.authorized`, `payment.rejected` | the mandate ledger: each allow or refusal of `POST …/mandates//authorize` |
| `mandate.created`, `mandate.version_created` | a mandate issued, and every change of its terms (commitment only, never the ceilings) |
| `agent.revoked` | the kill switch |
Events you ingest yourself (below) sit next to them under the same agent.
## Read an agent's journal
```bash
GET /v1/organizations//agents//events?limit=50&offset=0
```
Newest first. Each item carries `event_type`, `status`, `payload_hash`, `batch_id` and the
Merkle fields once batched.
## Ingest an event
```bash
curl -X POST https://api.regentprotocol.org/v1/organizations//audit/events \
-H "Authorization: Bearer rgnt_..." -H "content-type: application/json" \
-d '{
"event_id": "action-",
"agent_id": "agent_688b10bc62aef…",
"event_type": "action.completed",
"payload": {"amount": "25", "currency": "USD", "authorization_jti": "…"}
}'
```
- `event_id` — your idempotency key (≤64 chars). Re-sending the same id is safe.
- `event_type` — dotted, e.g. `action.completed`, `order.shipped`.
- `payload` — arbitrary JSON; it is hashed (`payload_hash`) and stored.
Best practice: put the decision id or the authorization `jti` into the payload — that is
what ties the action to its permission when someone reconstructs the case later.
## Read an event
```bash
GET /v1/organizations//events/
```
```json
{
"event_id": "action-…",
"agent_id": "agent_688b…",
"event_type": "action.completed",
"payload_hash": "…",
"status": "anchored",
"batch_id": "…",
"merkle_index": 3,
"merkle_proof": ["…"],
"received_at": "…",
"anchored_at": "…"
}
```
The event must belong to an agent of your organization; otherwise `404`.
## Verify a proof
```bash
POST /v1/organizations//events//verify
GET /v1/organizations//audit/events//proof
GET /v1/organizations//batches/
```
`verify` recomputes the event's Merkle path against its batch root and answers
`verified: true|false`. `proof` returns the same path together with the batch's anchor so a
reader can recompute it elsewhere. The batch record carries the on-chain commitment:
`anchor_tx` (the Solana signature; `anchor_chain: solana`), `solana_explorer_url`, and the
audit-chain position `seq` / `chain_tx` / `chain_head` with Regent's signed `checkpoint`.
The field `arbitrum_tx_hash` holds the same signature under its historical name and stays
for older clients.
For a file a third party can check offline — the event, its proof, the checkpoint, the
epoch and the keys — use the evidence bundle:
```bash
GET /v1/organizations//audit/events//bundle
```
An auditor can go further and check the anchor independently against Solana — the
end-to-end recipe is in [Verifying on-chain](/solana-programs/verification). That page is
the point of the whole design: the trail is checkable **without trusting Regent or the
operator**.
On devnet, batches seal and anchor within a minute of ingestion.
Verified on production: 2026-10-13.
---
source: https://docs.regentprotocol.org/rest-api/errors.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Error Reference
> Every error code the API returns, what it means, and what to do.
Every non-2xx response has one shape:
```json
{"detail": {"code": "MANDATE_LIMIT_EXCEEDED", "category": "PER_TX", "message": "…", "authorization_id": "…"}}
```
Codes are stable strings — match on `detail.code`, never on the message text.
This page is generated against the live service contracts; codes listed here are the
ones the API actually emits.
## Authentication & access
| HTTP | Code | Meaning · next step |
|---|---|---|
| 401 | `NO_TOKEN` | No credentials — send `Authorization: Bearer rgnt_…` (an org API key; a `rgnt_ctrl_…` control key is for `control-api` and the gateway, see [Authentication](/rest-api/authentication)) |
| 401 | `INVALID_TOKEN` / `INVALID_API_KEY` | Expired or wrong credential — issue a new key |
| 403 | `NOT_MEMBER` | Your key belongs to a different organization than the one in the path |
| 403 | `INSUFFICIENT_ROLE` | The action needs a higher org role (e.g. revoke needs `admin`) |
| 403 | `EMAIL_NOT_VERIFIED` | Confirm your email before registering agents |
| 403 | `KYC_NOT_VERIFIED` | Complete identity verification before registering agents or issuing mandates |
| 429 | `RATE_LIMITED` | Honor `Retry-After`. Current per-IP limits: 30 req/s (burst 20); login/signup 5/min |
## Input validation
| HTTP | Code | Meaning · next step |
|---|---|---|
| 422 | `AGENT_ID_MALFORMED` | `agent_id` isn't the canonical `agent_…` identifier. The message names the common mistake: passing the record UUID from the registration response |
| 404 | `AGENT_NOT_FOUND` | No such agent in this organization (also returned for agents of other orgs) |
| 404 | `MANDATE_NOT_FOUND` | No such mandate |
## Authorization refusals (HTTP 402)
Every refusal happens **before execution** — no money has moved. The refusal itself is
recorded and auditable.
| Code | Meaning · next step |
|---|---|
| `MANDATE_LIMIT_EXCEEDED` | The amount breaks a per-transaction, daily, monthly, per-entity, or relational limit — `category` names the window and the message says which. Don't retry the same amount; surface to the operator |
| `MANDATE_SUSPENDED` | The kill switch: the agent was revoked and its mandates suspended. Stop all activity |
| `MANDATE_REVOKED` / `MANDATE_EXPIRED` | This mandate is gone — a new one must be issued and approved |
| `MANDATE_NOT_OWNED` | `caller_agent_id` doesn't match the mandate's agent — one agent cannot spend against another's mandate |
| `AGENT_NOT_ACTIVE` | The agent's identity record is not active (revoked or pending) |
| `RISK_SCORE_TOO_HIGH` | Guardian's behavioural score crossed the hard-reject threshold |
| `IDENTITY_UNREACHABLE` | The identity service could not confirm the agent — fail-closed refusal, safe to retry once the service recovers |
| `JWT_ISSUE_FAILED` | Authorization passed but the proof token could not be minted — fail-closed refusal, retry |
Until 2026-10-03 the payment service emitted the bare code `LIMIT_EXCEEDED` on this endpoint while the
control gate already used `MANDATE_LIMIT_EXCEEDED`; both surfaces now emit `MANDATE_LIMIT_EXCEEDED`. Match on the new name.
---
source: https://docs.regentprotocol.org/solana-programs/overview.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Overview
> The three Anchor programs that anchor Regent Protocol on Solana.
Regent's on-chain footprint is three Anchor programs deployed to Solana devnet. Each program is **non-upgradeable** and authored to be minimal — they store hashes and status, not application logic.
## The three programs
| Program | Devnet ID | Purpose |
|---|---|---|
| [`agent-registry`](/solana-programs/agent-registry) | `5jBmqyeo1vUAjHbEFuY59NMGTQR8cEe9Jvz2uCwCjp3L` | Agent identity registration, revocation, status transitions |
| [`mandate-registry`](/solana-programs/mandate-registry) | `8HAzw3UFGmabsHJkAsuGLfBZG8djYQ3J1FRNUVjkseMr` | Mandate registration and revocation |
| [`audit-anchor`](/solana-programs/audit-anchor) | `8N1PpbJZKmvJjG86XWpP82XrWzp8HY5FHZuzyQTgjJas` | Merkle batch root storage for audit events |
All three programs accept signed instructions from the `blockchain-worker` service, which holds the protocol authority keypair. End users never call these programs directly — the protocol mediates all writes.
## Why on-chain at all
Regent's PostgreSQL stores the authoritative state. The on-chain programs serve as a **second, independent source of truth** that:
- Cannot be silently modified, even by Regent itself
- Is publicly observable without trust assumptions
- Outlives any single service or deployment
This is the foundation for credibility with regulators and counterparties. An auditor can verify what Regent claims by reading Solana, without trusting Regent's database.
## Why Solana
| Property | Why it matters for Regent |
|---|---|
| **Sub-second finality** | Agent state changes (registration, revocation) need to take effect immediately, not minutes later |
| **Low fees** | Frequent anchoring (every audit batch) would be prohibitive on slower L1s |
| **Native programmability** | Anchor framework lets us write tight, audited Rust programs rather than generic EVM contracts |
| **Standard tooling** | `@solana/web3.js`, Phantom, Solana Explorer — judges and partners can verify without bespoke tools |
## Account model
Each program writes structured accounts (PDAs) keyed by the relevant identifier:
```
AgentRegistry:
PDA = derive(["agent", agent_id_hash])
Account = { agent_id_hash, did_hash, responsible_party_hash, status, registered_at, revoked_at }
MandateRegistry:
PDA = derive(["mandate", mandate_id])
Account = { mandate_id, agent_id_hash, per_tx, daily, monthly, currency, status }
AuditAnchor:
PDA = derive(["batch", batch_id])
Account = { batch_id, merkle_root, event_count, sealed_at }
```
Off-chain components (the SDK, the dashboard) can re-derive any PDA from public data and read the account state directly via standard RPC.
## Verifying on-chain
Every API response that has an on-chain component carries a `solana_tx` field — the transaction signature. Drop it into [explorer.solana.com](https://explorer.solana.com/?cluster=devnet) and you'll see:
- The instruction that was executed (`register_agent`, `revoke_agent`, `register_mandate`, `revoke_mandate`, `anchor_batch`)
- The PDA account written
- The protocol authority signature
- The block time and slot
For programmatic verification, see [Verifying on-chain](/solana-programs/verification).
## Source
The Anchor programs are published in the public repository [`regent-protocol/regent-solana-programs`](https://github.com/regent-protocol/regent-solana-programs) (Apache-2.0); the same sources build the devnet deployments listed above.
```
regent-solana-programs/
├── Anchor.toml
└── programs/
├── agent-registry/src/lib.rs
├── mandate-registry/src/lib.rs
└── audit-anchor/src/lib.rs
```
Build and test locally:
```bash
cd packages/contracts-solana
anchor build
anchor test
anchor deploy --provider.cluster devnet
```
---
source: https://docs.regentprotocol.org/solana-programs/agent-registry.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# AgentRegistry
> On-chain agent identity registration and revocation.
`AgentRegistry` is the on-chain source of truth for **whether an agent exists and whether it is active**. Every off-chain claim about an agent can be checked against this program.
| | |
|---|---|
| **Program ID (devnet)** | `5jBmqyeo1vUAjHbEFuY59NMGTQR8cEe9Jvz2uCwCjp3L` |
| **Framework** | Anchor 0.31.1 |
| **Upgradeable?** | On devnet, yes — the upgrade authority `47BBdKL1SwY7auTN4mQpnQAFX14bCjs7qie9R34FrXxc` is held by Regent so fixes can ship during the pilot phase. ADR-010 commits the core programs to be non-upgradeable on mainnet |
| **Source** | [`regent-protocol/regent-solana-programs` → `programs/agent-registry`](https://github.com/regent-protocol/regent-solana-programs/tree/main/programs/agent-registry) |
## Account layout
Each agent gets a Program Derived Address (PDA) keyed by a 32-byte agent reference:
```rust
#[account]
pub struct Agent {
pub agent_id: [u8; 32], // sha256(agent_id) for `agent_…` ids; legacy UUID ids are the raw 16 bytes, left-padded
pub did_hash: [u8; 32], // sha256("did:regent:solana:{agent_id}")
pub responsible_party: Pubkey, // sha256(owner account id) = the owner's UserAccount PDA seed
pub registered_at: i64, // unix seconds
pub revoked: bool,
pub revoked_at: Option,
pub bump: u8,
}
// 8-byte discriminator + 123 bytes
```
The PDA seed is `[b"agent", agent_id]`, so anyone who knows the agent id can re-derive the address and read the account. There is no status enum: an agent is active while `revoked == false`.
> Only hashes are stored — never the agent id, the DID or the owner's identity. `responsible_party` is a 32-byte commitment to the owner, `sha256()`, and that value is exactly the `[b"user", user_id_hash]` seed of the owner's `UserAccount` below, so an agent account resolves on-chain to the record holding the owner's KYC attestation hash, country and DID hash. A value that already is a Solana public key (an owner wallet) is written as is.
> Agents registered before 2026-10-04 carry the **protocol authority's key** in `responsible_party`: owner ids are not Solana keys, and the worker fell back to the authority instead of committing to the owner. Those accounts are immutable; the binding for them is in the signed identity record and the anchored audit trail.
The owner's record is a `UserAccount` (`seeds = [b"user", user_id_hash]`): `user_id_hash`, `did_hash`, `kyc_attestation_hash`, `kyc_country`, `kyc_verified`, `registered_at`. It is written by `register_user` when the owner completes identity verification (the platform emits `user.did_created`, the worker anchors it). The signed identity record returned by the API additionally carries a pairwise commitment `sha256(agent_id:owner_id)`.
## Instructions
### `register_agent`
Writes a new `Agent` account with `revoked = false`.
| Account | Type | Notes |
|---|---|---|
| `config` | PDA `[b"config"]` | Holds the authority and the agent counter |
| `agent` | PDA `[b"agent", agent_id]` | Initialized; rent paid by the authority |
| `authority` | Signer | Must match `config.authority` |
| `system_program` | Program | — |
| Arg | Type | Notes |
|---|---|---|
| `agent_id` | `[u8; 32]` | SHA-256 of the `agent_…` id string |
| `did_hash` | `[u8; 32]` | SHA-256 of `did:regent:solana:{agent_id}` |
| `responsible_party` | `Pubkey` | `sha256(owner account id)`, the owner's `UserAccount` seed |
Reverts if the PDA already exists.
### `revoke_agent`
Sets `revoked = true` and stamps `revoked_at`.
| Account | Type | Notes |
|---|---|---|
| `agent` | Mutable PDA | Must exist; must not be revoked |
| `authority` | Signer | Must be the protocol authority |
Reverts if the agent doesn't exist or is already revoked. The protocol's `blockchain-worker` calls this when a responsible party (or automated workflow) revokes an agent off-chain.
### `transfer_authority`
Moves `config.authority` to a new key. Used once on 2026-09-26 to rotate the authority of all three programs.
## Trust model
Only the protocol authority can call `register_agent` or `revoke_agent`. This is intentional:
- Registration requires KYC verification, which the protocol enforces off-chain
- Revocation must follow the responsible-party authorization model
- A public, permissionless instruction would let anyone register or revoke arbitrary agents
The authority keypair is held in AWS Secrets Manager and loaded only by the `blockchain-worker` service. It was rotated on 2026-09-26 with `transfer_authority`.
## Verifying an agent off-chain
```typescript
import { Connection, PublicKey } from "@solana/web3.js";
import { Program, AnchorProvider } from "@coral-xyz/anchor";
import { sha256 } from "js-sha256";
const connection = new Connection("https://api.devnet.solana.com");
const programId = new PublicKey("5jBmqyeo1vUAjHbEFuY59NMGTQR8cEe9Jvz2uCwCjp3L");
// Re-derive the agent's PDA
const agentIdHash = Buffer.from(sha256("agent_b1c59d23..."), "hex");
const [agentPda] = PublicKey.findProgramAddressSync(
[Buffer.from("agent"), agentIdHash],
programId,
);
// Read it
const account = await program.account.agent.fetch(agentPda);
console.log("Revoked:", account.revoked); // false = active
console.log("Registered:", account.registeredAt);
console.log("Revoked at:", account.revokedAt);
// Resolve the owner: responsible_party is the seed of the owner's UserAccount
const [userPda] = PublicKey.findProgramAddressSync(
[Buffer.from("user"), Buffer.from(account.responsibleParty.toBytes())],
programId,
);
const owner = await program.account.userAccount.fetch(userPda);
console.log("Owner KYC verified:", owner.kycVerified, "country:", Buffer.from(owner.kycCountry).toString());
```
If the account is missing → the agent was never anchored. If `revoked` is `true` → don't trust any authorizations from this agent regardless of what the off-chain API says.
## Related
- [MandateRegistry](/solana-programs/mandate-registry) — Mandates carry their owning agent's hash and are suspended when their agent is revoked.
- [AgentID concept](/concepts/agent-id) — The off-chain identity model that this program anchors.
---
source: https://docs.regentprotocol.org/solana-programs/mandate-registry.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# MandateRegistry
> On-chain mandate registration and revocation — presence and status, never the ceilings.
`MandateRegistry` anchors the **existence, agent binding and revocation** of every spending mandate. It deliberately does **not** store the limits: an owner's spending caps are confidential, and the protocol proves them another way (below).
| | |
|---|---|
| **Program ID (devnet)** | `8HAzw3UFGmabsHJkAsuGLfBZG8djYQ3J1FRNUVjkseMr` |
| **Framework** | Anchor 0.31.1 |
| **Upgradeable?** | On devnet, yes — the upgrade authority `47BBdKL1SwY7auTN4mQpnQAFX14bCjs7qie9R34FrXxc` is held by Regent so fixes can ship during the pilot phase. ADR-010 commits the core programs to be non-upgradeable on mainnet |
| **Source** | [`regent-protocol/regent-solana-programs` → `programs/mandate-registry`](https://github.com/regent-protocol/regent-solana-programs/tree/main/programs/mandate-registry) |
## Account layout
```rust
#[account]
pub struct Mandate {
pub mandate_id: [u8; 32], // the mandate UUID's 16 bytes, left-padded with zeros
pub agent_id: [u8; 32], // the agent's PDA seed in AgentRegistry: sha256(agent id)
pub registered_at: i64, // unix seconds
pub revoked: bool,
pub revoked_at: Option,
pub bump: u8,
}
// 8-byte discriminator + 83 bytes
```
PDA seeds: `[b"mandate", mandate_id]`. `agent_id` is the same 32 bytes `AgentRegistry` uses as the agent's PDA seed, so a mandate resolves to its agent account; mandates registered before 2026-10-04 carry a differently derived reference (the id was zero-padded before hashing) and do not resolve.
> **No `per_tx_limit`, `daily_limit`, `monthly_limit`, `currency` or `status` fields.** Earlier versions of this page described such fields; the deployed program never had them. The only on-chain transition is `revoked: false → true`; `suspended` (agent revoked) is an off-chain state.
## Why the limits are not on-chain
A public ledger that carried every owner's ceilings would publish their spending policy to the world. The program therefore anchors **presence** (this mandate id exists, bound to this agent, since this time) and **revocation**, and nothing else.
The limits are proven through the decision evidence instead:
- Every change to the terms creates an **immutable version** with a salted SHA-256 commitment (`mandate_version`, `mandate_hash`).
- Every allow token, every completion receipt and every audit event carries that version and hash. The audit events are Merkle-batched and anchored by [`AuditAnchor`](/solana-programs/audit-anchor).
- The owner reveals a version — snapshot plus salt — with `GET /v1/organizations/{org}/mandates/{id}/versions/{version}`; the verifier recomputes the hash and matches it to the receipt.
An auditor reviewing a disputed `MANDATE_LIMIT_EXCEEDED` refusal therefore reads the limit from the revealed version named in the receipt, not from the chain — and the chain guarantees the audit event that names that version was not rewritten.
On the optional EVM anchor (Arbitrum Nova), the registry stores the terms **commitment** (the hash) alongside the ids — still never the amounts.
## Instructions
### `register_mandate`
Writes a new `Mandate` account with `revoked = false`.
| Account | Type | Notes |
|---|---|---|
| `config` | PDA `[b"config"]` | Holds the authority and the mandate counter |
| `mandate` | PDA `[b"mandate", mandate_id]` | Initialized; rent paid by the authority |
| `authority` | Signer | Must match `config.authority` |
| `system_program` | Program | — |
| Arg | Type |
|---|---|
| `mandate_id` | `[u8; 32]` |
| `agent_id` | `[u8; 32]` |
Off-chain, a mandate created through the API is `active` immediately; `blockchain-worker` then registers it here asynchronously and records the transaction signature on the mandate. Authorizations do not wait for the transaction.
### `revoke_mandate`
Sets `revoked = true` and stamps `revoked_at`. Reverts if already revoked. Called by `blockchain-worker` after `POST /v1/organizations/{org}/mandates/{id}/revoke`.
### `transfer_authority`
Moves `config.authority` to a new key (used for the 2026-09-26 rotation).
## Verifying a mandate off-chain
```typescript
const uuidToBytes32 = (u: string) =>
Buffer.concat([Buffer.alloc(16), Buffer.from(u.replace(/-/g, ""), "hex")]);
const [mandatePda] = PublicKey.findProgramAddressSync(
[Buffer.from("mandate"), uuidToBytes32(mandateId)],
programId,
);
const account = await program.account.mandate.fetch(mandatePda);
console.log("Registered:", new Date(account.registeredAt * 1000));
console.log("Revoked:", account.revoked, account.revokedAt);
```
If the account is missing, the mandate was never registered on-chain. If `revoked` is `true`, no authorization issued after `revoked_at` should be trusted, whatever the off-chain API says. For the limits, verify the receipt — see [Verifying on-chain](/solana-programs/verification#verify-the-terms-a-decision-was-made-against).
## Related
- [AgentRegistry](/solana-programs/agent-registry) — Mandates carry a reference to their owning agent and are functionally suspended when that agent is revoked.
- [Mandate concept](/concepts/mandate) — The off-chain enforcement layer: limits, versions, counters, rejection codes.
---
source: https://docs.regentprotocol.org/solana-programs/audit-anchor.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# AuditAnchor
> Merkle batch root storage for audit events.
`AuditAnchor` is the keystone of Regent's tamper-evidence claim: every audit event ever
ingested can be proven — or disproven — against an account in this program.
It holds two things. A **chain**: one account with a running head over every batch root in
order, so the audit trail is provably complete and unaltered, not merely individually
hashed. And **epochs**: periodic Merkle roots over ranges of that chain, which keep proofs
short and give each range a Bitcoin timestamp. The original per-batch account
(`anchor_batch`) still runs on devnet so that every account written since April 2026 and
every published recipe keeps working.
| | |
|---|---|
| **Program ID (devnet)** | `8N1PpbJZKmvJjG86XWpP82XrWzp8HY5FHZuzyQTgjJas` |
| **Framework** | Anchor 0.31.1 |
| **Upgradeable?** | On devnet, yes — the upgrade authority `47BBdKL1SwY7auTN4mQpnQAFX14bCjs7qie9R34FrXxc` is held by Regent so fixes can ship during the pilot phase. ADR-010 commits the core programs to be non-upgradeable on mainnet |
| **Source** | [`regent-protocol/regent-solana-programs` → `programs/audit-anchor`](https://github.com/regent-protocol/regent-solana-programs/tree/main/programs/audit-anchor) |
## Why a separate program
Agents and mandates are bounded — there are some number of agents, some number of mandates, and each has a lifecycle. Audit events are unbounded: every action of every agent produces one. Batching them into Merkle roots is the only way to keep on-chain costs constant per event regardless of throughput.
## Account layout
```rust
#[account]
pub struct Batch {
pub batch_id: [u8; 32], // the batch UUID's 16 bytes, left-padded with zeros
pub merkle_root: [u8; 32],
pub event_count: u32,
pub anchored_at: i64, // unix seconds, set by the program from the cluster clock
pub bump: u8,
}
```
PDA seeds: `[b"batch", batch_id]`.
The account is tiny — 85 bytes including the 8-byte discriminator — so anchoring is cheap even at scale.
## Instructions
### `anchor_batch`
Writes a new `Batch` account. Once written, an account is immutable — there's no `update_batch` instruction by design. The program rejects an empty batch (`event_count == 0`) and an all-zero root.
| Arg | Type |
|---|---|
| `batch_id` | `[u8; 32]` |
| `merkle_root` | `[u8; 32]` |
| `event_count` | `u32` |
Off-chain, `api-audit` accumulates events until either:
- the batch reaches its size threshold (100 events), or
- the auto-seal timer fires (seconds, configured per deployment)
…then computes the Merkle root over the events' payload hashes (SHA-256, `sha256(left ‖ right)`, an odd node paired with itself) and hands the sealed batch to `blockchain-worker`, which calls `anchor_batch`. The transaction signature is recorded on every event in the batch as `solana_tx`.
### `append_batch`
Appends one batch root to the **audit chain** — the single account that makes the series of
batches provable, not just each batch individually.
| Arg | Type | Notes |
|---|---|---|
| `batch_root` | `[u8; 32]` | the batch's Merkle root |
| `seq` | `u64` | must equal `chain.seq + 1` |
| `prev_head` | `[u8; 32]` | must equal `chain.head` — the caller states which history it extends |
```rust
#[account]
pub struct AuditChain { // seeds = [b"chain"], created once
pub head: [u8; 32], // sha256(head_prev ‖ batch_root ‖ seq_le8); zeros before the first
pub seq: u64, // latest appended batch; the program requires seq == chain.seq + 1
pub last_appended_at: i64,
pub epoch_count: u64,
pub last_epoch_end_seq: u64,
pub bump: u8,
}
```
The two guards are the point: a gap in `seq` is refused on chain, so a batch cannot be
skipped, and `prev_head` means a client whose own record of the chain has drifted is refused
rather than quietly extending a different history. Any change to an earlier batch changes
every head after it. Cost is one transaction fee
and no rent, which is why this replaces the per-batch account on mainnet — at one batch
every 30 seconds the accounts cost roughly $379 a day in permanent rent, the chain about
$1.90.
### `anchor_epoch`
Records a Merkle root over the batch roots of a contiguous sequence range, so a single event
is proven with two short Merkle paths instead of a replay of the chain.
| Arg | Type |
|---|---|
| `epoch_index` | `u64` (1-based; PDA seed, little-endian) |
| `first_seq` | `u64` |
| `last_seq` | `u64` |
| `merkle_root` | `[u8; 32]` |
```rust
#[account]
pub struct Epoch { // seeds = [b"epoch", epoch_index_le8]
pub epoch_index: u64,
pub first_seq: u64, // must be last_epoch_end_seq + 1
pub last_seq: u64, // must be <= chain.seq
pub merkle_root: [u8; 32],
pub anchored_at: i64,
pub bump: u8,
}
```
Epochs are sequential and contiguous by construction, so they tile the chain with no gaps
and no overlaps. Each epoch root is also stamped into Bitcoin through OpenTimestamps; the
proof is served at `/v1/audit/epochs/{n}/ots`.
### `init_chain`
Creates the `AuditChain` account. Run once per deployment by the operator.
### `transfer_authority`, `init_admin`, `transfer_admin`
`config.authority` is the **operator** — the hot key the anchoring service signs with. A
separate `AdminConfig` PDA (seeds `[b"admin"]`) holds the **admin**, a multisig on mainnet,
and only the admin can rotate the operator. `init_admin` is the one-time bootstrap;
`transfer_admin` hands the admin role on. A compromised operator key can therefore write
records — which the trail exposes — but cannot change who is in charge.
## How proofs work
Once a batch is sealed, every event in it has a **Merkle proof** — a list of sibling hashes that lets anyone verify the event without downloading the rest of the batch.
```mermaid
flowchart TB
R[Merkle root
on Solana]
R --> A[hash A]
R --> B[hash B]
A --> C[hash C]
A --> D[hash D]
B --> E[hash E]
B --> F[hash F]
D --> G[event 0]
D --> H[event 1]
```
If you want to prove that "event 1" was in this batch:
1. Hash event 1's canonical payload → leaf hash `H`
2. Combine with sibling `G` (provided in the proof) → get parent `D`
3. Combine `D` with sibling `C` (provided) → get parent `A`
4. Combine `A` with sibling `B` (provided) → root `R`
5. Compare `R` against the on-chain `merkle_root`
Match → event is provably in the batch. No match → either the event was forged, or the batch has been tampered with off-chain.
## Why this matters
Without on-chain anchoring, an audit log can be rewritten silently. With anchoring:
- Adding a fake event after the batch is sealed produces a different root → mismatch
- Deleting a real event produces a different root → mismatch
- Modifying any event's payload produces a different root → mismatch
The only way to forge convincingly would be to issue a new on-chain transaction with the modified root — which leaves a fingerprint on Solana that anyone can see (and that would conflict with the original transaction).
## Reading a batch off-chain
```typescript
// batch_id on-chain = the UUID's 16 bytes, left-padded to 32
const batchIdBytes = Buffer.concat([Buffer.alloc(16), Buffer.from(batchId.replace(/-/g, ""), "hex")]);
const [batchPda] = PublicKey.findProgramAddressSync(
[Buffer.from("batch"), batchIdBytes],
programId,
);
const batch = await program.account.batch.fetch(batchPda);
console.log("Merkle root:", Buffer.from(batch.merkleRoot).toString("hex"));
console.log("Event count:", batch.eventCount);
console.log("Anchored at:", new Date(batch.anchoredAt * 1000));
```
To verify an individual event, see [Verifying on-chain](/solana-programs/verification).
## Related
- [Audit Trail concept](/concepts/audit-trail) — The off-chain ingestion + Merkle batching pipeline that feeds this program.
- [Verifying on-chain](/solana-programs/verification) — End-to-end recipe for verifying an event against the on-chain root.
---
source: https://docs.regentprotocol.org/solana-programs/verification.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Verifying on-chain
> End-to-end recipes for independently verifying Regent agents, mandates, and audit events against Solana.
This page assumes you don't trust Regent's database. Everything below uses standard Solana tooling and public on-chain data only.
## Verify an agent
You need:
- An `agent_id` (from Regent's API or the dashboard)
- The `AgentRegistry` program ID: `5jBmqyeo1vUAjHbEFuY59NMGTQR8cEe9Jvz2uCwCjp3L`
```typescript
import { Connection, PublicKey } from "@solana/web3.js";
import { sha256 } from "js-sha256";
const conn = new Connection("https://api.devnet.solana.com");
const programId = new PublicKey("5jBmqyeo1vUAjHbEFuY59NMGTQR8cEe9Jvz2uCwCjp3L");
const agentId = "agent_b1c59d23...";
const agentIdHash = Buffer.from(sha256(agentId), "hex");
const [pda] = PublicKey.findProgramAddressSync(
[Buffer.from("agent"), agentIdHash],
programId,
);
const info = await conn.getAccountInfo(pda);
if (!info) {
console.log("Agent not anchored on-chain — do not trust");
process.exit(1);
}
// Parse with the Anchor IDL (program.account.agent.fetch) or borsh-deserialize manually.
// Fields: agent_id, did_hash, responsible_party, registered_at, revoked, revoked_at
// responsible_party = sha256(owner account id) = seed of the owner's UserAccount:
// [pda] = findProgramAddressSync([Buffer.from("user"), responsible_party], programId)
// → kyc_verified, kyc_country, kyc_attestation_hash, did_hash (agents registered before 2026-10-04 hold the authority key here)
```
If the account exists and `revoked == false`, the agent is verifiably present and not revoked. If the account is missing, the agent claim is forged.
## Verify a mandate
The chain answers two questions about a mandate: **does it exist**, and **has it been revoked**. UUID-shaped ids are stored as their 16 raw bytes left-padded to 32.
```typescript
const uuidToBytes32 = (u: string) =>
Buffer.concat([Buffer.alloc(16), Buffer.from(u.replace(/-/g, ""), "hex")]);
const programId = new PublicKey("8HAzw3UFGmabsHJkAsuGLfBZG8djYQ3J1FRNUVjkseMr");
const [pda] = PublicKey.findProgramAddressSync(
[Buffer.from("mandate"), uuidToBytes32("cab07ae3-e0e3-48d7-9e41-dc10512c4329")],
programId,
);
const account = await program.account.mandate.fetch(pda);
console.log("Registered:", new Date(account.registeredAt * 1000));
console.log("Revoked:", account.revoked, account.revokedAt);
```
## Verify the terms a decision was made against
The ceilings are deliberately **not** on the chain. What proves them is the decision's own evidence:
1. The allow token and the completion receipt name `mandate_id`, `mandate_version` and `mandate_hash` — a salted SHA-256 commitment of the canonical terms in force at that moment. Check the receipt's signature offline with `npx -p @regent-protocol/receipt-verify regent-verify --jwks jwks.json` (or `pip install regent-receipt-verify`); the keys come from `https://control-api.regentprotocol.org/v1/control/.well-known/jwks.json`.
2. The audit event for the decision carries the same version and hash, and its batch is anchored by `AuditAnchor` — the recipe below proves the event is in the batch.
3. The owner reveals that version to you with `GET /v1/organizations/{org}/mandates/{id}/versions/{version}` — the snapshot of the terms plus the salt. Recompute the hash and match it to the receipt. The gate's [receipt verify page](https://control-api.regentprotocol.org/v1/control/receipts/verify) does the recomputation in the browser.
A version is immutable; widening a limit creates a new version with a new hash, so a receipt can never be re-pointed at looser terms.
## Verify an audit event
This is the most involved recipe and the most valuable: it proves that a specific event was
in a specific batch, that the batch is in the chain in a specific position, and that the
whole thing was fixed in time — without trusting Regent.
Everything you need comes from one endpoint:
```bash
curl -s https://api.regentprotocol.org/v1/audit/events//proof
```
```json
{
"event_id": "ctrl-dec_8c810131572d",
"payload_hash": "0x15d5690b…",
"merkle_index": 0,
"merkle_proof": ["0xabc…", "…"],
"batch_id": "78422d51-…",
"batch_root": "11999505bde71ec4…",
"chain": { "seq": 170, "chain_tx": "5N3oA…", "chain_head": "0xef40d6…" },
"epoch": { "epoch_index": 1, "first_seq": 1, "last_seq": 169,
"epoch_leaf_index": 169, "epoch_proof": ["…"],
"epoch_root": "7c1f…", "epoch_tx": "2ScWP…", "ots_status": "complete" },
"programs": { "audit_anchor": "8N1PpbJZKmvJjG86XWpP82XrWzp8HY5FHZuzyQTgjJas" },
"cluster": "devnet"
}
```
### The one-command version
```bash
python3 ops/audit/verify_event.py
```
It uses only the Python standard library, talks only to the public API and a public Solana
RPC, and derives the program-derived addresses itself rather than taking them from the
response, so a wrong address cannot be slipped past it. Every line must read `PASS`.
### The hashing rules
Three rules, and they are the easy things to get wrong:
1. A Merkle parent is `sha256(left_string ‖ right_string)` over the hex **strings**,
rendered as bare lowercase hex. An odd node at any level is paired with itself.
2. Event payload hashes are stored with a `0x` prefix, so a batch of exactly **one** event
has a prefixed root while a batch of two or more has a bare one. Compare batch roots
ignoring an optional `0x`.
3. An **epoch leaf** is a batch root as bare 64-hex with any `0x` stripped, in `seq` order
(`leaf i` = `first_seq + i`). The epoch root is bare, and that is also its form on chain
and as the OpenTimestamps digest.
### Step by step
```typescript
import { createHash } from "crypto";
const bare = (h: string) => (h.startsWith("0x") ? h.slice(2) : h).toLowerCase();
const sha = (s: string) => createHash("sha256").update(s).digest("hex");
function fold(leaf: string, proof: string[], index: number): string {
let cur = leaf, idx = index;
for (const sib of proof) { cur = idx % 2 === 0 ? sha(cur + sib) : sha(sib + cur); idx >>= 1; }
return cur;
}
// 1. the event is in its batch
const batchRoot = fold(b.payload_hash, b.merkle_proof, b.merkle_index);
console.assert(bare(batchRoot) === bare(b.batch_root));
// 2. the batch is in its epoch
const epochRoot = fold(bare(b.batch_root), b.epoch.epoch_proof, b.epoch.epoch_leaf_index);
console.assert(bare(epochRoot) === bare(b.epoch.epoch_root));
```
```typescript
// 3. the epoch account on Solana carries that root, over a range containing this batch
const programId = new PublicKey(b.programs.audit_anchor);
const idx = Buffer.alloc(8); idx.writeBigUInt64LE(BigInt(b.epoch.epoch_index));
const [epochPda] = PublicKey.findProgramAddressSync([Buffer.from("epoch"), idx], programId);
const epoch = await program.account.epoch.fetch(epochPda);
console.assert(Buffer.from(epoch.merkleRoot).toString("hex") === bare(b.epoch.epoch_root));
console.assert(epoch.firstSeq <= b.chain.seq && b.chain.seq <= epoch.lastSeq);
// 4. the chain has reached this batch, so it is inside committed, ordered history
const [chainPda] = PublicKey.findProgramAddressSync([Buffer.from("chain")], programId);
const chain = await program.account.auditChain.fetch(chainPda);
console.assert(chain.seq >= b.chain.seq);
```
If 1–4 hold, the event was in that batch, the batch is at that position in a sequence the
program will not let anyone renumber, and the range is committed to a single on-chain root.
**No trust in Regent is required at any step.**
### The Bitcoin timestamp
Each epoch root is also submitted to the OpenTimestamps calendars, so an independent clock
dates the evidence without Solana and without us:
```bash
curl -s -o epoch-1.ots https://api.regentprotocol.org/v1/audit/epochs/1/ots
ots verify -d epoch-1.ots
```
`ots_status` on the epoch tells you where it is: `pending` until Bitcoin confirms (usually
within a day), then `complete`.
## Receipts verify offline today
The decision evidence has a packaged verifier — no Regent call, no chain call:
```bash
npx -p @regent-protocol/receipt-verify regent-verify --jwks jwks.json
# or: pip install regent-receipt-verify && regent-receipt-verify --jwks jwks.json
```
It checks the RS256 signature against the published keys, the expiry and the claim set, and prints the mandate and policy references. The on-chain recipes on this page remain manual snippets.
## What attacks this prevents
| Attack | Prevented? | How |
|---|---|---|
| Forging a non-existent agent | Yes | No on-chain account → verification fails |
| Re-activating a revoked agent in the DB | Yes | On-chain `revoked` is still `true` |
| Widening a mandate's limits retroactively | Yes | Every allow token, receipt and audit event names the mandate version and a salted hash of its terms; versions are immutable and the audit events are Merkle-anchored. The on-chain mandate account holds no limits by design |
| Inserting a fake past event into the audit log | Yes | Merkle root would change → mismatch with on-chain |
| Deleting an inconvenient event | Yes | Same — leaf removal changes the root |
| Modifying an event's payload | Yes | Hash changes → mismatch |
## What this does NOT prevent
- A forged audit event that was **never** ingested into the protocol won't have an on-chain proof — but it also won't carry the `payload_hash` and `merkle_proof` fields, so it's trivially identifiable as not-from-Regent
- Real-time censorship (the protocol could choose not to ingest an event in the first place — but the *responsible party* sees all events from their agent via the dashboard, so this is detectable)
## Related
---
source: https://docs.regentprotocol.org/use-cases/ai-trading-agent.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# AI Trading Agent
> A real AI agent trading BTC on Binance under Regent Protocol governance — identity, mandate, audit, kill switch.
This walkthrough follows [`examples/binance_trading_agent.py`](https://github.com/abay94/regent-sdk-python/blob/main/examples/binance_trading_agent.py) in the Python SDK repo — an 850-line example of a real autonomous agent governed by Regent end-to-end.
> This is what you'll see in the [demo video](https://youtu.be/-jfbEKsIhwg). Every action visible there is reproduced by code below.
## What the agent does
1. **Verifies its identity** on startup — calls `r.identity.get_agent()`, confirms KMS-signed payload + Solana anchor
2. **Loads its mandate** — `$50` per-tx, `$300` daily, `$5,000` monthly
3. **Connects to Binance testnet** and reads BTC/USDT price + balances
4. **Trades on price signal** — for every trade:
- **Authorize** via `r.payment.authorize(...)` (mandate decision)
- On approval, **fill** the order on Binance
- **Log** the action to the audit trail with the authorization JWT (`jti`)
- On rejection (over limit, agent revoked, etc.) log a `trade.rejected` event with the reason
5. **Logs everything** to Regent's audit trail — every approve, reject, fill, error
## Architecture
```mermaid
flowchart LR
BOT[Trading bot]
BINANCE[Binance testnet]
R_PAY[Regent payment]
R_AUDIT[Regent audit]
R_GUARD[Regent guardian]
BOT -->|"get price"| BINANCE
BOT -->|"authorize ?$50"| R_PAY
R_PAY -->|"approve jti"| BOT
BOT -->|"place order"| BINANCE
BOT -->|"trade.executed"| R_AUDIT
R_AUDIT -->|"agent score"| R_GUARD
R_GUARD -->|"alert"| BOT
```
## Setup
```bash
pip install rich httpx "git+https://github.com/abay94/regent-sdk-python.git"
```
Env vars:
```bash
export REGENT_API_KEY="rgnt_..."
export REGENT_ORG_ID="..."
export REGENT_AGENT_ID="agent_..." # registered ahead of time on the dashboard
export REGENT_MANDATE_ID="..." # 50/300/5000 USD
export BINANCE_API_KEY="..." # https://testnet.binance.vision
export BINANCE_API_SECRET="..."
```
## The authorize → fill → audit cycle
The heart of the agent is `_execute_trade`:
```python
async def _execute_trade(self, side: str, amount: float) -> None:
# 1. Ask the mandate
auth = await self.regent.payment.authorize(
self.mandate_id,
AuthorizeRequest(amount=Decimal(str(amount)), currency="USD"),
)
# 2. Fill the order on Binance
order = await self.binance.place_order(SYMBOL, side, amount)
order_id = order["orderId"]
filled_qty = float(order["executedQty"])
filled_quote = float(order["cummulativeQuoteQty"])
# 3. Log to Regent audit
await self.regent.audit.ingest_event(IngestEventRequest(
event_id=f"trade-{order_id}-{uuid.uuid4().hex[:8]}",
agent_id=self.agent_id,
event_type="trade.executed",
payload={
"exchange": "binance-testnet",
"symbol": SYMBOL,
"side": side,
"price": str(self._last_price),
"amount_usd": str(filled_quote),
"btc_qty": str(filled_qty),
"order_id": str(order_id),
"authorization_jti": auth.jti,
},
))
```
Every successful trade carries the authorization `jti`, the Binance order ID, the executed quantity, and the realized price. This is the **complete proof bundle** an auditor would need.
## What happens when the mandate denies
Replace `auth = await ...authorize(...)` with a try/except:
```python
try:
auth = await self.regent.payment.authorize(
self.mandate_id,
AuthorizeRequest(amount=Decimal("200"), currency="USD"), # over per-tx $50
)
except RegentAPIError as e:
# e.code will be "MANDATE_LIMIT_EXCEEDED"
await self.regent.audit.ingest_event(IngestEventRequest(
event_id=f"trade-rejected-{uuid.uuid4().hex[:12]}",
agent_id=self.agent_id,
event_type="trade.rejected",
payload={"side": "BUY", "amount": "200", "reason": e.code, "price": price},
))
return # don't touch Binance — the order never happens
```
The rejection itself is an audit event — the protocol records refusals as carefully as it records approvals. This is a regulatory feature: an auditor can see what the agent *tried* to do, not just what it succeeded at.
## The kill switch
While the bot is running, an operator can revoke the agent from the dashboard. Within ~1 second:
1. `api-identity` flips the agent's status to `revoked` and publishes `agent.revoked` to RabbitMQ
2. `api-payment` consumer receives the event, invalidates the identity-status cache, and suspends all active mandates for this agent
3. The next time the bot calls `/authorize`, it gets `MANDATE_SUSPENDED` (or `AGENT_NOT_ACTIVE`) instantly
4. The bot logs the rejection as a `trade.rejected` event and stops trading
There is no "wait for the next polling interval" or "wait for the cache to expire." The kill switch is immediate.
```python
# In the bot's main loop
while running:
side, amount = await self.decide_trade() # your strategy
try:
await self._execute_trade(side, amount)
except RegentAPIError as e:
if e.code in ("AGENT_NOT_ACTIVE", "MANDATE_SUSPENDED"):
print(f"Halted: {e.code}")
break
```
## Real numbers from a live demo
From a recent demo run (full output in the SDK examples):
```
Trades executed: 4
[1] BUY $49.20 @ $80,661.63
[2] SELL $49.20 @ $80,660.67
[3] BUY $49.20 @ $80,660.67
[4] SELL $49.21 @ $80,675.09
Rejections: 8
BUY $200.00 — MANDATE_LIMIT_EXCEEDED (per-tx breach)
BUY $500.00 — MANDATE_LIMIT_EXCEEDED (per-tx)
BUY $1000.00 — MANDATE_LIMIT_EXCEEDED (per-tx)
BUY $50.00 — MANDATE_SUSPENDED (agent revoked mid-run)
...
On-chain: revoked_onchain
Solana TX: https://explorer.solana.com/tx/...?cluster=devnet
```
Every line is observable on the dashboard, in the audit log, and on Solana Explorer.
## Source
Full implementation: [`abay94/regent-sdk-python/examples/binance_trading_agent.py`](https://github.com/abay94/regent-sdk-python/blob/main/examples/binance_trading_agent.py).
Run it yourself with `DEMO_MODE=true` for the scripted 11-trade screencast sequence, or no flag for autonomous price-signal trading.
## Related
---
source: https://docs.regentprotocol.org/resources/github.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# GitHub repositories
> What is public, what is not, and under which licence.
## Public repositories
- [regent-solana-programs](https://github.com/regent-protocol/regent-solana-programs) — The three Anchor programs behind the devnet deployments: agent registry, mandate registry, audit anchor. Apache-2.0.
- [regent-receipt-verify](https://github.com/regent-protocol/regent-receipt-verify) — Offline verifiers for Regent Control receipts and allow tokens — PyPI `regent-receipt-verify`, npm `@regent-protocol/receipt-verify`. Apache-2.0.
- [regent-httpsig](https://github.com/regent-protocol/regent-httpsig) — RFC 9421 HTTP message signatures, Web Bot Auth and AAuth for Python — PyPI `regent-httpsig`. Apache-2.0.
- [regent-sdk-python](https://github.com/abay94/regent-sdk-python) — Async Python SDK for the platform API, with the Binance trading-agent example. Install from GitHub (not on PyPI). Apache-2.0.
- [regent-control-sdk](https://github.com/abay94/regent-control-sdk) — TypeScript SDK for Regent Control. `npm install @regent-protocol/control-sdk`. Apache-2.0.
- [get4agent-mcp](https://github.com/abay94/get4agent-mcp) — MCP server for the get4agent marketplace. MIT.
## Published packages
| Registry | Package | What |
|---|---|---|
| PyPI | `regent-receipt-verify` | receipt / allow-token verifier |
| PyPI | `regent-httpsig` | RFC 9421 signer and verifier |
| PyPI | `regent-control` | Python SDK for Regent Control (gate-direct, sidecar session, dev mock, token verifier) |
| npm | `@regent-protocol/receipt-verify` | receipt verifier (`npx -p @regent-protocol/receipt-verify regent-verify`) |
| npm | `@regent-protocol/control-sdk` | TypeScript SDK for Regent Control |
| GHCR | `ghcr.io/abay94/control-sidecar` | the Regent Control sidecar image |
The names `regent` and `regent-sdk` on PyPI belong to unrelated projects.
## Private repositories
The protocol services (`regent-protocol`), the console (`regent-platform`), the Regent Control gate and sidecar sources (`regent-control`; the Python SDK from that repository is published on PyPI under Apache-2.0) and this documentation site are private — all rights reserved. Everything a third party needs to **verify** a Regent decision is in the public repositories above; nothing in the private ones is required for verification.
## Contributing
Pull requests and issues are welcome on the public repositories. The private ones are not open to external contribution.
---
source: https://docs.regentprotocol.org/resources/demo.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Live demo
> Try Regent Protocol end-to-end without writing any code.
The fastest way to see Regent in action is the hosted demo at [web.regentprotocol.org](https://web.regentprotocol.org).
## What you can do
| Capability | How |
|---|---|
| **Sign up** | Email and password; you start in a sandbox. Identity verification (real documents, a few minutes) is needed only to go live; on completion your DID is anchored on Solana devnet |
| **Register an AI agent** | One form on the Agents page. On-chain anchoring is automatic |
| **Create a spending mandate** | Set per-tx, daily, monthly limits in USD |
| **Issue an API key** | Use it to drive an agent programmatically |
| **Watch the audit log** | Every action your agent takes appears here within seconds, with hashes |
| **See Guardian alerts** | Run an agent with anomalous behavior and watch the alerts page |
| **Pull the kill switch** | Revoke an agent and watch its next authorize call fail in ~1 second |
## Run the example trading agent
The Binance trading agent showcase ([`regent-sdk-python/examples/binance_trading_agent.py`](https://github.com/abay94/regent-sdk-python/blob/main/examples/binance_trading_agent.py)) trades BTC/USDT on Binance testnet under Regent's governance:
```bash
pip install rich "git+https://github.com/abay94/regent-sdk-python.git"
export REGENT_API_KEY="rgnt_..."
export REGENT_ORG_ID="..."
export REGENT_AGENT_ID="agent_..."
export REGENT_MANDATE_ID="..."
export BINANCE_API_KEY="..." # https://testnet.binance.vision
export BINANCE_API_SECRET="..."
DEMO_MODE=true python examples/binance_trading_agent.py
```
`DEMO_MODE=true` runs a scripted 11-trade sequence designed for a 90-second screencast. Without the flag it runs autonomous price-signal trading.
## What you'll see
- Rich terminal dashboard with live BTC price, mandate usage bars, risk gauge, and trade feed
- Real authorizations approved or rejected by the mandate
- Audit events visible on the dashboard within seconds
- Solana TXs for agent registration, mandate registration, and the on-chain revocation when the agent is killed
See the full walkthrough at [AI Trading Agent](/use-cases/ai-trading-agent).
---
source: https://docs.regentprotocol.org/resources/agent-validation.md
---
> For the complete documentation index, see [llms.txt](https://docs.regentprotocol.org/llms.txt). Every docs page has a markdown twin: append `.md` to its URL, or request it with `Accept: text/markdown`. Full corpus in one file: [llms-full.txt](https://docs.regentprotocol.org/llms-full.txt).
# Agent Validation (agentvalidate)
[agentvalidate](https://validate.regentprotocol.org) is a joint product:
**identity & governance by Regent, behavioral evaluation by RagMetrics.** One lookup
answers three questions about any AI agent:
1. **Who is it** — cryptographic identity (RFC 9421 HTTP Message Signatures; both the
Web Bot Auth and AAuth dialects).
2. **Who answers for it** — a KYC-verified human owner who approved it and holds the
kill switch.
3. **How well does it behave** — a RagMetrics evaluation tier (A/B/C), signed into the
agent's passport with an expiry.
## How it relates to Regent
Regent Protocol (this documentation) is the financial authorization layer: mandates,
authorize-before-execution, anchored audit. Agent Validation applies the same identity
and governance machinery to a different question — *should this service trust an
incoming agent at all* — and packages it as a metered trust lookup for SaaS platforms,
with policy configured in a console rather than in code. KYA — Know Your Agent — is
Regent's product framework behind both; it is not a formal regulatory standard.
## Where to go
- Site & console: [validate.regentprotocol.org](https://validate.regentprotocol.org) ·
[self-serve signup](https://validate.regentprotocol.org/app/signup)
- Documentation: [/docs](https://validate.regentprotocol.org/docs) — quickstarts for
SaaS and agent builders, API reference, payment rails
- Machine surfaces: [llms.txt](https://validate.regentprotocol.org/llms.txt) ·
[AGENTS.md](https://validate.regentprotocol.org/AGENTS.md) ·
[skill.md](https://validate.regentprotocol.org/skill.md)
- Open-source verifier: [regent-httpsig](https://github.com/regent-protocol/regent-httpsig)
(`pip install regent-httpsig`)