Enrol an agent
An agent that runs on a machine you do not sit at — a cloud agent, a server, a teammate’s laptop — should not hold your organization’s key. Enrolment gives it two things of its own:
- its own signing key (Ed25519), generated on that machine; Regent keeps only the public half;
- a credential bound to this one agent (
rgnt_agt_…): the gate refuses it for any other agent, and it cannot manage the organization, create mandates or approve its own escalations.
The owner mints a one-time code in the console; the machine turns it into both. Nothing organization-wide is ever typed on the agent’s side, and the code can travel through a chat: it is single-use and expires.
Mint the code in the console
Open the agent (Agents → the agent) and find the Enrolment card. Choose whether this
machine’s requests must be signed (the default, and the right choice when the agent will use the
SDK or the hooks), then New invite. The console shows the code — rgnt_inv_…, shown once,
valid for 24 hours — together with the exact command to run.
Run the command on the agent’s machine
pip install regent-control
regent-control enroll rgnt_inv_… --label "where this runs"The command generates the agent’s key if the machine has none, registers the public half with a
proof of possession, and writes ~/.regent/agent-key.json and ~/.regent/credentials.json
(both mode 600). Its last lines name the registered key id and the agent. Options: --key-file
to reuse an existing key, --credentials to write elsewhere, --platform-url for another
Regent host.
Use the credentials
The SDK and the hooks pick the files up by themselves:
from regent_control import RegentControl
rc = RegentControl.from_credentials() # ~/.regent/credentials.json + agent-key.json
d = rc.authorize("stripe", "pay", resource="acct_supplier", amount_usd=12.5,
intent="pay the supplier invoice", idempotency_key="inv-4411-try-1")
if d.decision == "allow":
... # do the thing
rc.complete(d.decision_id, status="executed", downstream_ref="pi_…")d.decision is allow, deny or escalate. An escalated request waits under Approvals in
the console; after a person approves, the agent repeats the same call with the same
idempotency_key. Tools behind the Cloud Gateway (Agentcard, Stripe through the vault) are
rc.gateway("agentcard_shop", "buy", body={"ask": "…"}) from the same client, signed with the
same key — the gateway verifies the signature and the receipt names the agent’s key. For Claude Code, Codex or Hermes, regent-control hook install <runtime>
wires the same credentials into the runtime’s hooks instead.
Ask for a mandate
An enrolled agent holds a credential and, until a person sets limits, no mandate: every money
call is refused with MANDATE_NOT_FOUND. Budgets are per currency, so the refusal names the
currency it is missing in (d.currency): an agent with a tenge budget that tries to pay in
dollars asks for a dollar budget. The agent may ask for one; it never grants one:
req = rc.request_mandate(currency="USD", per_tx="10", daily="30", monthly="100",
purpose="office supplies")
print(req.request_id, req.status) # pending_approvalThe organization’s owners get the approval link by e-mail and a card under Approvals;
a person signs the limits (passkey in a production organization). rc.mandate_request_status(req.request_id)
reports pending_approval, approved (with the mandate_id), denied or expired; the
simpler way is to retry authorize later. The agent may propose caps per seller with the
request — request_mandate(..., entity_limits={"arbuz.kz": "3000", "kaspi.kz": ("5000", "30000"), "*": (None, "10000")})
— the person sees them row by row and signs them with the rest. When paying, name the
counterparty: rc.authorize(..., payee="arbuz.kz"); through a sidecar or the gateway the
rail names it instead, and the receipt says which. One request may wait at a time, an agent that
already holds an active mandate in that currency is told so (MANDATE_EXISTS), an agent may
hold at most three currencies (TOO_MANY_CURRENCIES), and the organization can switch agent
requests off. The ask itself is filed in the agent’s journal
as mandate.requested, next to the approval it led to.
What the console shows afterwards
The Enrolment card lists every invite with its state (pending, used, expired, revoked),
which key used it, and the credential it produced. Revoke on a credential cuts that machine
off immediately; revoking the agent cuts off everything. Receipts for decisions made with an
enrolled key carry key_custody: agent, which is how a reader later tells “the agent held its
own key” from “Regent held a custodial key for it”.
Whoever runs the code first becomes that agent. Send it only to the machine or the party that should, and revoke it from the card if it goes astray before it is used.
Start from the agent instead
When the person has no account yet, or you would rather not send them to a console, the agent can begin:
pip install regent-control
regent-control onboard --name "Grocery helper" --vendor "Instinct" --purpose "groceries"The command prints a link for the person (valid 15 minutes). They open it, sign in or create
an account with a passkey, set the budget in plain words and confirm. The command waits, then
claims the agent’s credential: the agent’s own key is generated and registered, the bound
credential lands in ~/.regent/credentials.json, and the mandate is already active. The first
payment still asks the person. Over HTTP the same three calls are POST /v1/onboard,
GET /v1/onboard/claim/{code} and POST /v1/onboard/claim/{code}.
An agent with no key store
Some agents can send HTTP with a stored secret but cannot run a command line or keep a key file (a hosted assistant, a no-code runtime). For those the owner creates the invite with require the agent’s own key and signed requests switched off, and the agent claims a bearer credential with an empty body:
curl -X POST https://api.regentprotocol.org/v1/enrol/rgnt_inv_…
# → {"control_key": "rgnt_agt_…", "credential": "bearer", "kid": null, …}The credential is bound to that one agent and nothing else; every decision and receipt names the
agent, and the console shows the invite was claimed with a bearer credential rather than a key. A
strict invite answers KEY_REQUIRED instead. Prefer the agent’s own key wherever the machine
can hold one: it is what lets a receipt prove which agent asked.
When not to enrol
- The agent lives in an app with custom MCP connectors (Claude, Claude Code): use Connect from your agent app — no key to hold at all.
- The agent cannot run a command line but can send HTTP with a stored secret: the consent screen gives it a bound key directly — see Without MCP.
- The agent runs on your own infrastructure next to the sidecar: the sidecar holds the key and the agent holds nothing — see Run the Sidecar.
Verified on production: 2026-10-12 (regent-control 0.6.0).