Regent ControlWalkthrough: Refund Agent

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.
ScenarioOutcomeEnforced by
$50 refund · active · to originalallowpolicy + mandate
Refund on an account under reviewescalate → manager approvespolicy (Cedar)
Refund on a frozen accountdenypolicy (Cedar)
$250 (over the $200 per-tx cap)deny MANDATE_LIMIT_EXCEEDEDmandate
Refund > the original chargedeny EXCEEDS_REFERENCEmandate (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_refundsper-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_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

$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 / escalate

Or 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 SidecarTry it locally.