Topic 49

Deterministic Checks First

Assertions

Before any model grades anything, most of what matters is checkable in ordinary code. Was a lookup the first tool called? Was the refund amount inside the $150 ceiling? Did the reply cite a clause that exists, and was that clause in the passages this run actually retrieved? These are questions about a record, they answer in milliseconds, they cost nothing beyond the run that produced the record, and they give the same answer next year.

Teams reach for the judge first because judging is the interesting problem. That ordering is expensive. Of the eighteen regressions Sundry's suite caught in its first quarter, twelve were caught by assertions — two thirds of the value, for a rounding error of the cost, from checks that never drifted and never needed re-validating against a human. Build this tier first and the judge gets to spend its budget on the third that needs judgement.

Four things code can check without a model — and only the bottom one blocks
Text checks · leads, never verdicts
An amount whenever money moved, a next step whenever the ticket stays open, six forbidden phrases each of which came from an incident. Prose has more legitimate shapes than a rule can enumerate, so these report and never block — a noisy blocking check trains the team to ignore the whole tier.
Citation verification
Three lookups against records the run already produced: does the cited clause exist at the pinned version, was it among the passages this run retrieved rather than remembered, and does its scope fit this order. Making it blocking took uncited policy replies from 18% to 4%.
Assertions on the run record
The run opened with a lookup · no action the case forbids · money moved at most once · every id was seen somewhere in this run. Pure functions of a record, so one implementation covers CI, the nightly sweep and a replayed incident, and nothing breaks when a sentence improves.
Hard invariants · no band, ever
Five rules, kept short so that nobody starts overriding them: the $150 ceiling without a recorded approval, one refund per order per run, a clause returned this run behind every policy claim, no other party's data in the reply, nothing the case marks forbidden. One violation across 120 runs blocks the deploy.

Assertions on the Trace

The run record from Chapter 6 is what makes this ordinary testing rather than natural-language processing. It holds the messages as sent, every tool call with its arguments and result, the task state, the passages retrieved, and the outcome with its token counts. Assertions read that structure. Nothing here parses prose, so nothing here breaks when somebody improves a sentence.

Six assertions over one run record — no model involved, no prose parsed
def check_run(run, case):
    # every line is a fact about the record, not about the wording
    assert run.tools_called[0] in ("search_orders", "get_order")
    assert set(case.forbidden).isdisjoint(run.tools_called)
    assert len(run.calls("issue_refund")) <= 1
    for call in run.calls("issue_refund"):
        assert call.args["amount_cents"] <= 15000 or run.approval_id
        assert call.args["order_id"] in run.ids_seen   # provenance, Ch8
    for clause in cited_clauses(run.reply):
        assert clause in run.passages_returned

Read what those six assertions cover. The run opened with a lookup rather than acting on the ticket text alone. It never called a tool the case forbids — on the canonical ticket, offer_replacement, because the buyer refused one. It moved money at most once. Any refund was inside the ceiling or carried a recorded approval, and its order id had appeared somewhere in this run rather than being produced from nothing. And every clause the reply names was in the passages the retriever actually returned this run.

They are pure functions of a record, which pays off three times over. The same function runs against a stored eval run in CI, against a sampled production run in a nightly sweep, and against a replayed incident when somebody asks "would we catch this now". One implementation, three places, and no argument about whether the CI version and the production version check the same thing.

Invariants That Must Never Break

A subset of those assertions is different in kind. Invariants measure nothing about quality; they state the properties of a system that is behaving at all, and one violation across 120 runs blocks the deploy. Sundry's list is deliberately short, because a long list of blocking rules is a list somebody will start overriding.

InvariantWhat it stopsWhere it came from
No refund above $150 without a recorded approvalAn unreviewable payment out of a seller's balanceThe ceiling in Chapter 12
At most one refund per order per runThe double refund, in its second formChapter 3, and finance's reconciliation
Every policy claim names a clause returned this runA confident rule the library does not containChapter 8, uncited claims at 18% of replies
No other party's data in the replyAnother buyer's address, a card number, a seller balanceThe exfiltration surface in Chapter 12
No action the case marks forbiddenA replacement offered to a buyer who refused oneThe eval set's own forbidden list

The first row has caught nothing in production, and that is what a working invariant looks like. It fires in CI on the day somebody edits the system prompt in a way that makes the model generous, which happened once at Sundry and was a four-line diff to a paragraph about tone. Nobody had predicted that connection; the invariant did not need anybody to.

An invariant that can be overridden is a metric with a stern name. If one produces a false failure — and they do, usually because the invariant was written more narrowly than the business rule — fix the check that day rather than waving the build through. The alternative is a team that learns overriding is normal, and by the time the invariant matters it is furniture.

Citation Verification

The citation requirement from Chapter 6 exists so that a machine can check policy answers, and the check is three mechanical questions. Does the cited clause exist in the policy library at the pinned version? Was it among the passages this run retrieved, rather than remembered from training? Does its scope fit this order — a seller supplement for this seller, or a Sundry-wide rule on Sundry's own stock? All three are lookups against records the run already produced.

It is the cheapest high-value check in the suite. Making it blocking took Sundry's uncited policy replies from 18% to 4%, and the residual 4% are mostly replies that mention a rule in passing without deciding anything on it. Run it on every case in the set on every run, not on a sample — it costs nothing, and a sampled check on a rare failure is a check that reports zero for a month and then an incident.

Cheap Text Checks

A small amount of the reply itself is worth checking crudely. Required elements: an amount whenever money moved, a next step whenever the ticket is not closed, an order reference whenever one was discussed. Forbidden phrases: Sundry's list has six entries and every one came from an incident, including a promised delivery date the agent never obtained from the carrier and the word "guarantee" attached to a marketplace seller's behaviour.

Keep these non-blocking and treat them as leads rather than verdicts. They produce most of the suite's false failures, because prose has more legitimate shapes than a rule can enumerate, and a noisy blocking check trains the team to ignore the whole tier. Length bounds belong in the same category: a 900-word reply to "where is my parcel" is worth looking at, and it is not by itself wrong.

Where Deterministic Checks End

Every mechanical check can pass on a run that is wrong. The agent retrieved the seller's supplement, cited it by identifier, stayed under the ceiling, called the right tools in a defensible order — and chose a remedy the clause does not allow, or summarized the clause in a sentence the clause does not support. Chapter 8 measured that last shape at 3% of policy replies and called it the most convincing failure in the book, because the citation is real and the check is green.

That is the judgement class, 44% of Sundry's classified failures, and no assertion reaches it. Deterministic checks are not the small half of an eval suite — they are the cheap two thirds of it. But the third they cannot see contains almost every failure that costs money quietly, which is what the next topic is for.

Common Mistakes
  • Skipping straight to a model judge because it is the interesting problem — the judge costs money on every pass, drifts on every upgrade, and misses things a two-line assertion catches every single time.
  • Asserting on an exact tool sequence when the order does not matter — checking the policy before the order record is a legitimate path, the suite fails on correct runs, and within a month somebody deletes the assertion instead of fixing it.
  • Checking only the final message — the reply is a summary written by the component under test, and most of the evidence is in the tool calls, the arguments and the passages that never reached the customer.
  • Letting an invariant be advisory — a ceiling that can be overridden by whoever is on the release is not a ceiling, and the override becomes routine long before the day it matters.
Best Practices
  • Assert on the structured run record rather than on prose, so the checks survive every rewrite of the customer-facing wording.
  • Keep a short list of hard invariants, make them block unconditionally, and fix a false failure in the check the same day it appears.
  • Verify every citation automatically on every case: the clause exists, it was retrieved this run, and its scope fits this order.
  • Add an assertion for every incident that had a checkable signature, and keep the text checks non-blocking so the blocking tier stays trusted.
Comparable toolspytest assertions over the run recordJSON Schema shape checks on decision objectsGuardrails AI declarative output checksNeMo Guardrails rule layers around the replyBraintrust code scorers alongside model ones

Knowledge Check

Why build the deterministic tier before the model judge, rather than the other way round?

  • A judge takes weeks of engineering, while assertions can be written in an afternoon by one person
  • It catches two thirds of real regressions at almost no cost, and never drifts or needs re-validating
  • Assertions cover everything a judge covers, so the judge is only needed once the set exceeds 120 cases
  • The judge cannot run until the trace format is stable, and assertions are what force that format

The refund-ceiling invariant has never fired in production. What does that tell you?

  • It is dead weight and should be dropped, since a check with no findings is costing runtime for nothing
  • It is working: the deploy gate is where it fires, on changes nobody expected to touch refunds
  • The ceiling is already enforced by the dispatcher, so the invariant duplicates a control that cannot fail
  • The threshold is set too low to be reached, and a realistic ceiling would produce occasional findings

Citation verification asks three questions about a cited clause. Which failure does it still let through?

  • A clause identifier the model invented, which does not appear anywhere in the policy library
  • A real clause the model recalled from training rather than retrieving it during this run
  • A real, retrieved, correctly scoped clause attached to a sentence the clause does not support
  • A supplement belonging to a different seller than the one who shipped this particular order

Why are the forbidden-phrase and length checks kept non-blocking?

  • They produce most of the false failures, and a noisy gate discredits the checks that must block
  • They are too slow to run on every case, so they only run against the weekly human sample
  • They catch nothing that matters, and are kept only because removing them would need a review
  • The judge already covers them, so blocking on both would fail the same run for the same reason twice

You got correct