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
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)
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 itTypeScript — gate-direct
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.
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-readyHuman-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.