2026-05-28 · verification · from Naulus

Never let the LLM author the numbers

A hallucinated macro total is a lie someone eats. Every number in my systems comes from plain code, and the model only chooses between validated options.

There's a moment in every AI product where a number needs to appear on screen: calories in a meal plan, a setback distance in a zoning answer, a confidence score. The temptation is to let the model produce it, because the model's right there, it already understands the context, and it'll happily oblige. Don't.

A model generating "1,847 calories" isn't calculating, it's producing the kind of token that tends to follow the tokens before it. Often the result is close, and close is worse than wrong, because close passes review. A macro total that's off by 40% gets caught. One that's off by 8% gets eaten, every day, by someone trusting your product to manage that number.

The pattern: models choose, code computes

In my fitness app, the meal engine works like this. Every food item lives in a database with verified nutrition facts. Every quantity is a structured field. When a meal is assembled, plain code sums the macros: the same inputs produce the same totals, forever, and a unit test can prove it.

The model still does real work. It interprets "swap the rice for something lower carb that still gets me to 40 grams of protein," finds candidate substitutions, and proposes them. But every proposal has to reference a real item with real numbers, or the mutation layer rejects it.

# The shape of the pattern, not production code.

def meal_totals(items):
    # plain code authors every number: same inputs, same totals, forever
    return sum(db.nutrition_facts(i.food_id) * i.quantity for i in items)

# the model chooses between validated options, it never writes a number
swap = model.propose_swap("lower carb, still 40g protein", candidates)
if swap.food_id not in db:
    reject(swap)  # the mutation layer throws out anything that isn't real

The same split shows up in everything I build now. In the zoning checker, the by-law thresholds come from extracted, verified tables, and the model assembles the explanation around numbers it can't invent. In the research pipeline, effect sizes and sample sizes are extracted fields validated against the source, never summarized into existence. Anywhere a total, a date, a price, or a threshold appears, there's a deterministic function with its own tests behind it.

Not a patch until models get better

It's tempting to treat this as a temporary workaround until models get better at arithmetic. I think that misreads the problem. Even a model that computes perfectly is still a probabilistic system, and a property that has to hold every time can't rest on a system that's allowed to vary. The question that matters is what enforces the invariant when the model has a bad day.

Drawing the line between choosing and computing also makes the system easier to reason about. When a user reports a wrong number, I know where to look, and it's never a prompt. When the model proposes something invalid, the rejection gets logged and the failure is cheap. The worst case is an assistant that's less helpful for a day. The numbers stay right.

New essays when the work ships. No noise.