Regent Control — Python SDK
Runtime policy enforcement for AI agent actions. Every action your agent takes — a refund, a wire, a DB write, an API call — is authorized first: allow / deny / escalate, with a short-lived scoped token, the human it's acting for, the intent, and an immutable audit record.
pip install regent-control
Requires Python 3.10+. The only dependency is httpx.
Two ways to integrate
1. Gate-direct — RegentControl (you hold the provider key)
Authorize the action, get a scoped token, perform it, report the outcome.
from regent_control import RegentControl, MandateExceeded, Escalated
control = RegentControl(api_key="rgnt_ctrl_…", agent_id="agent_refund_bot")
decision = 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 rep's OIDC token — verified by the gate
intent="refund the duplicate charge on ticket 8842",
facts={"account_status": "active", "refund_to_original": True},
)
if not decision.allowed:
... # decision.code / decision.reason
do_refund(scoped_token=decision.token) # the token authorizes the downstream call
control.complete(decision.decision_id, status="success", downstream_ref=refund_id)
A deny is a normal return value, not an exception. Opt into raising:
decision.raise_for_status() # raises MandateExceeded / PolicyDenied / Escalated / …
# or
control.authorize_or_raise(tool="payments", action="refund.create", amount_usd=999)
2. Sidecar-routing — SidecarSession (the agent holds no key)
Point your normal HTTP call at the sidecar; it injects the real vaulted credential and forwards. A policy deny comes back as a typed error.
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},
idempotency_key="T-8842:ch_aaa")
refund = resp.json()
except MandateExceeded as e:
... # e.code, e.reason, e.decision_id
Both clients have async twins: AsyncRegentControl and AsyncSidecarSession.
Wrap a tool in one line — @guarded
from regent_control import RegentControl, guarded
control = RegentControl(api_key="rgnt_ctrl_…", agent_id="agent_refund_bot")
@guarded(control, tool="payments", action="refund.create",
amount_arg="amount_usd", mandate_id="mnd_support_refunds")
def issue_refund(*, charge: str, amount_usd: float, scoped_token: str = "") -> str:
return provider.refund(charge, amount_usd, token=scoped_token)
issue_refund(charge="ch_1", amount_usd=50) # authorized → runs → completed
issue_refund(charge="ch_1", amount_usd=999) # raises MandateExceeded; never runs
The decorator authorizes before the call, injects the scoped token (if the function
declares scoped_token), and reports success/failed to close the audit. Pass
per-call context via a control_context={...} keyword.
Verify the scoped token at your service edge — regent_control.verify
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 — no shared static key, no trusting the caller).
pip install "regent-control[verify]" # adds PyJWT
from regent_control.verify import TokenVerifier, ScopeError
verifier = TokenVerifier(jwks_url="https://control-api.regentprotocol.org/v1/control/.well-known/jwks.json")
scoped = verifier.verify(token, expected_tool="payments", expected_action="refund.create")
# raises TokenError (bad sig / expired / wrong issuer|audience) or ScopeError (wrong scope)
# scoped.agent_id / scoped.decision_id are audit-ready
See examples/verify_service.py for a FastAPI dependency.
Develop locally with zero prod — regent-control dev
regent-control dev --port 8009 --deny-over 50 --escalate-over 500
# point any client at base_url="http://localhost:8009"
In tests, force a verdict with the in-process mock:
from regent_control import RegentControl, MandateExceeded
from regent_control.dev import run_mock_control
def test_over_limit_is_denied():
with run_mock_control(deny_over=50) as base_url:
control = RegentControl(api_key="dev", agent_id="a", base_url=base_url)
d = control.authorize("stripe", "refund.create", amount_usd=999)
assert d.code == "MANDATE_LIMIT_EXCEEDED"
Typed errors
| Exception | Decision code | Meaning |
|---|---|---|
IdentityNotResolved |
IDENTITY_NOT_RESOLVED |
the agent id didn't resolve |
AgentNotActive |
AGENT_NOT_ACTIVE |
the agent is suspended/revoked |
PolicyDenied |
POLICY_DENIED |
a policy rule forbade it |
ToolNotAllowed |
TOOL_NOT_ALLOWED |
the tool isn't in the agent's catalog |
MandateNotFound |
MANDATE_NOT_FOUND |
a money action with no mandate |
MandateExceeded |
MANDATE_LIMIT_EXCEEDED |
over the spend cap |
RiskThresholdExceeded |
RISK_THRESHOLD_EXCEEDED |
risk score too high |
VelocityExceeded |
VELOCITY_EXCEEDED |
too many decisions for this agent in the window; back off |
DuplicateRequest |
DUPLICATE_REQUEST |
an identical money request was just allowed; pass an idempotency_key for a deliberate repeat |
Escalated |
ESCALATION_REQUIRED |
needs a human approval (carries .escalation) |
ControlError (transport/auth/5xx) and ControlNetworkError (never reached the
plane) are always raised; everything above subclasses ControlDenied.
Request signing (optional)
Pass sign_requests=True to HMAC-sign the request body with the per-agent secret
HMAC-SHA256(api_key, agent_id) (X-Agent-Signature). Identical to
@regent/control-sdk (TypeScript), so an agent moves between the two unchanged.
Examples
examples/quickstart.py— gate one action.examples/refund_agent.py— the full bank refund agent (allow / deny / escalate), runnable with no setup.
Metadata
Release files for regent-control 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| regent_control-0.1.0.tar.gz | 26.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| regent_control-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 53.4 kB
Release files / regent_control-0.1.0.tar.gz
| Download URL | regent_control-0.1.0.tar.gz |
|---|---|
| Size | 26.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d641dbbf96d1d7009861fef823fded2c3c676796da3bdf186837dbdf740eb96c
|
|
BLAKE2b-256 checksum How to use checksums |
65db16be887c727eda73b8323dc3945e1e31811fc1550061331645e92f730ed3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency logRelease files / regent_control-0.1.0-py3-none-any.whl
| Download URL | regent_control-0.1.0-py3-none-any.whl |
|---|---|
| Size | 26.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f0f26a1f74205c83d511cbb16702b7a8a71d9944331fa6ae13a29568254b02d6
|
|
BLAKE2b-256 checksum How to use checksums |
2db66bf33ef6dad8bd2b43cd415ca73b4dd009dd9202aae3acb7eb6120e0dcb1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 4, 2026.
Transparency log