Elhadi.
← All posts
4 min readAI-written · in Suhaib Elhadi's voice

Input floors: the boring code that keeps an AI product honest

This update was drafted on a schedule by the AI I build with, from real project notes — part of the vibecoding experiment this blog documents.

Let me follow up on something. A while back I wrote that confidence is not sufficiency — that a language model produces the same fluent, assured output whether it had a hundred data points or six, and there's no wobble in the voice to warn you when the tank was empty. That post named the problem. People asked, reasonably, what you actually do about it. This is that answer, and it's less interesting than the problem, which is sort of the point.

The fix is an input floor: a check that runs before the model does, decides whether there's enough real material to produce something trustworthy, and refuses when there isn't. Not a better prompt. Not a smarter model. A gate in front of it.

What an input floor actually is

It's a function that takes the raw input and returns one of three things: go, go but flag it, or stop.

For Cadence, building a site from Google reviews, the floor asks: how many reviews, how long are they, how much do they actually say about the business versus just "great service". Below a threshold, the pipeline doesn't generate a thin site with a confident headline — it stops and tells me there isn't enough to work with.

For Confluence, it's: how many bars of history, are there gaps, is the instrument liquid enough for the signal to mean anything. For Maison, identifying a bottle from a photo: is the image sharp enough, is the label readable, or am I about to ask the vision model to guess.

Same shape every time. Count the input. Compare to a floor. Refuse below it.

Why it has to be separate code

You cannot ask the model to police its own inputs, because the thing you're defending against is the model being confidently wrong, and "are you sure?" is answered by the same faculty that produced the overconfident answer.

So the floor is deterministic and dumb on purpose. It counts things. It checks lengths and dates and null rates. It doesn't reason about whether the input is good — it measures whether there's enough of it, using rules I can read six months later and a threshold I picked deliberately. The value is precisely that it can't be talked out of its answer.

How I pick the threshold

Badly at first, then less badly. Here's the honest process.

Start with a guess and make it visible. I pick a number that feels defensible — say, fifteen substantive reviews — and I write it as a named constant with a comment explaining the reasoning. Not buried in a conditional. Somewhere I'll see it.

Generate at, above, and below it. Run the pipeline on a case right at the floor, one comfortably above, one below. Read all three outputs side by side. The question isn't "is the below-floor one bad" — it's often fine on the surface, that's the whole trap. The question is "is it generic — could this text describe any business in this category". If yes, the floor's too low.

Watch where it stops being about the specific thing. There's usually a fairly clear point where the output goes from "this is about this salon" to "this is about salons". That transition is roughly where the floor belongs. Slightly above it, if I'm choosing which way to err.

Leave a middle band. Between "clearly enough" and "clearly not" there's a zone where I let it run but attach a flag — a note to me, or a visible marker, saying this was built on thin input, check it before it goes out. That band matters because a hard line always cuts through some genuinely borderline cases, and a flag is cheaper than a wrong refusal.

What "stop" should look like

The refusal has to be more useful than an error. When the floor blocks something, it tells me: what was missing, how far below the line it fell, and what would clear it. "8 usable reviews, floor is 15, need 7 more or a manual override" — that's actionable. I can go ask the business for photos and a few detailed testimonials, or I can look at the 8 and decide to proceed by hand with my eyes open.

A refusal that just says "insufficient data" trains you to add an override flag and never think about it again. A refusal that tells you exactly what's short keeps you honest.

The part that's easy to skip

The floor is the least fun code in the project. It's not the model, it's not the product, it's a paragraph of counting and a constant. Nobody demos their input validation. And because a thin-input output looks completely fine, you can ship without a floor and not feel the absence for months — right up until someone relies on a confident page that was built on nothing.

I've started treating the floor as the first thing I build in anything with a model in it, before the prompt, before the UI. If I can't state what counts as enough input to run this, I don't understand the product well enough to ship it yet. The boring check is where I find that out.

This one's auto-drafted from my notes on a schedule. If a number isn't in the notes, it doesn't show up here — I'd rather leave a blank than make something up.