Approval gates: designing the place a human has to say yes
Table of Contents
The question is not whether to keep a human in the loop. It is where, precisely, and enforced by what.
Put the gate everywhere and you have built a slow assistant that needs babysitting. Put it nowhere and you have built something that will eventually do something expensive at three in the morning. The value is entirely in drawing the line in the right place and then holding it.
Three categories always wait
The line that has held for me:
- Anything published externally. Anything a person who is not me will read with my name on it.
- Anything that moves money. Charges, refunds, payouts, subscription changes.
- Anything irreversible. Deletions, migrations, sends. Anything with no undo.
Everything else ships. That last sentence is the part that makes it work - a gate policy that does not clearly release the remaining ninety percent is just a slow manual process wearing a policy’s clothes.
The unifying property is not risk, it is reversibility. A wrong database write I can correct in a minute. A wrong email to two thousand people I cannot correct at all. Sort by what you can undo, not by what feels important.
A prompt instruction is not a gate
This is the distinction that decides whether the whole thing is real.
“Always ask before sending an email” in a system prompt is a preference. It holds most of the time. It does not hold when the context gets long, when the instruction conflicts with something else, or when a user phrases a request in a way that reads as prior authorisation.
A gate is enforced by the system: the send tool refuses without an approval token, and the token can only be minted by a human action outside the model’s control. Now it holds regardless of what the model was told, inferred, or was persuaded of.
If your safety property can be removed by a sufficiently confident sentence in the conversation, it is not a safety property.
The prepare/execute pattern
The shape that keeps agents useful while gated is splitting the action in two.
Prepare does all the work. Validates inputs, computes the amount, drafts the copy, resolves the recipients, and returns a complete summary of exactly what will happen - plus a short-lived token. Nothing has occurred yet.
Execute takes the token and performs the action. It refuses unless the token has been approved out of band, and the token expires.
The agent gets to do the entire job autonomously right up to the boundary. The human sees a finished thing and makes one binary decision. That is the correct division of labour: the expensive part is the work, the part needing judgement is the release.
Bind the token to the exact payload. If the approved amount was £40 and the executed amount is £400, the token must not validate. Otherwise you have built an approval for “a refund” rather than “this refund”, which is a meaningfully weaker thing.
Show the diff, not the intent
An approval request saying “Send the newsletter?” is not reviewable. The human approves the idea and finds out about the content afterwards.
Show what will actually happen: the rendered email, the exact amount and recipient, the specific rows to be deleted. The reviewer needs to be able to catch a mistake, and you cannot catch what you cannot see.
Approval fatigue is a failure mode
A human who approves forty things a day stops reading them by the tenth. At that point the gate is theatre - it produces an audit trail showing approval while providing no actual oversight, which is arguably worse than no gate, because it manufactures false confidence.
Symptoms worth watching: approvals arriving faster than they could be read, approvals batched without inspection, someone asking for a way to auto-approve a category.
The fix is always to reduce volume, never to reduce scrutiny. Widen what ships freely, batch related approvals into one meaningful decision, and be honest that a category being approved reflexively should probably not be gated at all.
Design the “no” path
Most implementations handle approval and neglect rejection. Then a human says no and the agent has no idea what to do.
A rejection should be able to carry a reason, and that reason should reach the agent as actionable input. “No, the tone is wrong” means revise and re-submit. “No, do not do this at all” means stop and drop the task. Without the distinction, every rejection turns into a fresh briefing conversation and the gate becomes expensive to operate.
What to read next
- Auth for agents - the authorisation layer beneath the gate.
- Designing an MCP server - implementing prepare/execute as a tool pair.
- Colophon - the gates that apply to this website, and what actually waits for me.