Why every AI agent we ship has a gate it cannot open
An automated system that guesses when it is unsure is worse than no system at all. A person who is unsure asks. Software that is unsure publishes. So we build the asking in, and we make the uncertain answer mean stop.
The failure that started it
WhereToGoNYC publishes nightlife events. Each event has a flyer. The flyer is the art a guest sees before they buy, and it is the one asset a pipeline is most tempted to trust, because it arrives with a filename that tells you what it is.
Three flyers went out with filenames that looked correct in every way. The dates were right. The venue was right. The naming matched the event. Every conventional check we had passed them. And the art itself, printed in the pixels, said “AUGUST 20TH” on an August 27 card, “August 19th” on an August 26 card, and “08.20” on another August 27 card.
Filenames lie. Metadata lies. The only thing that does not lie is what is actually printed on the image, and nothing in the system was reading that.
The gate we built
Now every poster is read by a vision model — Gemini 2.5 Flash at temperature zero, so the same image gets the same answer every time. It extracts three things from the pixels: the printed date, the headliner name, and whether the art is dateless branding rather than a specific night.
Those three facts go into judgeFlyer(), a pure function with no network calls and no side effects, covered by unit tests. Two rules do most of the work:
- Printed date does not match the event date. Do not publish. The art is advertising a different night.
- No printed date, but a named headliner. Do not publish. Reusing that art would advertise the wrong artist to people buying tickets.
Making the judgment a pure function matters more than the model choice. The AI produces facts. A tested, deterministic function makes the decision. We can prove what the second half does. We can only sample the first half.
It fails closed, and that is the whole point
No API key. Spent quota. A timeout. Output the parser cannot read. Every one of those means the same thing: do not publish. There is no path where the gate shrugs and lets the flyer through because it could not reach the model.
This is the decision most teams get backwards. When the check breaks, the tempting default is to keep the pipeline moving, because a stalled pipeline is visible and a bad publish is not. But a stalled pipeline costs you a delayed listing. A bad publish sells tickets to the wrong night. Those are not the same size of mistake, so they should not have the same default.
An unreachable check is not a passing check. Treat a transport failure as an unknown, and treat an unknown as a stop.
Every gate has a scar behind it
We did not design this set of gates up front. Each one exists because a defect reached the owner first: wrong-night art, a mismatched promo code, a doors-time error, a link still selling a night that had already happened. Someone noticed, and then we wrote the rule.
The three bad flyers are now frozen as regression tests. They are not documentation of a past mistake — they are the mistake, still running, every time the code changes. That is the only version of “we fixed it” that survives a refactor six months later.
The full ledger of gates lives in the codebase, next to the code they guard. Not in a wiki. Not in someone’s head. If you cannot point at the file that enforces a rule, the rule is a preference.
What we would check on your systems
- Find your automation’s failure default. When the API key is missing or the model times out, does the work stop, or does it continue as if the check passed? Test it by revoking the key on purpose.
- Ask what your checks actually read. A filename, a database field, and the content itself are three different sources. Verify the one your customer sees.
- Separate the guessing from the deciding. Let the model extract facts. Let a tested function make the call. You cannot unit-test a vibe.
- Turn every past incident into a test. If the bad input is not in your test suite, you have not fixed anything — you have patched it.
- Size the two mistakes against each other. Cost of stopping too often versus cost of publishing something wrong. Set the default to the cheaper one, deliberately, and write down why.
The rule we ship by
Every agent we build has at least one gate it cannot open on its own. Not because we distrust the model — the vision gate is genuinely good at reading posters — but because confidence and correctness are different things, and only one of them is observable from inside the system.
Automation earns trust by being predictable when it is wrong, not by being impressive when it is right.