{"course":{"slug":"agents-and-automation","title":"Agents and Automation","url":"https://addaly.com/learn/agents-and-automation","level":"intermediate","track":"build"},"lesson":{"slug":"validators-not-vibes","title":"The validator is the product","url":"https://addaly.com/learn/agents-and-automation/validators-not-vibes","position":35,"of":72,"minutes":9,"answer":"Check the output in code before anyone sees it: schema, then arithmetic, then every number and identifier traced back to a tool result in the same run.","keyIdea":"Check the output in code before anyone sees it: schema, then arithmetic, then every number and identifier traced back to a tool result in the same run."},"body":{"format":"markdown","text":"## An output is a claim until something checks it\n\nThe single highest-value component in a production automation is usually not the prompt, the model or the framework. It is a hundred lines of dull checking code that sits between the agent and the world.\n\nBuild the checks in this order, because they get more expensive and less reliable as you go down.\n\n> Figure: Five checks, cheapest and most reliable first. Steps: 1. Schema (Parses, required fields present, types right, enums inside their sets. Free and instant.); 2. Arithmetic and invariants (Line items sum to the total; the discount is not larger than the subtotal. Two lines each, and never an off day.); 3. Trace every value to a tool result (Each id, figure and quotation appears somewhere in this run's results. Exempt derived fields by name.); 4. Business rules (Refund never exceeds amount paid. Usually the first time these are written down anywhere.); 5. A model as judge, last (For 'is this polite and on topic', which no rule expresses. Slower, dearer, and it agrees with fluent nonsense.). When a check fails: hand the message back to the model once, then a human queue with the failed check named, then fail closed for anything touching money. Never a silent default.\n\n## 1. Schema\n\nDoes it parse? Are the required fields present? Are the types right? Are enums within their sets?\n\nFree, instant, catches a surprising amount. Structured output modes make malformed JSON rare, but \"valid JSON with `total` as the string `unknown`\" is still very much on the menu.\n\n## 2. Arithmetic and invariants\n\nThe checks a competent clerk would do:\n\n- line items sum to the stated total, within one unit of rounding\n- the discount is not larger than the subtotal\n- the delivery date is not before the order date\n- quantities are non-negative integers\n- the currency on the invoice matches the currency on the account\n\nEach is two lines. Together they catch most extraction errors, and unlike anything model-based they never have an off day.\n\n## 3. Trace every value back to a tool result\n\nHere is the rule that catches the most fabrication, and it is cruder than you would expect:\n\n**Every identifier, figure and quotation in the output must appear in some tool result from the same run.**\n\nCollect the text of every tool result into one string. For each claimed value in the output, check that it is present. Exact substring for ids and quotations; normalised comparison for numbers, since 2499 may appear as ₹2,499.00.\n\nIt is blunt. It has false alarms: a legitimately computed sum will not appear anywhere. So exempt derived fields explicitly, by name, and check them with rule 2 instead. What remains catches invented order numbers, invented citations and invented quotations at a rate that will surprise you.\n\n## 4. Business rules\n\nRefund never exceeds amount paid. No discount above 30% without approval. Never email an address not on the account. These are the rules that exist in somebody's head; the validator is where they get written down for the first time, which is often the most valuable side effect of building one.\n\n## 5. A model as judge, last\n\nUseful for \"is this reply polite and on-topic\", which no rule expresses. Expensive, slower, and it has its own failure modes, including agreeing with fluent nonsense. Put it last, never let it be the only check, and see the evaluation course for how to check the judge.\n\n## What to do when a check fails\n\nThree options, in order:\n\n1. **Return the validator's message to the model as an observation** and let it try once. `The line items sum to 2,340 but you reported 2,490. Recheck the items.` This works well.\n2. **Route to a human queue**, with the failed check named. This is what queues are for.\n3. **Fail closed**, no output at all, for anything money-related.\n\nNever drop a failure silently, and never fall back to a default.\n\n## Retry once, then stop\n\nSecond retries rarely help, and the mechanism is worth knowing: the failed attempt is now in the context, and models tend to produce variations on the shape they just produced. Two failed validations means something structural: a bad tool result, an ambiguous instruction, or a task the model cannot do. That is a human's problem, not a third attempt's.\n\n## The honest limit\n\nA validator catches what you thought of. It cannot catch a well-formed, plausible, internally consistent, wrong answer that satisfies every rule you wrote.\n\nThat is not a reason to skip it. It is the reason the human queue and the golden set exist, and why the last question in every design review is: *what would a wrong answer look like that all of this passes?*"},"check":{"question":"An extraction agent reads invoices and outputs supplier name, invoice number, line items and total. Which single check catches the largest share of real errors for the least code?","options":[{"text":"A second model call asking whether the extraction looks correct.","correct":false,"tempting":"The most expensive and least reliable check, placed first. It reads the same document and often agrees with a confident, wrong extraction."},{"text":"A confidence score from the model, with anything under 0.9 sent to a human.","correct":false,"tempting":"Gates on self-assessed confidence, which is uncorrelated with correctness and highest exactly when the model is fluently wrong."},{"text":"A regular expression on the invoice number format.","correct":false,"tempting":"Cheap and worth having, but it validates the shape of one field while saying nothing about whether any value is right."},{"text":"Checking that the line items sum to the stated total, with the invoice number and supplier name both present verbatim in the document text.","correct":true}]},"prev":{"slug":"retries-and-idempotency","title":"Retries, timeouts and doing it twice","url":"https://addaly.com/learn/agents-and-automation/retries-and-idempotency"},"next":{"slug":"testing-with-replay","title":"Testing something that is not deterministic","url":"https://addaly.com/learn/agents-and-automation/testing-with-replay"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'The validator is the product', in Agents and Automation. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/agents-and-automation/validators-not-vibes (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"The validator is the product","courseTitle":"Agents and Automation","module":{"index":4,"slug":"making-it-reliable","title":"Surviving real data"},"answer":null,"keyIdea":"Check the output in code before anyone sees it: schema, then arithmetic, then every number and identifier traced back to a tool result in the same run.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure","figure":{"type":"flow","steps":[{"label":"Schema","detail":"Parses, required fields present, types right, enums inside their sets. Free and instant."},{"label":"Arithmetic and invariants","detail":"Line items sum to the total; the discount is not larger than the subtotal. Two lines each, and never an off day."},{"label":"Trace every value to a tool result","detail":"Each id, figure and quotation appears somewhere in this run’s results. Exempt derived fields by name."},{"label":"Business rules","detail":"Refund never exceeds amount paid. Usually the first time these are written down anywhere."},{"label":"A model as judge, last","detail":"For ‘is this polite and on topic’, which no rule expresses. Slower, dearer, and it agrees with fluent nonsense."}],"title":"Five checks, cheapest and most reliable first","caption":"When a check fails: hand the message back to the model once, then a human queue with the failed check named, then fail closed for anything touching money. Never a silent default."},"alt":"Figure: Five checks, cheapest and most reliable first. Steps: 1. Schema (Parses, required fields present, types right, enums inside their sets. Free and instant.); 2. Arithmetic and invariants (Line items sum to the total; the discount is not larger than the subtotal. Two lines each, and never an off day.); 3. Trace every value to a tool result (Each id, figure and quotation appears somewhere in this run’s results. Exempt derived fields by name.); 4. Business rules (Refund never exceeds amount paid. Usually the first time these are written down anywhere.); 5. A model as judge, last (For ‘is this polite and on topic’, which no rule expresses. Slower, dearer, and it agrees with fluent nonsense.). When a check fails: hand the message back to the model once, then a human queue with the failed check named, then fail closed for anything touching money. Never a silent default."}},"blocks":[{"t":"h2","id":"an-output-is-a-claim-until-something-checks-it","runs":[{"text":"An output is a claim until something checks it"}]},{"t":"p","runs":[{"text":"The single highest-value component in a production automation is usually not the prompt, the model or the framework. It is a hundred lines of dull checking code that sits between the agent and the world."}]},{"t":"p","runs":[{"text":"Build the checks in this order, because they get more expensive and less reliable as you go down."}]},{"t":"h2","id":"1-schema","runs":[{"text":"1. Schema"}]},{"t":"p","runs":[{"text":"Does it parse? Are the required fields present? Are the types right? Are enums within their sets?"}]},{"t":"p","runs":[{"text":"Free, instant, catches a surprising amount. Structured output modes make malformed JSON rare, but “valid JSON with "},{"text":"total","code":true},{"text":" as the string "},{"text":"unknown","code":true},{"text":"” is still very much on the menu."}]},{"t":"h2","id":"2-arithmetic-and-invariants","runs":[{"text":"2. Arithmetic and invariants"}]},{"t":"p","runs":[{"text":"The checks a competent clerk would do:"}]},{"t":"ul","items":[[{"text":"line items sum to the stated total, within one unit of rounding"}],[{"text":"the discount is not larger than the subtotal"}],[{"text":"the delivery date is not before the order date"}],[{"text":"quantities are non-negative integers"}],[{"text":"the currency on the invoice matches the currency on the account"}]]},{"t":"p","runs":[{"text":"Each is two lines. Together they catch most extraction errors, and unlike anything model-based they never have an off day."}]},{"t":"h2","id":"3-trace-every-value-back-to-a-tool-result","runs":[{"text":"3. Trace every value back to a tool result"}]},{"t":"p","runs":[{"text":"Here is the rule that catches the most fabrication, and it is cruder than you would expect:"}]},{"t":"p","runs":[{"text":"Every identifier, figure and quotation in the output must appear in some tool result from the same run.","b":true}]},{"t":"p","runs":[{"text":"Collect the text of every tool result into one string. For each claimed value in the output, check that it is present. Exact substring for ids and quotations; normalised comparison for numbers, since 2499 may appear as ₹2,499.00."}]},{"t":"p","runs":[{"text":"It is blunt. It has false alarms: a legitimately computed sum will not appear anywhere. So exempt derived fields explicitly, by name, and check them with rule 2 instead. What remains catches invented order numbers, invented citations and invented quotations at a rate that will surprise you."}]},{"t":"h2","id":"4-business-rules","runs":[{"text":"4. Business rules"}]},{"t":"p","runs":[{"text":"Refund never exceeds amount paid. No discount above 30% without approval. Never email an address not on the account. These are the rules that exist in somebody’s head; the validator is where they get written down for the first time, which is often the most valuable side effect of building one."}]},{"t":"h2","id":"5-a-model-as-judge-last","runs":[{"text":"5. A model as judge, last"}]},{"t":"p","runs":[{"text":"Useful for “is this reply polite and on-topic”, which no rule expresses. Expensive, slower, and it has its own failure modes, including agreeing with fluent nonsense. Put it last, never let it be the only check, and see the evaluation course for how to check the judge."}]},{"t":"h2","id":"what-to-do-when-a-check-fails","runs":[{"text":"What to do when a check fails"}]},{"t":"p","runs":[{"text":"Three options, in order:"}]},{"t":"ol","start":1,"items":[[{"text":"Return the validator’s message to the model as an observation","b":true},{"text":" and let it try once. "},{"text":"The line items sum to 2,340 but you reported 2,490. Recheck the items.","code":true},{"text":" This works well."}],[{"text":"Route to a human queue","b":true},{"text":", with the failed check named. This is what queues are for."}],[{"text":"Fail closed","b":true},{"text":", no output at all, for anything money-related."}]]},{"t":"p","runs":[{"text":"Never drop a failure silently, and never fall back to a default."}]},{"t":"h2","id":"retry-once-then-stop","runs":[{"text":"Retry once, then stop"}]},{"t":"p","runs":[{"text":"Second retries rarely help, and the mechanism is worth knowing: the failed attempt is now in the context, and models tend to produce variations on the shape they just produced. Two failed validations means something structural: a bad tool result, an ambiguous instruction, or a task the model cannot do. That is a human’s problem, not a third attempt’s."}]},{"t":"h2","id":"the-honest-limit","runs":[{"text":"The honest limit"}],"role":"limit"},{"t":"p","runs":[{"text":"A validator catches what you thought of. It cannot catch a well-formed, plausible, internally consistent, wrong answer that satisfies every rule you wrote."}]},{"t":"p","runs":[{"text":"That is not a reason to skip it. It is the reason the human queue and the golden set exist, and why the last question in every design review is: "},{"text":"what would a wrong answer look like that all of this passes?","i":true}]}],"check":{"question":[{"text":"An extraction agent reads invoices and outputs supplier name, invoice number, line items and total. Which single check catches the largest share of real errors for the least code?"}],"options":[{"text":[{"text":"A second model call asking whether the extraction looks correct."}],"correct":false,"tempting":[{"text":"The most expensive and least reliable check, placed first. It reads the same document and often agrees with a confident, wrong extraction."}]},{"text":[{"text":"A confidence score from the model, with anything under 0.9 sent to a human."}],"correct":false,"tempting":[{"text":"Gates on self-assessed confidence, which is uncorrelated with correctness and highest exactly when the model is fluently wrong."}]},{"text":[{"text":"A regular expression on the invoice number format."}],"correct":false,"tempting":[{"text":"Cheap and worth having, but it validates the shape of one field while saying nothing about whether any value is right."}]},{"text":[{"text":"Checking that the line items sum to the stated total, with the invoice number and supplier name both present verbatim in the document text."}],"correct":true,"tempting":null}],"order":[0,3,1,2],"why":null},"prev":{"slug":"retries-and-idempotency","title":"Retries, timeouts and doing it twice","minutes":8},"next":{"slug":"testing-with-replay","title":"Testing something that is not deterministic","minutes":9}}}