Pause the run before any tool call that sends, pays, deletes, deploys or emails customers, and show one approval card: what the agent wants to do, the values a person must check (recipients, amount, target), the risk level, and Approve or Reject with an optional note. Resume or stop the run on the decision, never on a timeout.
Which actions need a person#
Ask for approval when the action is hard to undo or reaches outside your product:
- Sends: emails, messages, posts, calendar invites.
- Moves money: refunds, credits, purchases, payouts.
- Deletes or overwrites: records, files, branches, production data.
- Deploys or changes access: releases, permissions, API keys.
Reads, searches and drafts usually do not need a person. Asking for everything trains people to click Approve without reading, which is worse than not asking. The OWASP Top 10 for Agentic Applications (December 2025) lists human sign-off on high-impact actions as a control for tool misuse and for misplaced trust in agents; the key word is high-impact.
What the card must show#
- The action as a sentence in the agent's words: "Send renewal reminders to 3,412 customers".
- The values that decide it. Three to five rows: who, how many, how much, from where. Amounts, counts and IDs in tabular figures so they line up and are easy to compare.
- Risk. Low, medium or high, based on the action and its size, set by your code, not by the model. High risk should take a second, deliberate press.
- Who is asking. The agent's name and the run ID, so the decision can be traced later.
- Approve and Reject. Use verbs that say what happens ("Approve and send"). Reject takes an optional note that goes back to the agent so it can try another way.
After a decision, fold the card into a one-line receipt ("Approved: 3,412 reminders queued") so the thread stays readable and there is a record of what was agreed.
Wiring it to the run#
The agent loop has to actually wait. The pattern most agent frameworks support is the same:
- The model proposes a tool call that your policy marks as needing approval.
- Your server stores the pending call with its arguments and returns it to the client instead of executing it.
- The client renders the card. The decision goes back to the server.
- On approve, the server executes the stored call (the arguments the person saw, not new ones) and continues the run. On reject, it sends the note back to the model as the tool result.
Two rules keep this safe. Never execute different arguments from the ones shown. Never auto-approve on a timer; a countdown may pause the run, but expiry should cancel, not approve. Write each decision to an audit log with who decided and when.
With PixilUI#
ApprovalCard shows its own pending state while your callbacks run, then folds into the receipt.
import { ApprovalCard } from "@/components/pixilui/approval-card";
export function SendRemindersApproval({ runId, onDecision }: { runId: string; onDecision: (approved: boolean, note?: string) => Promise<void> }) {
return (
<ApprovalCard
title="Send renewal reminders to 3,412 customers"
source={`Billing agent · run ${runId}`}
risk="medium"
items={[
{ label: "Template", value: "Q3 renewal, second notice" },
{ label: "Audience", value: "3,412 customers on annual plans", numeric: true },
{ label: "Sends from", value: "billing@northwind.co" },
{ label: "Estimated cost", value: "$41.30", numeric: true },
]}
labels={{ approve: "Approve and send" }}
onApprove={() => onDecision(true)}
onReject={(note) => onDecision(false, note)}
/>
);
}Install it with npx shadcn@latest add @pixilui/approval-card. For multi-step agents, put the card inside the waiting step of a run timeline so the person sees what happened before the request.
When not to use this#
- Reversible, low-stakes actions. If the action can be undone in one click (archive, label, draft), do it and offer Undo instead of asking first.
- High volume. If an agent needs fifty approvals an hour, one card at a time will be ignored. Use a review queue with keyboard shortcuts, or change the policy so the agent asks less.
- Your own destructive actions. When a person deletes their own data, a confirm dialog or hold-to-confirm fits better; there is no agent to explain.
- As your only control. Approval does not replace permissions. The agent should not hold credentials for actions nobody may approve.