Playbook: Customer-Support Copilot
The scenario
A copilot helps support agents resolve tickets. It can look up a customer, draft replies, and — the part that matters — issue refunds and update account settings. Those last actions change money and state, so they need bounded, verifiable authority.
The risk
Without a mandate, the copilot's authority is "whatever the support API key can do." A prompt-injected ticket, a confused plan, or a bug could issue a large refund, touch the wrong customer, or change a setting no one intended. Nothing would explain, after the fact, why the action was allowed.
The mandate design
| Field | Value | Why |
|---|---|---|
| Principal | user:support-lead@acme.com | A human owns the delegation |
| Delegate | agent:support-copilot | The specific agent, not a vague class |
| Actions | billing.refund.issue, account.setting.update | Only the two write actions it needs |
| Resource | acme:customer:* | Any customer, but nothing outside the customer domain |
| Constraints | refunds require human review; 30-day expiry; rate limit | Force a human in the loop and bound blast radius |
Create the mandate
const mandate = await mandaitor.createMandate({
principal: {
type: "NATURAL_PERSON",
subject_id: "user:support-lead@acme.com",
display_name: "Support Lead",
},
delegate: {
type: "AGENT",
subject_id: "agent:support-copilot",
display_name: "Support Copilot",
},
scope: {
actions: ["billing.refund.issue", "account.setting.update"],
resources: ["acme:customer:*"],
effect: "ALLOW",
},
constraints: {
time: { duration: "P30D" },
rate_limits: { max_operations: 200, window_seconds: 3600 },
context: { requires_human_review: true },
},
});
Enforce it at runtime
Verify inside the tool handler the model calls — before the refund actually happens.
async function issueRefundTool(args: { customerId: string; amount: number; currency: string }) {
const result = await mandaitor.verify({
delegate_subject_id: "agent:support-copilot",
action: "billing.refund.issue",
resource: `acme:customer:${args.customerId}`,
context: { amount: args.amount, currency: args.currency, channel: "support" },
});
if (result.decision !== "ALLOW") {
// REVIEW or DENY — hand off to a human, do not issue the refund.
return { authorized: false, decision: result.decision, reason_codes: result.reason_codes };
}
const refund = await billing.issueRefund(args);
return { authorized: true, refund, proof: result.proof };
}
Mandaitor returns the decision; your handler decides how strict to be. A common pattern: treat any refund over a threshold as requiring review by checking result.decision === "REVIEW" (driven by the requires_human_review constraint) and routing it to a human queue.
What should be denied
Paste these into the Sandbox with the mandate above and confirm the agent cannot:
| Test | Expected | Reason |
|---|---|---|
billing.refund.issue on acme:customer:c_123 | REVIEW | Human review required by constraint |
billing.subscription.cancel on acme:customer:c_123 | DENY | Action not in scope |
billing.refund.issue on acme:vendor:v_9 | DENY | Resource outside acme:customer:* |
| Any action after the mandate is revoked | DENY | Mandate not active |
If those four behave as expected, the copilot is bounded.
Evidence & review
Every ALLOW returns a signed proof — store it with the ticket. In the compliance dashboard, a reviewer can see which refunds the copilot was authorized to make and which were stopped, without reading agent logs.
Adapt it
- Tighter scope: replace
acme:customer:*with the specific customer on the open ticket (acme:customer:${ticketCustomerId}) so the copilot is bound to the case it is working. - No writes at all: drop the actions to read-only and let humans execute — see the data-analyst playbook.
- Tiered approval: for higher-risk account changes, use the deployment playbook pattern.