A conversation about the only hard part of building a loop.
What actually counts as a check?
One rule: it can fail on its own, without asking you and without asking the AI.
That’s it. If a person or a model has to look at the result and form an opinion, it isn’t a check. It’s the same reading job you were trying to get rid of, wearing a different hat.
My task doesn’t have tests. Now what?
Most tasks have a check hiding in them. You just described the task in words instead of in a command.
Here are ones people actually say, and what they turn into:
| What you say | The check hiding in it |
|---|---|
| “make sure it still compiles” | tsc --noEmit, scoped to that file |
| “don’t break anything” | that file’s tests, or the build |
| “make sure the tag is there” | grep -q '<meta name="description"' "$FILE" |
| “every row should have a price” | count rows where the price column is empty, assert zero |
| “the links should work” | request each one, assert the status is under 400 |
| “make sure it’s really a photo” | read the first bytes and compare to the extension |
| “all of them, not just some” | count the list before and after, assert they match |
| “match the old behaviour” | run both, diff the output |
| “don’t make things up” | save the source, then check every value appears in it |
Notice the pattern. Each one reads something back out of the finished work, a byte, a count, a status code, a string, and compares it to a rule you set before you started.
None of them ask an AI whether it feels done.
What about the last one? How do you check for made up stuff?
You save what it read.
This is the one people skip and it’s the most valuable. An AI reads a page, writes structured data, and throws the page away. Now nothing on earth can tell the difference between “extracted” and “invented.”
So keep the page:
.sources/invoice-4412.txt
Then assert that every value in the output appears in there. Be a bit generous about it, because reformatting is normal and invention isn’t:
// exact match after normalizing, or at least 70% of the
// meaningful words present
function appearsIn(value, source) {
const v = norm(value)
if (source.includes(v)) return true
const words = v.split(' ').filter((w) => w.length >= 3)
return words.filter((w) => source.includes(w)).length / words.length >= 0.7
}
That catches a supplier name that was never on the invoice. It won’t catch a real number copied into the wrong column. Worth knowing which one you’re buying.
What if I’m generating text? There’s no right answer.
Pull out the parts that do have right answers first. There are usually more than you’d think.
For a product description you might check: it names the actual product, it’s under 40 words, it doesn’t contain the phrase “elevate your,” it mentions at least two attributes from the spec sheet, and every number in it appears in the spec sheet.
None of that tells you the copy is good. All of it fails on copy that’s wrong, which is the failure that actually costs you.
Then, for whatever’s left, you can have three separate AI judges each try to prove the result is wrong, and accept it only if most of them can’t. That’s weaker than a real check and you should say so out loud when you use it. Not for anything about health, money, or safety.
What are the checks that look real but aren’t?
Four common ones.
“The AI confirms it’s done.” The failure mode you’re worried about is the AI being wrong and confident. Asking it is asking the suspect.
“The output isn’t empty.” Passes on garbage. Passes on an error message. Passes on the word “done.”
“It didn’t throw an error.” This is the sneakiest one, because it feels rigorous. Here’s the specific case that taught me: curl will happily save a 403 page as photo.jpg and exit 0. The file exists, it’s the right size to look plausible, the script reported success, and the image is an HTML error page.
The question to ask about any check is not “did it crash.” It’s “what does a successful wrong result look like, and would this notice?”
“An LLM scored it 8 out of 10.” Uncalibrated, drifts between runs, and tends to agree with itself. If you must use a model, ask it to refute rather than to score. Refuting is a harder task and produces more honest failures.
How do I know my check actually works?
Run it against work you already finished.
This is the single best thing you can do and it takes ten minutes. Point the check at output from before the loop existed. Every failure it reports is a free bug report about work you already shipped.
Two things happen. You find real problems. And you find out your check is too strict, because it flags 400 things that are fine.
When that happens, do not just accept the number. Pull up five of the failures and read them. On the first run of a check I wrote recently, it reported 904 broken images. Reading five of them showed they were all SVG logos, which are perfectly valid images that my byte check didn’t know about. Real count after the fix: three. Those three were genuinely broken.
If I’d trusted the 904, I’d have “fixed” 901 files that were fine.
Anything else?
Feed it something you know is bad and confirm it fails.
A check that has never failed has never been tested. I shipped one that used a grep flag my machine didn’t support. The error was hidden, the exit code came back clean, and it reported PASS on every single run for days. It was never checking anything.
Watch your check say no once. Then you can trust the yes.