# 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)** — On-chain spending rulebook — per-transaction, daily, and monthly limits enforced before every action.
- **[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 `PATCH /v1/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
> On-chain spending rules that bound what an agent can do without requiring per-action human approval.
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.
## Why on-chain
The mandate ID, agent binding, and limit values are also written to Solana via the **`MandateRegistry`** program. Like AgentID anchoring, this is for tamper-evidence:
- An auditor can confirm the mandate's limits at the time of any disputed action
- A compromised Regent database cannot retroactively widen limits
- A regulator can verify the mandate existed and was bound to the right agent
## 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
Responsible party creates the mandate. Status starts as `pending`.
### Anchored on Solana
`blockchain-worker` submits the mandate to `MandateRegistry`. On confirmation, status flips to `active`.
### 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 four-stage pipeline
```mermaid
flowchart LR
INGEST[Event ingest
POST /audit/events]
HASH[payload_hash
SHA-256 of canonical JSON]
BATCH[Merkle batch
periodic seal]
ANCHOR[Solana
AuditAnchor program]
INGEST --> HASH --> BATCH --> ANCHOR
```
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` | Batch root written to Solana, `solana_tx` recorded |
The Merkle-batching is what makes anchoring economical: hundreds of events share a single on-chain transaction (and its fee) while each one retains its own cryptographic proof of inclusion.
## 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"
}
```
To verify off-chain:
1. Pull the batch root from Solana via the `AuditAnchor` program
2. Hash the event payload (canonical JSON) → get `payload_hash`
3. Walk up the Merkle tree using the proof
4. Confirm the resulting root matches the on-chain value
If steps 1–4 produce a match, the event is provably part of that batch and was not altered after anchoring.
## 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.
## 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 | `PATCH /v1/agents/{id}/revoke` | Anytime — the kill switch |
| Revoke a mandate | `PATCH /v1/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.
- [Compliance posture](/advanced/compliance-posture) — How responsible-party binding maps to specific regulatory frameworks.
---
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**. The KYC flow takes a few minutes and uses Regent's mock verifier in demo mode.
### 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 regent
```
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
- [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
```
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/control-sdk`) | your code | Node/TS agents |
| **MCP** (sidecar `/mcp`) | the sidecar only | MCP-native agents — zero code |
## 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/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://api.regentprotocol.org/.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 |
| `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/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/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: `
- **Protocol:** JSON-RPC 2.0 (`initialize`, `tools/list`, `tools/call`)
## 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).
> Prefer plain HTTP? The same tools are at `https://gw.regentprotocol.org/gw/{tool}/{path}` with
> the same two headers.
## 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](/sdk-python/example-binance) — 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
```bash
pip install regent
```
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.__version__)"
```
Should print the current SDK version (e.g. `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
- [Registering agents](/sdk-python/guide/registering-agents) — First real call: create an agent under your account.
---
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.
All REST requests go to the gateway at `https://api.regentprotocol.org` (production) and 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": "LIMIT_EXCEEDED",
"message": "LIMIT_EXCEEDED: Amount 200 exceeds per-transaction limit 50.000000",
"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 completed KYC on your account (`403 EMAIL_NOT_VERIFIED` /
`403 KYC_NOT_VERIFIED` otherwise). 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 '{"name": "demo-bot", "description": "what this agent does"}'
```
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
Requires completed KYC (`403 KYC_NOT_VERIFIED` otherwise). `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`.
```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: `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
> Ingest tamper-evident audit events and verify their Merkle proofs.
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`.
## 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. `payment.authorized`, `action.completed`.
- `payload` — arbitrary JSON; it is hashed (`payload_hash`) and stored.
Best practice: put 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//audit/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": "…"
}
```
## Verify a proof
```bash
POST /v1/organizations//audit/events//verify
GET /v1/organizations//audit/batches/
```
`verify` recomputes the event's Merkle path against its batch root; the batch record
carries the on-chain commitment. 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 seconds of ingestion.
---
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": "LIMIT_EXCEEDED", "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_…` |
| 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 |
|---|---|
| `LIMIT_EXCEEDED` | The amount breaks a per-transaction, daily, monthly, per-entity, or relational limit — 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 |
The historical name `MANDATE_LIMIT_EXCEEDED` was never emitted by the current API —
the live code is `LIMIT_EXCEEDED`.
---
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`, `seal_batch`, etc.)
- 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 live in [`packages/contracts-solana/`](https://github.com/abay94/regent-protocol/tree/main/packages/contracts-solana) of the regent-protocol repo.
```
packages/contracts-solana/
├── 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.30 |
| **Upgradeable?** | No — non-upgradeable by design (ADR-010) |
| **Source** | [`packages/contracts-solana/programs/agent-registry`](https://github.com/abay94/regent-protocol/tree/main/packages/contracts-solana/programs/agent-registry) |
## Account layout
Each agent gets a Program Derived Address (PDA) keyed by its agent ID hash:
```rust
#[account]
pub struct Agent {
pub agent_id_hash: [u8; 32], // sha256(agent_id)
pub did_hash: [u8; 32], // sha256(canonical DID payload)
pub responsible_party_hash: [u8; 32], // sha256(responsible_party_id)
pub status: AgentStatus, // Active | Revoked
pub registered_at: i64, // unix seconds
pub revoked_at: Option,
pub bump: u8,
}
```
The PDA seed is `[b"agent", agent_id_hash]`, so anyone can re-derive the address and read the account.
> Only hashes are stored — never the raw agent ID, DID payload, or responsible-party ID. This is for privacy: the on-chain record proves *existence and status* but doesn't disclose the human behind the agent.
## Instructions
### `register_agent`
Writes a new `Agent` account in `Active` state.
| Account | Type | Notes |
|---|---|---|
| `agent` | PDA | Initialized; payer-funded |
| `authority` | Signer | Must be the protocol authority |
| `payer` | Signer | Pays rent |
| `system_program` | Program | — |
| Arg | Type | Notes |
|---|---|---|
| `agent_id_hash` | `[u8; 32]` | SHA-256 of the agent ID string |
| `did_hash` | `[u8; 32]` | SHA-256 of the canonical DID document |
| `responsible_party_hash` | `[u8; 32]` | SHA-256 of the responsible party UUID |
Reverts if the PDA already exists.
### `revoke_agent`
Flips `status` to `Revoked` and stamps `revoked_at`.
| Account | Type | Notes |
|---|---|---|
| `agent` | Mutable PDA | Must exist; must be `Active` |
| `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.
## 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 lives in AWS KMS; only the `blockchain-worker` service can sign with it.
## 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("Status:", account.status); // Active | Revoked
console.log("Registered:", account.registeredAt);
console.log("Revoked:", account.revokedAt);
```
If the account is missing → the agent was never anchored. If `status` is `Revoked` → 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.
`MandateRegistry` anchors the existence, limits, and status of every spending mandate. A regulator examining a disputed authorization can verify the **exact limits** that were in effect at the time of the authorization.
| | |
|---|---|
| **Program ID (devnet)** | `8HAzw3UFGmabsHJkAsuGLfBZG8djYQ3J1FRNUVjkseMr` |
| **Framework** | Anchor 0.30 |
| **Upgradeable?** | No |
| **Source** | [`packages/contracts-solana/programs/mandate-registry`](https://github.com/abay94/regent-protocol/tree/main/packages/contracts-solana/programs/mandate-registry) |
## Account layout
```rust
#[account]
pub struct Mandate {
pub mandate_id: [u8; 16], // raw UUID bytes
pub agent_id_hash: [u8; 32],
pub per_tx_limit: Option, // base units of the configured currency
pub daily_limit: Option,
pub monthly_limit: Option,
pub currency: [u8; 8], // null-terminated ASCII, e.g. "USD"
pub status: MandateStatus, // Pending | Active | Suspended | Revoked
pub created_at: i64,
pub activated_at: Option,
pub bump: u8,
}
```
PDA seeds: `[b"mandate", mandate_id]`.
## Why limits are on-chain
The dollar amounts in `per_tx_limit`, `daily_limit`, and `monthly_limit` are stored directly on Solana, not just hashed. This is intentional:
- An auditor reviewing a disputed `MANDATE_LIMIT_EXCEEDED` rejection can confirm the exact limit value at the time
- A regulator can query the on-chain history of limit changes (each revoke + re-create creates new PDAs)
- The responsible party cannot retroactively claim the limit was different
The amounts are stored as **base units of the configured currency** — for USD, that's cents. The off-chain API serializes them as decimal strings for ergonomics, but the on-chain representation is integer-safe.
## Instructions
### `register_mandate`
Writes a new `Mandate` account in `Active` state.
| Arg | Type |
|---|---|
| `mandate_id` | `[u8; 16]` |
| `agent_id_hash` | `[u8; 32]` |
| `per_tx_limit` | `Option` |
| `daily_limit` | `Option` |
| `monthly_limit` | `Option` |
| `currency` | `[u8; 8]` |
Off-chain, `api-payment` first inserts the mandate with status `pending`. The `blockchain-worker` then calls `register_mandate`. On confirmation, an `onchain.mandate_registered` event flips the off-chain status to `active`.
### `revoke_mandate`
Sets `status` to `Revoked`. Distinct from `Suspended` — the latter is a soft state triggered by agent revocation, while `Revoked` is an explicit off-switch by the responsible party.
The on-chain program does not distinguish `Suspended` from `Active` (those are off-chain concerns). Only `Revoked` is an explicit on-chain transition.
## Currency representation
The `currency` field is fixed-width 8 bytes, null-terminated ASCII. Examples:
| Off-chain | On-chain bytes |
|---|---|
| `"USD"` | `0x55 0x53 0x44 0x00 0x00 0x00 0x00 0x00` |
| `"EUR"` | `0x45 0x55 0x52 0x00 0x00 0x00 0x00 0x00` |
| `"USDC"` | `0x55 0x53 0x44 0x43 0x00 0x00 0x00 0x00` |
This keeps the account a fixed size while still supporting major fiat and stablecoin denominations.
## Verifying a mandate off-chain
```typescript
const [mandatePda] = PublicKey.findProgramAddressSync(
[Buffer.from("mandate"), Buffer.from(mandateId, "hex")],
programId,
);
const account = await program.account.mandate.fetch(mandatePda);
console.log("Per-tx:", account.perTxLimit?.toString() ?? "∞");
console.log("Daily:", account.dailyLimit?.toString() ?? "∞");
console.log("Status:", account.status);
```
If the on-chain limits differ from what the API reports, **trust the chain** — that's the regulatory-safe choice.
## Related
- [AgentRegistry](/solana-programs/agent-registry) — Mandates carry their owning agent's hash and are functionally suspended when that agent is revoked.
- [Mandate concept](/concepts/mandate) — The off-chain enforcement layer (Redis counters, JWT issuance, 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 cheapest of the three programs — it stores one Merkle root per batch and that's it. But it's the keystone of Regent's tamper-evidence claim: every audit event ever ingested can be proven (or disproven) against an account in this program.
| | |
|---|---|
| **Program ID (devnet)** | `8N1PpbJZKmvJjG86XWpP82XrWzp8HY5FHZuzyQTgjJas` |
| **Framework** | Anchor 0.30 |
| **Upgradeable?** | No |
| **Source** | [`packages/contracts-solana/programs/audit-anchor`](https://github.com/abay94/regent-protocol/tree/main/packages/contracts-solana/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 AuditBatch {
pub batch_id: [u8; 16], // raw UUID bytes
pub merkle_root: [u8; 32],
pub event_count: u32,
pub sealed_at: i64,
pub bump: u8,
}
```
PDA seeds: `[b"batch", batch_id]`.
The account is tiny — 53 bytes plus discriminator — so anchoring is cheap even at scale.
## Instructions
### `seal_batch`
Writes a new `AuditBatch` account. Once written, an account is immutable — there's no `update_batch` instruction by design.
| Arg | Type |
|---|---|
| `batch_id` | `[u8; 16]` |
| `merkle_root` | `[u8; 32]` |
| `event_count` | `u32` |
Off-chain, `api-audit` accumulates events until either:
- A configured batch size threshold is reached (~512 events), or
- A configured time interval elapses (~5 minutes)
…then computes the Merkle root over the event payload hashes and calls `seal_batch`. The transaction signature is recorded on every event in the batch as `solana_tx`.
## 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
const [batchPda] = PublicKey.findProgramAddressSync(
[Buffer.from("batch"), Buffer.from(batchId, "hex")],
programId,
);
const batch = await program.account.auditBatch.fetch(batchPda);
console.log("Merkle root:", Buffer.from(batch.merkleRoot).toString("hex"));
console.log("Event count:", batch.eventCount);
console.log("Sealed at:", new Date(batch.sealedAt * 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 anchor IDL or borsh-deserialize manually
// Expected: status field offset, registered_at, revoked_at, etc.
```
If the account exists and `status == Active`, the agent is verifiably present and not revoked. If the account is missing, the agent claim is forged.
## Verify a mandate's limits
When a disputed transaction is challenged, you want to confirm the mandate's limits at the time of authorization.
```typescript
const programId = new PublicKey("8HAzw3UFGmabsHJkAsuGLfBZG8djYQ3J1FRNUVjkseMr");
const mandateIdBytes = uuidToBytes("cab07ae3-e0e3-48d7-9e41-dc10512c4329");
const [pda] = PublicKey.findProgramAddressSync(
[Buffer.from("mandate"), mandateIdBytes],
programId,
);
const account = await program.account.mandate.fetch(pda);
console.log("Per-tx:", account.perTxLimit?.toString()); // e.g. "5000" cents = $50
console.log("Daily:", account.dailyLimit?.toString()); // e.g. "30000" cents = $300
console.log("Status:", account.status); // Active | Suspended | Revoked
```
These limits are what the protocol used to authorize or reject the transaction. If the API's reported limits differ from these, trust the on-chain values.
## 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, and that the batch is anchored on Solana.
You need:
- The event's `payload` (or `payload_hash`)
- The event's `batch_id`, `merkle_index`, `merkle_proof` (returned by `GET /audit/events/{event_id}`)
- The `AuditAnchor` program ID: `8N1PpbJZKmvJjG86XWpP82XrWzp8HY5FHZuzyQTgjJas`
```typescript
import { createHash } from "crypto";
// 1. Canonicalize the event payload and hash it
function canonicalJson(obj: unknown): string {
// Sort keys, no whitespace
return JSON.stringify(obj, Object.keys(obj as object).sort());
}
const payloadHash = createHash("sha256")
.update(canonicalJson(event.payload))
.digest();
// 2. Walk up the Merkle tree using the proof
function combine(left: Buffer, right: Buffer): Buffer {
return createHash("sha256").update(Buffer.concat([left, right])).digest();
}
let current = payloadHash;
let index = event.merkle_index;
for (const sibling of event.merkle_proof) {
const sibBuf = Buffer.from(sibling.replace(/^0x/, ""), "hex");
if (index % 2 === 0) {
current = combine(current, sibBuf);
} else {
current = combine(sibBuf, current);
}
index = Math.floor(index / 2);
}
const computedRoot = current.toString("hex");
// 3. Read the on-chain root for this batch
const programId = new PublicKey("8N1PpbJZKmvJjG86XWpP82XrWzp8HY5FHZuzyQTgjJas");
const [batchPda] = PublicKey.findProgramAddressSync(
[Buffer.from("batch"), uuidToBytes(event.batch_id)],
programId,
);
const batchAccount = await program.account.auditBatch.fetch(batchPda);
const onchainRoot = Buffer.from(batchAccount.merkleRoot).toString("hex");
// 4. Compare
if (computedRoot === onchainRoot) {
console.log("Event verified on-chain");
} else {
console.log("Mismatch — event is either tampered or the API lied");
}
```
If steps 1–4 produce a match, the event was provably in the batch, and the batch is anchored on Solana. **No trust in Regent is required.**
## Programmatic verification via the CLI
A future Regent CLI will wrap these recipes so a single command suffices:
```bash
regent verify agent agent_b1c59d23...
regent verify mandate cab07ae3-e0e3-48d7-9e41-dc10512c4329
regent verify event trade-2234146-d41d57ed
```
…each returning a green check (or a structured failure reason). Until that lands, the snippets on this page are the source of truth.
## 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 `status` is still `Revoked` |
| Widening a mandate's limits retroactively | Yes | On-chain `per_tx_limit` is fixed |
| 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 regent rich httpx
```
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 — LIMIT_EXCEEDED (per-tx breach)
BUY $500.00 — LIMIT_EXCEEDED (per-tx)
BUY $1000.00 — 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
> Where the code lives.
Regent Protocol is split across four repositories:
- [regent-protocol](https://github.com/abay94/regent-protocol) — Backend services (api-identity, api-audit, api-guardian, api-payment, api-platform), blockchain-worker, Solana Anchor programs, infra.
- [regent-platform](https://github.com/abay94/regent-platform) — Dashboard frontend (Next.js + React + Tailwind).
- [regent-sdk-python](https://github.com/abay94/regent-sdk-python) — Async Python SDK + Binance trading agent example. MIT licensed.
- [regent-docs](https://github.com/abay94/regent-docs) — The site you're reading right now. Mintlify .mdx sources.
## License
| Repository | License |
|---|---|
| `regent-protocol` | All rights reserved (review-only) |
| `regent-platform` | All rights reserved (review-only) |
| `regent-sdk-python` | MIT (clients should be freely usable) |
| `regent-docs` | All rights reserved (review-only) |
## Contributing
External contributions are not currently accepted while the protocol is in the Bridge Round phase. We may open contribution channels after the Solana Summit and mainnet launch.
Issues and bug reports are welcome on any repo.
---
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 + complete KYC** | Email + the mock KYC flow (~2 min). Issues you a DID 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 regent rich
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`)