evaluating deterministic AI systems

Evaluating a Deterministic AI System: What Hundreds of Green Checks Actually Prove

A parser either returns the right graph or it does not. A model rarely returns exactly anything. Testing both the same way wastes the easy half of the job.

Somewhere inside almost every agentic system sits a piece of ordinary, boring code: a parser that reads source files and builds a graph of what calls what, a retriever that walks an index and returns the same documents for the same query. None of this samples from a probability distribution. Given the same input, it returns the same output, run after run. Yet it is common to see a team wire this exact code into the same evaluation harness built for the model: sample its output, score it with an LLM judge, track a pass rate, treat a good score as reassurance. That is a mismatch between the tool and the job, and not a small one.

A companion piece on this site, evaluating an agent when the output is not deterministic, argues that an agent's output cannot be tested with a plain assertion, because there is no single correct answer to check it against, only a wide range of acceptable ones. That argument holds, and it is easy to over-apply. Not everything wired into an agentic system inherited the model's uncertainty. Retrieval, parsing, indexing, structural analysis, schema validation: a meaningful share of the infrastructure underneath an agent is ordinary deterministic software, testable the way software has been tested for decades, with exact assertions that either hold or do not. Conflating the two, testing a parser the way you would test a model, is a specific and expensive mistake, and it runs in both directions. This piece is about drawing that line correctly, and what a large deterministic test suite actually proves once you do.

Origin: two kinds of uncertainty under one label

"AI system" now covers two computationally different things, and the industry mostly talks about them as one. The first is the model call itself: a forward pass that ends in sampling a token from a probability distribution. The second is everything wired around that call: the code that fetches what the model reads, structures what it is allowed to do, and checks what it hands back. The first is genuinely, irreducibly variable. The second usually is not, and whether it is or is not has a real, checkable answer rather than being a matter of framing.

Start with the model call, since it is worth being precise about where its uncertainty comes from. Setting temperature to zero is the standard advice for making a model "deterministic," and it does remove one source of variance: at temperature zero the model always picks the single highest-probability token instead of sampling. But production inference systems batch requests together to use the GPU efficiently, and which other requests share your batch changes the exact computation your request goes through, so the batch a request lands in is not stable from one run to the next, and a small enough shift can flip which token wins. Temperature zero narrows the model's non-determinism. It does not remove it.

That inverts the normal rule of software testing. An ordinary test that passes sometimes and fails sometimes with no code change is a bug to be hunted down, not a fact of life to design around. Martin Fowler's description of a non-deterministic test is blunt: left unquarantined, it is "a virulent infection that can completely ruin your entire test suite," because once a team learns to shrug off one red result, it stops trusting reds at all. A model call is the rare, genuine exception: its non-determinism is the nature of the computation, not a defect, and no amount of engineering discipline removes it, only narrows it.

That exception is why the scaffolding around the model deserves the opposite instinct. A parser that walks a file and emits a syntax tree applies a fixed grammar to a fixed sequence of characters, and a correctly written one produces the same tree from the same file every time, a property parsers have had since the first compilers. A dependency graph builder recording which module imports which is the same kind of computation, and a schema validator checking a tool call's arguments returns true or false, not a spectrum. Where one of these behaves non-deterministically, that is a bug, not a feature of the domain it sits inside.

This is one of the oldest ideas in software correctness, stated bluntly by the reproducible builds project: "a build is reproducible if given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts." Compilers and package managers are built on that assumption, verifiable by a third party who simply runs the process twice and diffs the result. A parser or a graph builder inside an agentic system can make the same claim.

The distinction that matters is not "is this part of an AI product" but "does this specific function have a fixed mapping from input to output." Most of what sits under an agent answers yes. The part that answers no is the part that actually calls the model.

Present: what determinism buys back

Once a component is genuinely deterministic, testing it goes back to being a discipline of proof, in the narrow sense that word can honestly carry in software. You do not sample its output and estimate a pass rate, and you do not need several trials per case, because a second trial reveals nothing the first did not already show. You write down the correct output for a given input once and assert equality. Either it holds or the build is red. This is the ordinary unit test, and it occupies the bottom, biggest layer of the test pyramid precisely because it is this cheap and this exact.

Determinism also enables stronger tools than a hand-written example, which only checks the specific inputs someone thought to type in. Property-based testing, the technique QuickCheck introduced in 2000 and now standard across most languages, generates hundreds of inputs and checks an invariant across all of them, such as a parser never emitting a node with a dangling reference, shrinking any failure to the smallest example that still breaks it. None of this is available against a model's output, because there is no invariant of the form "the summary is always exactly this" to check.

There is also a class of test that checks determinism itself, with no equivalent on the model side. Run the same parse twice and diff the two outputs: if anything differs, the component is not actually deterministic yet, whatever anyone assumed. That is an idempotency check, real and binary. Freeze a known-good output as a fixture and compare every future run against it, and that is golden-master or characterization testing: it turns "does this still work" into a diff instead of a judgment call, decided in milliseconds, without an opinion.

The economics follow directly. A judge call to grade a subjective output costs real money and latency on every run, and the judge itself has to be calibrated and rechecked as models and prompts change. An assertion against deterministic code costs a function call, and you can run tens of thousands of them against a parser on every commit for less than a handful of judge calls. Unlike the judge score, a green assertion suite is not an estimate, not "probably still correct." It is a direct, verifiable statement about this exact input and output, a categorically stronger claim than a sampled, judged evaluation can produce, for the slice of the system where it applies.

The mistake: testing a parser like a model, and a model like a parser

The expensive version of this confusion runs in both directions, worth naming both, because a team that has just learned to respect the first usually swings straight into the second.

The first direction is importing eval-harness thinking into a component that never needed it. A team builds a dependency graph builder and scores its output with an LLM judge on a rubric: does this dependency map look complete, does it look accurate. It sounds rigorous. It is actually a downgrade, because the judge introduces its own noise into a question that already has an exact answer. Known judge failure modes include position bias, where swapping the order two candidate answers are shown in flips the verdict in a meaningful share of cases, on top of the verbosity and self-preference biases that come from using a model to grade a model. Paying for that noise on a component checkable with a hash comparison is spending money to know less than a free assertion would have told you. Worse, a judge that scores a sample as "looks reasonable" nine times out of ten can still hide a reproducible bug that shows up on one input pattern every time, because a small slice of a large sample barely moves an aggregate score. An exact regression test on that input catches it every run, forever, the moment anyone reintroduces it.

The second direction is the one the companion piece on evaluating non-deterministic agents covers in depth: writing an exact-match assertion against a model's output and being surprised when a correct paraphrase fails it. Both mistakes share a root cause: treating "AI system" as one undifferentiated thing instead of asking, component by component, whether this function has a fixed mapping from input to output. A retriever built on exact graph traversal has one. A summarizer does not. Grading both the same way is not neutral: it makes the deterministic component's evidence weaker than it should be, and the non-deterministic component's evidence look more exact than it is.

The failure shows up quietest in incident review, when a bad answer ships and the postmortem has to establish where it came from: bad reasoning over correct information, or fine reasoning over a corrupted graph. If the deterministic layer is provably correct, that question gets easy to answer: check the assertions, and if the suite is green, the bug lives in the model's reasoning. Determinism where it is available is not a nice-to-have. It is what makes the rest of the system debuggable at all.

Present: where the line actually runs

Draw the boundary with real components, because "the deterministic parts" stays vague until you name them.

Parsing and structural analysis are the clearest case. A compiler front end, an incremental parser like tree-sitter, a call-graph builder, a type checker, a schema validator checking a tool call's arguments: these apply fixed rules to fixed input and have done so since long before anyone talked about agents. A static analysis pass that flags every function with no caller, or a check that a referenced ID actually exists, is either right or wrong about a given case, not persuasive or unpersuasive.

Retrieval is the case worth being careful about, because it can sit on either side of the line depending on how it is built. A retriever doing exact structural traversal, walk this call graph outward from this function, return every file within two hops, is fully deterministic: same graph, same starting point, same result. A retriever built on nearest-neighbor search over embeddings is different. Its similarity scores are usually reproducible given a fixed index and query, but the pipeline around it, what gets embedded, how the index is built, can introduce its own soft edges, which is why evaluation frameworks built for retrieval-augmented generation exist. Retrieval is not automatically deterministic just because it is not the model; whether it is depends on what it is built on.

Generation and reasoning are where genuine non-determinism lives and stays: summarizing, planning, deciding which of several plausible next steps to take, writing the response itself. None of it has a single correct output to assert against, for the reasons the companion piece sets out in full, and it needs the sampled, judged, rubric-scored harness built for exactly that problem.

This lines up with guidance from people building agents at the frontier: Anthropic's own engineering guidance recommends workflows, systems where the model and tools are orchestrated through predefined code paths, for tasks where the steps can be hardcoded, reserving open-ended agent autonomy for tasks where they cannot. Use exactly as much model as the task requires, and keep the rest on rails.

The useful reframe: an agentic system does not have one evaluation problem, it has at least two, and the deterministic one is worth solving properly precisely because the non-deterministic one is unavoidable. Every bit of avoidable uncertainty left in the substrate the model reasons over gets blamed on the model, mixed in with uncertainty that actually is the model's. Push the graph, the index, and the parsed structure as far toward provably deterministic as the computation allows, and what remains is at least honestly located: the difference between an agent that misbehaves for reasons nobody can pin down and one whose remaining unpredictability sits exactly where expected, in the model.

Future and impact: what the green checks are actually claiming

It is worth being exact about what a deterministic component proves when it passes hundreds, or thousands, of automated checks, because the honest claim is narrower than "the system is good," and the narrowness is the point. Each green check is a specific, falsifiable statement: for this input, the output matched what was specified, verified again just now. Stack enough of those together, including checks that probe beyond what anyone hand-wrote, and the aggregate claim becomes something close to "this behavior can be independently reproduced on demand, and it just was." That is not a vibe or an estimate with a margin of error. A statistical eval score, by contrast, is always an estimate: a sample, scored by a method with its own known biases, standing in for outputs too varied to check exhaustively. Both kinds of evidence are legitimate, but not the same strength, and treating a judge-graded pass rate in the low nineties as equivalent to a fully green deterministic suite understates how much stronger the second claim is.

The practical takeaway is to sort the stack before deciding how to test it. Ask, for each component, whether it has a fixed mapping from input to output. If it does, build the boring, cheap, exact suite: unit tests, property-based tests, idempotency checks, golden-master fixtures, wired into the same CI gate that has blocked a broken build for decades. If it does not, that is where the sampled, judged eval harness belongs, worth the overhead the companion piece describes, because there is no cheaper way to get honest signal out of a genuinely variable system. Defaulting to the expensive method everywhere because the product gets called AI wastes money for weaker evidence than the tools already on hand would have given for free. Defaulting to the cheap method everywhere because determinism sounds old-fashioned produces green checks that do not mean what they look like they mean.

Spiderbrain, built by Perform Digital, is one concrete example of leaning into the deterministic side on purpose. Its context graph, the map of a codebase's structure that gives a coding agent persistent memory to reason over instead of a flat, resettable context window, is built to be deterministic by design: the same codebase produces the same graph every time, independently checkable rather than asserted. Roughly 350 automated checks run against it on every release, verifying that determinism directly, in a way a generated summary from a model cannot be verified, because a summary has no single correct form to diff against. The public brain registry at spiderbrain.ai/brains lists a determinism score alongside each published brain, a direct measure of how much of that codebase was parsed deterministically versus needing inference to fill a gap, and the npx spiderbrain verify command exists specifically so anyone can check a brain's determinism rather than take it on faith. None of this replaces evaluating the model that reasons over the graph. It is the other half of the problem, built the way this piece has argued that half should be built, and how the graph itself gets built is worth its own closer look.

None of this makes determinism the harder-won half of building with models. It is the easier half, and that is the point: because it is easier, there is no excuse for getting it wrong, and no reason to spend the expensive, uncertain machinery built for model evaluation on a question a plain assertion already answers. A system built on both, tested with the method each part actually deserves, is more trustworthy than one tested with a single method stretched to cover parts it does not fit.

Council summary

This post argues that an agentic system is not one evaluation problem but at least two, and that the deterministic half, parsing, structural analysis, schema validation, and any retrieval built on exact traversal rather than approximation, deserves the plain assertion-based testing classical software has used for decades rather than the sampled, judged harness built for the model's genuine non-determinism. It draws the boundary component by component instead of by product label, is precise about where model non-determinism actually comes from, batching more than raw sampling, and names the concrete tools available once a component is provably deterministic: property-based testing, idempotency checks, golden-master fixtures. It names the costly mistake in both directions, judging a parser like a model and asserting against a model like a parser, and shows why sorting the stack correctly makes the rest of the system easier to debug. The reader's takeaway: check whether a component has a fixed mapping from input to output before choosing how to test it, and treat a fully green deterministic suite as the categorically stronger claim it actually is.

Comments

Leave a comment

Your email won't be published. Comments are reviewed before they appear.
★ Read next