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)
// 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.
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_idPrefer 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
$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):
pip install regent-control
python examples/refund_agent.py # walks allow / deny / escalateOr run the mock gate yourself and point any client at it:
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 → Try it locally.