# 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`)