Skip to content
Writing/Detail

Approval Gates for AI Agents: Why 'Allow' Is Not the Same as 'Checked'

21/09/2026
Tech
856 Words
4Min read

An approval gate for an AI agent is the piece of code that sits between the agent deciding to do something and that thing actually happening, and hands the call to a stricter rule, or a human, whenever the consequences are real. Mine had one job. It did the job perfectly, right up until the day it did the opposite of the job, and nothing about how it ran told anyone that had happened.

Approval Gates for AI Agents: Why ‘Allow’ Is Not the Same as ‘Checked’ - infographic

What is an approval gate for an AI agent?

A gate is not “the agent asked, and something answered.” It has to be able to say three separate things: deny it, allow it, or say nothing and let something else downstream decide. Most people build it as if there are only two outcomes, block or let through, and that is exactly the bug that got me.

I run a company through a stack of AI agents, and a chunk of that stack is small watchdog scripts, hooks that fire before a bigger action goes ahead. Two of them exist purely to stop an agent from reading too much untrusted text into its own context at once, a cost and safety control, nothing dramatic. Both of them, on the normal happy path where nothing looked wrong, printed the word “allow”.

That one word does not mean “this looks fine to me.” In the framework these hooks run under, it means skip the permission system entirely. Allow is not a vote of confidence, it is an instruction to stop checking. I wrote two guards whose reassuring green light was, mechanically, a switch that turned off the actual lock.

Here is where it stopped being a shrug. The delegate agent these hooks were guarding had a separate, narrow permission list, built specifically so it could read SSH keys, AWS credentials, and cloud secret stores for one bounded task and nothing else. Every one of those files happened to sit under the size threshold the guard used to decide whether a read was “big enough to worry about.” So on every ordinary run, the guard that was supposed to be a second layer of caution was the thing quietly telling the first layer not to bother. I had built a gate, named it a guard, and it spent its whole life waving things through.

Silence reads as “fine,” and that is the trap

Nobody caught this by watching the gate fail. It doesn’t fail. It runs, it exits cleanly, it prints “allow,” and every one of those signals looks identical to a gate doing its job well. The only way anyone found it was a second reviewer, another agent doing a code review, that stopped and asked what the word “allow” actually did in that specific framework instead of assuming the label matched the intent.

That is the general shape of the trap, not a one-off typo. The same week, a status line on my own machine had been rendering blank for seventeen days because it was quietly spawning a binary that no longer existed at that path. An empty row and a missing binary look, from the outside, exactly like “nothing configured here.” A refusal that never announces itself is indistinguishable from success, to a human glancing at a screen and, worse, to any downstream system that’s supposed to be learning from that signal. If you are trying to run something that improves itself from its own data, and the data itself quietly lies about what happened, you are steering with a broken instrument and reading it as a smooth road.

How to build a gate that actually gates

Four things I now do to every check that stands between an agent and something that matters:

  1. Write down all three outcomes before you write the code. Deny, allow, silence. If your design only has two boxes, you have already built the bug, you just haven’t found it yet.
  2. Never let “I didn’t check” be expressible as approval. If a guard cannot evaluate a case, that has to produce a distinct signal, not the same green light as a case it actually inspected.
  3. Test the gate against the exact thing it exists to stop, not a case near it. If it protects credential files, hand it a credential file and check for a block. Checking that it “doesn’t crash” tells you nothing.
  4. Audit for drift between the gate’s name and its measured behaviour, on a schedule, not just at launch. A threshold, a variable, a path pattern, any one of these can separate quietly from what the gate is supposed to mean, one small change at a time, until the thing called a guard hasn’t guarded anything for months.

The guard file that stopped guarding was still called a guard the entire time. Naming something a gate does not make it one. What makes it one is what happens the one time it’s actually tested, and until you’ve done that test, you don’t have a gate, you have a script that agrees with you.

Back to writing
End of Post