What the approver needs on screen
An approval gate is only as good as the decision the human makes in four seconds. Most interfaces show intent when they should show consequence.
The state machine behind an approval gate can be perfect and the gate can still be worthless, because the gate is only as good as the decision a person makes in the few seconds they look at it.
Most approval interfaces show what the agent intends. The reviewer needs to see what will happen.
"Send onboarding email to 47 contacts — Approve?" is not a decision. It is a request to trust the system, which is exactly what the gate exists to avoid.
Show the consequence, not the intent
The difference in practice:
Intent. The agent will update the pricing records for 12 products.
Consequence. A table of twelve rows, each with the current price, the new price, and the difference. Three of them are highlighted because the change exceeds 20%.
The second takes more work to build and it is the only version that lets a reviewer catch the row where a decimal moved. The first can only be approved or rejected wholesale, on faith.
For anything that modifies existing state, the answer is almost always a diff:
type ApprovalView = {
summary: string // one line, for the queue listing
changes: {
entity: string
field: string
before: unknown
after: unknown
/** Set when the change is unusual against recent history. */
flag?: "large_delta" | "unusual_target" | "first_time"
}[]
/** Everything that made the agent propose this. */
evidence: { label: string; value: string; sourceUrl?: string }[]
}
The flag field is what makes a long list reviewable. A reviewer facing forty
changes cannot examine each one; a reviewer facing forty changes with three
flagged reads those three carefully and scans the rest. Flagging is the interface
doing triage, which is the only way volume and care coexist.
For new content, show the artefact
Where the action creates something rather than changing it — an email, a document, a message — show the thing exactly as the recipient will see it.
Not a summary. Not the template with variables. The rendered output, with the actual recipient, the actual subject line, the actual body.
Alongside it, the evidence that produced it. In an outbound system that means the specific finding the message cites, with a link to the page it came from — because the failure mode being guarded against is a message that confidently describes something that is not true, and the reviewer can only catch that if they can see the claim and its source together.
Two anti-patterns that void the gate
Approve all. A button that clears the queue in one click is the queue not existing. It gets added because the backlog grew, and once it exists it becomes the default action.
If bulk approval is genuinely needed, make it selective and typed: approve all changes under 5% delta, approve all for this known supplier. A bulk action with a predicate is a policy; a bulk action without one is an abdication. And either way the queue depth problem is the real thing to fix — bulk approval is a symptom.
A summary generated by the same model. An LLM-written description of what the agent is about to do, shown as the basis for approval. If the model misunderstood the task, the summary describes the misunderstanding faithfully and reassuringly. The reviewer is checking the model's work using the model's own account of it.
Render the consequence from the structured action, deterministically. Code, not generation.
Design for the keyboard
Approval volume is a throughput problem and throughput is decided by hands leaving the keyboard. The same arithmetic as the review queue: at $25/hour, a twenty-second approval costs 14 cents and a four-second one costs 3.
j/kto move,ato approve,rto reject,eto edit.- Reject requires a reason — one keystroke from a short list, not free text. Reasons are the training data for improving the agent, and free text is not analysable.
- Never a confirmation dialog on approve. Make undo cheap instead, with a ten-second window before the action actually dispatches.
That last one is worth stating plainly: a confirmation dialog doubles the cost of every approval to protect against a mistake that a brief undo window handles better. Undo also protects against the mistake nobody predicted, which confirmation does not.
Show the reviewer their own accuracy
The failure mode of a high-volume approval queue is reflexive clicking. The countermeasure is feedback:
-- Per reviewer, per week: how often did an approved action later get
-- reversed, bounce, or generate a complaint?
select reviewer, count(*) as approved,
count(*) filter (where outcome_bad) as later_reversed
from approvals
where approved_at > now() - interval '7 days'
group by 1;
Surface it to the reviewer, not to their manager. Someone who can see that three of their approvals last week were reversed slows down on their own. Someone who receives no signal has no reason to believe anything is wrong.
What the queue listing shows
Before the detail view, the list. Sort by risk, not by arrival:
select * from pending_approvals
order by
(flags <> '{}')::int desc, -- anything flagged first
estimated_impact desc, -- then by what it affects
expires_at asc -- then by what expires soonest
Arrival order is the wrong default. It means the highest-risk item waits behind forty routine ones, and it is the routine ones that train the reflex.
More in the approval gate is a state machine, what happens when nobody clicks approve, and agentic workflows with human approval gates.