When There Is More Than One Correct Answer: How Do You Test Non-Deterministic Software?
Share this post

An enterprise support agent receives one instruction:

"Find the duplicate invoice for Account 4821, verify that it qualifies under the billing policy, and prepare the appropriate resolution."

The agent has access to five tools: account lookup, invoice history, policy search, support ticket history, and billing actions. The instruction is run five times. Nothing about the environment is deliberately changed between runs. The account data is the same. The policy is the same. The model is the same. And yet the five executions look like five different programs.

RUN A

lookup_account(4821)
list_invoices(4821)
identify_duplicate(INV-91, INV-88)
retrieve_policy("duplicate_charge")
validate_eligibility(INV-91, policy)
prepare_resolution(credit, INV-91)

RUN B

lookup_account(4821)
retrieve_policy("duplicate_charge")
list_invoices(4821)
identify_duplicate(INV-91, INV-88)
validate_eligibility(INV-91, policy)
prepare_resolution(credit, INV-91)

RUN C

lookup_account(4821)
list_invoices(4821)
search_support_history(4821)
retrieve_policy("duplicate_charge")
identify_duplicate(INV-91, INV-88)
prepare_resolution(credit, INV-91)

RUN D

search_invoice_globally("INV-91")
find_similar_account("Northwind Logistics")
retrieve_invoice(INV-91)
prepare_resolution(credit, INV-91)

RUN E

lookup_account(4821)
list_invoices(4821)
retrieve_policy("duplicate_charge")
retrieve_policy("duplicate_charge")
list_invoices(4821)
validate_eligibility(INV-91, policy)
prepare_resolution(credit, INV-91)

Every one of these traces is different at the level of exact sequence. Three of them — A, B, and C — are straightforwardly acceptable: the agent verifies the account, finds the correct invoice pair, retrieves the applicable policy, checks eligibility, and prepares a resolution against the right record. The order in which invoice history and policy retrieval happen doesn't matter to the business; both are read operations, and either can legitimately come first. Run C does something neither A nor B does — it also checks the support ticket history — and this doesn't make it wrong, because nothing about the task forbids gathering a bit more context before acting.

Run E reaches the same endpoint as A, B, and C. It also retrieves the policy twice and re-lists the invoices for no evident reason. It is not incorrect in the sense that matters most: the resolution it prepares is against the correct invoice, for the correct account, following a policy check that did occur. But it does almost twice the work to get there, and if this pattern showed up across a large fraction of production traffic, it would show up on a cost dashboard and a latency dashboard before anyone noticed it in a correctness report.

Run D is the dangerous one. It reaches for search_invoice_globally, a tool that searches invoice numbers across every account in the system rather than looking within a single verified account. It finds an invoice with a similar-looking ID, then works backward to a "similar account" by name, then prepares a resolution. If invoice INV-91 happens to exist under a different account entirely — a plausible outcome in any invoicing system where numbering isn't strictly per-account — Run D issues a credit against an account that was never verified in the first place. Depending on how the data lines up, Run D might even produce output text that reads identically to Run A's output text. A test that only checks the final message the agent returns to the user would not be able to tell these two runs apart.

This is the actual engineering problem this article is about. Not "AI is unpredictable," which is true but unhelpful, and not "testing needs to be more flexible," which is true but says nothing about how. The problem is precise: when a system can reach a valid result through more than one route, and can also reach an invalid result that looks like a valid one, what does a test actually assert? What has to be true, what is allowed to vary, and what must never happen — and how do you write that down in a way a machine can check on every run, not just the one someone happened to look at closely?

The rest of this piece works through that question directly. It does not open with a definition of non-determinism and work outward from there. It starts from the five traces above, and it keeps returning to them, because the traces are the argument. By the end, the goal is that Run A, B, and C should obviously pass; Run D should obviously fail; and Run E should produce a result that is more interesting than pass or fail — because binary pass/fail is often not the right shape for the answer.

What "correct" doesn't mean here

Before going further it's worth being exact about what "no single correct output" does and doesn't mean, because the phrase gets used loosely.

It does not mean the system's behavior is random. A large language model sampling from a probability distribution over tokens is not the same thing as a system whose behavior can't be reasoned about. Model sampling is one source of variation among several, and even that source can be made narrower — lower temperature, fixed seeds where the provider supports them, constrained decoding for structured fields — without ever reaching true determinism, because the surrounding system introduces variation of its own.

It's useful to separate three sources of variation that get lumped together under "AI is non-deterministic":

Model variability. The model itself may sample different tokens across runs even at identical temperature and prompt, may be routed to a different underlying checkpoint by the provider without notice, may behave differently after a provider-side model update, and may plan a task differently because "planning" for an LLM agent is itself a generative act, not a lookup.

Environment variability. The account lookup tool might return invoices in a different order because of database read replicas. A retrieval index might return a slightly different top-k result set because of near-tied relevance scores. A UI element the agent is operating might render with a loading spinner some of the time and not others, depending on network conditions. An external API the agent calls might be slow on one run and fast on another, changing whether a timeout branch gets triggered. None of this is about the model being probabilistic; it's about the world the model operates in being only partially predictable, which was already true of the deterministic software calling those same APIs.

Workflow variability. Given the same environment and the same model, an agent's own planning process can still legitimately choose a different valid order of operations — check policy before invoices, or invoices before policy — because the task specification under-constrains the order, not because anything is broken.

These three sources compound. A test that treats "the trace differs" as evidence of a defect is conflating all three, and in practice it's usually workflow and environment variability, not model randomness, that produces most of the divergence a QA team actually observes in a support agent's logs.

It's also worth stating plainly that most production agent systems are not "AI software" in some pure sense. They are ordinary software — API calls, database transactions, authorization checks, state machines, retry logic — with one or more LLM calls embedded in the control flow. The account lookup, the invoice list, the billing action: these are deterministic operations with deterministic contracts. Only the planning and the natural-language interpretation layered on top of them is probabilistic. A testing strategy that treats the entire system as an undifferentiated non-deterministic blob throws away the deterministic guarantees that are still available and cheap to check. Most of what follows in this article is about using deterministic assertions everywhere correctness can be expressed deterministically, and reserving flexible, semantic, or statistical evaluation for the genuinely variable parts.

The correctness envelope

The organizing idea for the rest of this article is something we'll call the correctness envelope. This is not an industry standard or a formal specification language — it's an explanatory model, useful for reasoning about the problem, that this article will use consistently from here on.

The starting intuition: instead of specifying one exact execution that a system must reproduce, specify the boundaries within which many different executions are acceptable. An execution is correct if it stays inside those boundaries, regardless of the specific path it took to do so. It fails the moment it crosses one.

The envelope has several independent dimensions, and a given task's envelope is defined by giving content to each of them:

  • Required outcome — what must be true about the final result.
  • Required states — which milestones the execution must pass through, in whatever order the task allows.
  • Required facts — which specific values (account ID, invoice ID, amount) must appear correctly.
  • Valid transitions — which state-to-state moves are legitimate, and which orderings between states are mandatory versus free.
  • Business invariants — properties that must hold at every point in the execution, not just at the end.
  • Forbidden actions — things that must never happen, regardless of the final outcome.
  • Side-effect constraints — limits on what the execution is allowed to change in the world, and how many times.
  • Authorization constraints — who or what is allowed to trigger a given action, under what conditions.
  • Latency and cost limits — bounds on how expensive, in time or money, an acceptable execution is allowed to be.
  • Quality thresholds — for any natural-language or generated content, the minimum bar for accuracy, completeness, and tone.

An execution can vary freely along any dimension the task doesn't constrain, and must stay within bounds on every dimension it does. Run A, B, and C differ in their action order but agree on required outcome, required states, and forbidden actions — so they're all inside the envelope. Run D reaches the required outcome's surface appearance (a resolution gets prepared) but violates a required state (the account was never actually verified before the state-changing action) — so it's outside the envelope regardless of what its final message says. Run E stays inside the correctness envelope on every dimension except cost and step-count, which is exactly why it deserves a different verdict than either "pass" or "fail" alone can express.

This framework will organize nearly everything from here forward: which parts of a trace are essential, which are optional, how paths converge, what an invariant is, what a forbidden state is, and how a test result should be reported when a run is correct on some dimensions and marginal on others.

Outcome is only one dimension, and treating it as the only one is a mistake

There's a tempting simplification once you accept that paths can vary: "if the final output is right, the execution was correct." This is wrong often enough, and dangerously enough, that it deserves to be addressed directly before moving on.

Consider four ways an agent can produce a correct-looking final answer while doing something unacceptable to get there:

It successfully prepares a refund, but along the way it queried a different tenant's account data to build context, even though that data was never surfaced in the final output. The refund is correct. The access wasn't authorized.

It temporarily changes an account's status field to bypass a validation check, performs the action, and changes the status back before returning. Every state read at the beginning and the end of the trace looks normal. The audit log of intermediate states does not.

It sends a snippet containing a customer's card-linked billing details to an external summarization tool that was never designed to receive sensitive fields, in order to generate a cleaner internal note. The final response to the customer is fine. The data handling wasn't.

It retries a financial action three times because the first two attempts appeared, from the agent's perspective, to fail silently — when in fact all three succeeded, and the customer's account was credited three times before someone downstream deduplicated the ledger entries.

In every one of these cases, an outcome-only test — "does the final state show a correctly issued credit for the correct amount?" — passes. In every one of these cases, the execution was not correct. This is why validation of non-deterministic systems generally needs to examine both the outcome and properties of the path itself, without needing to pin down one exact path. The path doesn't have to be identical to a reference trace to be inspectable; it has to be checkable against a set of properties that don't care about exact sequence but do care about specific things never happening and specific things always happening.

Four ways an execution can be correct — and fail

It helps to break "correctness" into components that can be evaluated somewhat independently, because a single pass/fail signal collapses information a team actually needs. This article will use four (occasionally five) categories, again as an engineering model rather than a formal taxonomy:

Outcome correctness. Did the task produce the intended final result — the right invoice identified, the right resolution type chosen, the right amount computed?

State correctness. Did the execution actually pass through the states that had to occur for the outcome to be real, rather than merely producing text that claims those states occurred?

Constraint correctness. Did the execution avoid everything that was explicitly or implicitly prohibited — wrong-tenant access, unauthorized writes, duplicate financial actions?

Process quality. Was the path taken reasonably efficient, safe, and proportionate to the task, even if it didn't violate any hard constraint?

A fifth, worth naming separately when the task involves any kind of judgment or claim:

Evidence correctness. Was the conclusion the agent reached actually supported by the data it retrieved, or did it assert something the retrieved evidence doesn't back up?

Applying these four to the opening traces sharpens the picture. Run A: passes outcome, state, constraint, and process quality. Run D: passes outcome correctness in the narrowest sense (a resolution was prepared, an amount was computed) but fails state correctness (account verification never happened against the actual target account) and very likely fails constraint correctness (a cross-tenant or cross-account write occurred without the prerequisite verification step). Run E: passes outcome, state, constraint, and evidence correctness, but fails or at minimum triggers a warning on process quality, because four redundant tool calls for a task that needs two is a real, measurable inefficiency even though it isn't a safety problem.

The reason to keep these separate rather than folding them into one score is that they call for different kinds of oracle, they have different business consequences when violated, and they should often gate a release differently. A regression in process quality might be worth tracking and fixing without blocking a deploy. A regression in constraint correctness should probably block the deploy outright. Collapsing all four into a single "quality score" makes that distinction invisible.

Essential states, optional variations, and where the line actually is

A concept that turns out to matter a great deal in practice is the distinction between a state that is essential to a task's correctness and one that is merely present in a particular recorded trace. This distinction is easy to state and surprisingly easy to get wrong in implementation, because the natural first instinct is to define "essential" as "everything that showed up in the reference run I happened to record." That definition is exactly backwards, and getting it wrong is one of the most common causes of test suites that generate false failures on every legitimate variation an agent produces.

An essential state is not "every state observed in a successful reference trace." It is a state whose presence is logically necessary for the outcome to be considered real — a state that has to occur, in some order relative to the others, for the claimed result to actually hold.

Take the billing scenario. Whether invoice search happened before or after policy search is not essential; either order is fine, and a validator that insists on one specific order will fail traces that are functionally identical to a passing one. Whether the agent additionally checked support ticket history, as Run C did, is not essential; it's a reasonable elaboration, not a requirement. Which specific tool call retrieved account identity — a direct ID lookup versus a name search followed by disambiguation — is not essential, as long as the identity that comes out the other end is verified and correct.

What is essential: that account identity gets verified at all, and that it's verified as the specific account the customer is asking about, before any state-changing action happens. That the specific invoice identified as a duplicate is actually a duplicate — same account, matching amount, adjacent or matching transaction window — not merely an invoice with a similar ID. That eligibility gets checked against the actual retrieved policy text, not asserted without evidence. That the resolution ultimately prepared targets the verified account and the verified invoice, not a plausible-looking substitute.

The same distinction generalizes cleanly outside billing. For an agent editing a source file, which editor panel was open, whether a file tree was expanded, and how many times a search box was focused are optional; the file that exists on disk after the run, containing the correct change, is essential. For a UI automation task, a loading spinner appearing for two seconds versus not appearing at all is optional; landing on the results screen that actually reflects the requested query is essential.

Optional states matter for a very concrete economic reason: treating every observed state as mandatory produces false negatives, and false negatives are not a harmless conservative default. Every false negative that reaches a human is an investigation that starts from the assumption something is broken, consumes an engineer's attention, gets traced back to "oh, this was just a loading screen that took longer than usual," and gets closed with no code change. Do this often enough and two things happen: the pipeline becomes noisy enough that people start ignoring its alerts, and the team starts treating flaky-looking agent tests as inherently untrustworthy — which then discourages writing more of them, which is the opposite of what should happen. The cost of over-specifying essential states isn't abstract; it shows up directly in reruns, in triage time, and in whether anyone trusts the green checkmark.

Convergent paths are not the same thing as identical paths

Related to the essential/optional distinction, and easy to conflate with it, is the idea of convergence: different executions can diverge for a while and then rejoin at the same required state, after which their subsequent required behavior can be identical.

In the billing example: one path to a verified account is a direct search by account ID. Another is a search by company name, which returns two candidates because two divisions of the same customer share a name prefix, followed by a disambiguation step that picks the correct one based on the invoice number mentioned in the original request. Both paths — different tool calls, different number of steps, different intermediate states — arrive at the same place: a verified, specific account identity. From that point forward, whatever the task requires next applies identically regardless of which route got the agent there.

This is a meaningfully different idea from "record one golden trace and replay it." Replay testing assumes the recorded trace is the path and treats deviation as failure by definition. Convergence-aware validation assumes there may be several legitimate paths to the same checkpoint, and that the validator's job is to recognize when a divergent path has, in fact, rejoined the required trajectory — not to demand that it never diverged in the first place. Getting this right is what separates a validator that tolerates a company-name search from one that only accepts an ID lookup because that's what happened to be in the recorded example.

Executions as graphs, not scripts

Once essential states, optional variations, and convergence are on the table, the natural representation for an execution stops being a line and starts being a graph.

A single execution as a script is a strict sequence: step 1, then step 2, then step 3. This representation cannot express "either of these two branches is fine" or "these two paths reconverge here" without contorting itself into a combinatorial pile of separate linear scripts, one per acceptable variation — which is exactly what teams end up doing when they try to hand-author enough test cases to cover legitimate agent variability, and exactly why that approach doesn't scale past a handful of variations before someone gives up and either deletes half the tests or stops trusting the suite.

A graph representation handles this naturally. Nodes represent observable states — a business milestone like "account verified," a system state like a database row's value, a UI screen, a tool's returned result. Edges represent the actions or transitions that move the execution from one state to the next. A conceptual sketch of the billing scenario's graph:

START
  ├── search_by_id ─────────────┐
  │                             ↓
  ├── search_by_name → disambiguate → VERIFIED_ACCOUNT
  │                             ↑
  └── recent_accounts_list ─────┘
                                ↓
                          FIND_DUPLICATE_INVOICE
                                ↓
                          CHECK_POLICY_ELIGIBILITY
                                ↓
                          PREPARE_RESOLUTION

Branching captures the fact that multiple entry paths exist toward the same checkpoint. Convergence captures the fact that they rejoin. A graph can represent optional detours — the support-history lookup Run C performs — as a side loop that leaves and returns to the main trajectory without altering it, rather than as a deviation the validator has never seen and therefore rejects.

This is not a hypothetical modeling choice; it's the direction production engineering teams working on this exact problem have converged on. GitHub's engineering team, writing about validating GitHub Copilot's coding agent when it operates via "Computer Use" against a live IDE, described the core failure mode this way: a build that's green one day and red the next with no code change, because a loading screen persisted for a few extra seconds and the agent correctly waited it out — the agent didn't fail, the validation did. Their response was to stop modeling executions as scripts and start modeling them as directed graphs they call Prefix Tree Acceptors, built by merging multiple successful traces, where nodes are observable states and edges are the actions that connect them. That representation is what makes it possible to ask a structural question — "did this new trace pass through the required checkpoints in a valid order?" — instead of a much weaker and much more brittle one — "does this new trace match the recorded trace character for character?"

Deciding when two states are the same state

Before any graph merging or structural comparison can happen, there's a prior question that turns out to be the genuinely hard part: given two observations — two screenshots, two API responses, two tool outputs — are they the same logical state, or different ones?

This sounds like it should be simple and isn't. A screenshot of a results page taken at 10:03:14 and another taken at 10:03:19 differ in a visible timestamp and are otherwise identical — same logical state. A screenshot showing three search results and one showing two search results plus an error banner are visually similar in layout but represent different logical states, and treating them as equivalent would hide a real failure.

GitHub's team addressed this with what they describe as a three-tier equivalence framework: fast perceptual hashing and structural similarity metrics catch near-identical states cheaply and immediately; when those metrics are ambiguous, a multimodal model is asked specifically whether the difference between two states is semantically meaningful — trained, in effect, to ignore a changed timestamp or a different window decoration but flag a different error message or a missing control; and states are merged conservatively, only when the equivalence is confident, so that the graph naturally branches wherever paths genuinely diverge rather than being smoothed over into false equivalence. That last point matters as much as the first two — a validator eager to merge states will also merge states that shouldn't be merged, and quietly hide the very branching it was supposed to represent.

For business-workflow agents rather than UI agents, the same idea applies to functional states rather than screenshots. A modal that opens showing "saved" and a toast notification with an updated record in a table can both represent the single logical state "record updated" even though their raw representations — DOM structure, one being a transient overlay and the other a persistent table row — are entirely different. A validator working only at the level of raw DOM diffing will treat these as unrelated and either miss the equivalence or, worse, require one specific implementation to be considered correct. A validator that maps both onto a shared abstract state — ACCOUNT_UPDATED — sidesteps that brittleness entirely. The cost is that someone has to define the mapping from concrete observation to abstract state, which is real work, but it's work done once per state type rather than once per test case.

Dominator analysis: borrowing from compilers to find what's actually essential

The essential/optional distinction needs a rigorous way to be computed, not just intuited case by case, once the number of tasks being validated grows past a handful. The technique GitHub's team applied — and the one worth understanding in some depth, because it's the clearest formal anchor available for this whole problem — comes from compiler theory: dominator analysis.

In a control-flow graph, node A dominates node B if every path from the entry point to B passes through A. This is a decades-old concept from compiler optimization, originally used to reason about loop structure and where it's safe to hoist computations. Applied to agent execution graphs, the same relationship identifies structurally required states: if every recorded successful execution that reaches PREPARE_RESOLUTION passes through VERIFIED_ACCOUNT first, no matter which of the branching paths it took to get there, then VERIFIED_ACCOUNT is a dominator of PREPARE_RESOLUTION — and by this framework's definition, an essential state.

Concretely, in GitHub's implementation for validating Copilot's IDE interactions: their algorithm captured a small number of successful execution traces — between two and ten — converted each into a directed graph (a Prefix Tree Acceptor), merged them using the state-equivalence approach described above, and then computed dominator relationships over the merged graph to extract a "dominator subtree" representing the minimal set of must-pass checkpoints. In their VS Code search example, the "Search Dialog" state turned out to be a true dominator — it's logically impossible to reach search results without first triggering the search — while a "Loading" screen dominated nothing, because at least one recorded successful run bypassed it entirely (a fast environment skipped the spinner), and the algorithm correctly classified it as optional rather than required.

Validating a new, previously unseen trace against this dominator skeleton becomes a structural comparison rather than a search for an exact match: the algorithm checks whether the new trace's essential states appear in the required relative order, using something GitHub describes as topological subsequence matching. If the reference skeleton requires A → B → C, and a new trace produces A → X → B → Y → C, the extra states X and Y are treated as incidental noise and the trace passes, because the required subsequence A, B, C is present in the required order. A trace fails only when an essential state is missing entirely, or when the essential states that are present appear out of the order the task requires.

GitHub reported measuring this structural approach against having the agent self-report its own success (referred to as "CUA self-assessment," where the computer-use agent judges whether it succeeded) on a real test suite built around a custom VS Code extension. The comparison, as they published it:

Metric Agent self-assessment Dominator-tree validation
Accuracy 82.2% 100%
Precision 83.3% 100%
Recall 60.0% 100%
F1-score 69.8% 100%

Perhaps the more telling number is what happened specifically on "not a bug" cases — executions where something looked like a failure but was actually just environmental noise or a valid alternate path. The agent's own self-assessment scored 0% F1 on identifying these correctly; it could not distinguish "the agent stumbled" from "the product is broken." The structural, dominator-based approach scored 52.2% F1 on the same distinction — a large improvement, though still far from perfect, which is worth sitting with honestly rather than rounding up to "solved."

It's worth stating the limitation directly, because dominator analysis does not solve agent testing on its own, and treating it as if it does invites the same brittleness it was meant to fix. It's only as good as the traces used to build the reference skeleton. If those two-to-ten traces happen to share a state coincidentally — for instance, if every recorded trace happened to check support history even though nothing about the task actually requires it — dominator analysis will incorrectly classify that coincidental commonality as essential, because from a purely structural standpoint it looks identical to a genuine requirement. If the traces lack path diversity — if nobody ever recorded the company-name-search route to account verification — the resulting skeleton will reject a legitimate variation it simply never saw. And traces drawn only from "known good" runs, by construction, tell you little about which failure modes matter most; a reference model built exclusively from successes has no direct signal about which of the many ways to fail are common versus rare, or benign versus catastrophic. Structural inference from examples still needs domain knowledge layered on top — someone deciding, based on actual understanding of the billing workflow, that account verification really is required and that a fourth policy-retrieval call really is just waste rather than a hidden requirement. GitHub's own published limitations list acknowledges several of these directly: the technique currently requires success traces and can't yet learn correctness purely from failure logs, it depends on an external multimodal model for semantic equivalence checks (introducing cost and latency), and it doesn't yet capture temporal constraints like "this loading state must resolve within five seconds" — only ordering, not timing.

Necessary is not sufficient

A state being essential — a genuine dominator of the outcome — does not mean its presence alone proves the outcome correct. This is an easy point to state and an easy one to forget in practice, especially once a team has built a dominator-based validator and started trusting its coverage metric as a proxy for correctness.

VERIFIED_ACCOUNT being present in a trace proves identity verification happened. It does not prove the correct invoice was subsequently identified as the duplicate — an agent could verify the right account and still misidentify which of several similar invoices is the actual duplicate. It proves nothing at all about whether the eligibility check that follows correctly applied the retrieved policy rather than fabricating a plausible-sounding but unsupported conclusion.

Validation, in other words, needs to reason about sets of required conditions together, and in many cases about ordering relationships between them, rather than treating any single essential state's presence as a green light. Structural graph validation answers "did the required checkpoints get hit in a valid order" — a necessary condition. It does not, by itself, answer "was each checkpoint's content correct" — a separate, sufficient condition that generally needs its own oracle, often a deterministic one (did the identified duplicate invoice actually match the target invoice on account, amount, and date range?) layered directly on top of the structural check.

Partial ordering: when sequence matters and when it doesn't

A rigid test script implicitly asserts a total order: A, then B, then C, then D, then E, every time, in that exact sequence. Most real task specifications don't actually require a total order — they require a much weaker partial order, where some steps must precede others and some pairs of steps are free to happen in either sequence.

For the billing task, the actual requirement is closer to this shape:

        VERIFY_ACCOUNT
              ↓
      ┌───────┴───────┐
      ↓               ↓
CHECK_INVOICES   CHECK_POLICY
      └───────┬───────┘
              ↓
        RESOLVE_CASE

Account verification must precede both invoice checking and policy checking — that's a genuine ordering requirement, because acting on unverified identity is the exact failure mode Run D illustrates. But invoice checking and policy checking can happen in either order relative to each other; nothing about the business logic cares which one comes first, only that both happen before resolution. A validator built around a total order will fail a trace the moment invoice and policy checks swap places, even though the swap changes nothing about whether the task was done correctly. A validator built around the actual partial order — some edges mandatory, some pairs explicitly unordered — passes both variations correctly and only fails when an edge that genuinely must exist, such as verification before either check, is missing or inverted.

This doesn't require adopting the full weight of a formal methods textbook to use productively. In practice it means, for each task, explicitly writing down which pairs of steps have a real "must happen before" relationship and which pairs are simply "both must happen, order doesn't matter" — and then encoding that distinction into the validator rather than defaulting to strict sequence because strict sequence is what happened to get recorded first.

Temporal invariants: rules that hold across an execution, not points in one

Partial ordering handles relationships between specific pairs of steps. A related but distinct category of assertion concerns properties that must hold across the whole execution regardless of exact structure — temporal invariants, in the loose sense of "statements about when things happen or don't happen," not the formal temporal-logic sense, though the intuition is drawn from the same place.

Useful shapes for these assertions, expressed in plain terms:

  • A must happen before B. Identity verification must happen before any state-changing action — the core rule Run D violates.
  • C must eventually happen. A ticket that enters an "in review" state must eventually reach either "resolved" or "escalated" — it cannot simply be abandoned mid-trace.
  • D must never happen. A write operation must never target an account that hasn't been verified in the current execution.
  • E may happen at most once. A given financial credit must be issued at most once per invoice per execution — protection against exactly the double-issue scenario described earlier.
  • F must occur if G occurs. If a policy exception is applied, a corresponding audit log entry must be written; the two are coupled.

These assertions are valuable specifically because they allow real path variability while still protecting the properties that matter. None of them care what order invoice checking and policy checking happen in. All of them care, in a precise and checkable way, about a handful of relationships that would be catastrophic to get wrong.

Invariants and forbidden behavior: properties, not sequences

Moving one level further from sequence entirely: some of the most durable and reusable test assertions for non-deterministic systems aren't about steps or order at all, but about properties that must hold at every point in an execution, independent of path.

Examples worth having on hand as a starting template, adapted to whatever domain a given agent operates in:

  • The agent must never modify a customer record outside its own currently active tenant.
  • A financial adjustment must never exceed the amount a policy or an approval actually authorizes.
  • An irreversible action — a delete, a send, a payment — must not execute before the authorization step that's supposed to gate it.
  • A final answer must not assert that an external action occurred unless the system of record actually confirms it occurred.
  • A generated code patch must pass deterministic compilation and the existing test suite before being presented as complete.

The reason invariants are unusually powerful specifically for non-deterministic systems is that they don't require anticipating every path an agent might take to violate them. A sequence-based assertion has to be written against a particular trace shape; an invariant like "never write to an unverified account" catches every path that leads there, including ones nobody thought to write a specific test for — which is exactly the property Run D needed and exactly the property a purely structural, essential-states-only validator, if not paired with this kind of check, could plausibly miss, since Run D does technically reach a "resolution prepared" state.

The mirror image of an invariant is a forbidden behavior — an anti-invariant, in effect — and it's worth stating as a category on its own because it's easy to write extensive positive requirements ("must verify account, must check policy, must produce resolution") and quietly assume that satisfying all of them is sufficient. It isn't. A system can satisfy every positive requirement and simultaneously do something unacceptable that no positive requirement happened to rule out: accessing the wrong tenant's data on the way to a correct-looking answer, exposing a credential in a debug log, calling a destructive tool that wasn't actually needed, issuing a duplicate financial action, presenting unverified information as confirmed fact, or writing to a production system from what was supposed to be a test environment. A specification built entirely from "what must happen" has a blind spot exactly where "what must never happen" needs to sit, and both need to be present for a task's correctness envelope to actually hold.

When the output itself is language: semantic equivalence and its limits

Everything so far concerns action sequences and system states. A large share of what agents produce, though, is natural language — the final message returned to a customer, an internal summary, an explanation of a decision — and exact string matching is usually the wrong tool here.

Two candidate responses to the billing scenario:

"Your duplicate invoice has been confirmed and a credit will be prepared."

"We confirmed the duplicate charge and the account is eligible for a credit."

These express the same essential content through different wording, and a validator that requires an exact match, or even a high string-similarity score, will reject the second for no good reason — a textbook false negative. The natural response is to shift toward judging semantic equivalence: do the two statements mean the same thing, regardless of phrasing?

That shift is necessary, but it introduces a real danger if applied carelessly: semantic similarity is not the same thing as factual correctness. Two sentences can be extremely close in surface meaning while differing in exactly the details that make one right and the other wrong:

"A $340 credit has been applied to account 4821."

"A $430 credit has been applied to account 4812."

These two sentences are, by most semantic-similarity measures, nearly identical — same structure, same intent, same general claim. They also transpose two digits in the amount and two digits in the account number, which is the difference between a correct resolution and a wrong one applied to the wrong customer. A validator judging purely on semantic closeness will not reliably catch this, because "closeness" as most similarity measures compute it doesn't weight a transposed digit any differently than a synonym swap. This is the specific failure mode that motivates moving critical claims out of free text entirely wherever possible.

Structure helps — but structure alone doesn't solve semantics

The practical fix for exactly the transposed-digit problem is to require structured output for anything load-bearing, and let natural language stay free-form only where its exact wording genuinely doesn't matter to correctness. Instead of relying on a sentence to convey the credit amount, require a structured object alongside it:

json
{
  "duplicate_confirmed": true,
  "account_id": "4821",
  "invoice_id": "INV-91",
  "eligible": true,
  "recommended_action": "credit",
  "amount": 340.00
}

The customer-facing sentence can vary in wording freely; the fields that actually determine whether the business outcome is correct become directly, deterministically checkable — account_id equals the requested account, amount matches the invoice total, invoice_id matches the identified duplicate. This is a straightforward, high-leverage move, and it's why teams building serious agent evaluation increasingly push critical claims into structured fields rather than trying to parse correctness out of prose after the fact.

It is not, on its own, a complete solution, and it's worth being direct about the reason. A model can populate "eligible": true with complete internal confidence and still be wrong, because the field being structured says nothing about whether the underlying reasoning that produced its value was sound. Structure removes the ambiguity of what was claimed; it does nothing to verify whether the claim was true. That verification still requires checking the structured claim against the actual evidence the agent retrieved — did the invoice comparison it ran actually show matching amounts and accounts, did the policy text it retrieved actually support an eligibility determination of "true" for this specific case. Structured output and evidence-based validation aren't alternatives to each other; they're complementary, and skipping the second because the first feels like it solved the problem is a common and avoidable mistake.

Evidence-based validation: judging conclusions by what supports them

This leads directly to a stronger and generally underused form of oracle: instead of, or in addition to, checking what an agent concluded, check what it based the conclusion on.

If an agent asserts "this invoice is duplicated," that claim can be validated against the specific evidence retrieved during the execution: which invoice IDs were compared, whether their amounts matched, whether they belong to the same account, whether their dates fall within a window the policy considers meaningful, and whether the retrieved policy actually applies to this category of charge. This is a materially stronger oracle than inspecting the final prose alone, because it checks the chain of reasoning against ground-truth data rather than checking whether the output merely sounds plausible. It's also exactly the check that would catch Run D even if its final text were indistinguishable from Run A's: Run D's evidence trail shows an invoice found via global search and an account inferred by name similarity, not verified identity — a clearly weaker evidentiary basis, and checkable as such.

The test oracle problem, restated for agents

Every testing discipline eventually runs into the "oracle problem" — how you know what the correct output is, independent of running the system under test and hoping it agrees with itself. Deterministic software mostly sidesteps this: the oracle is expected == 42. Agentic systems can't rely on a single fixed expected value, but the oracle problem doesn't go away — the oracle is constructed differently, often as a set of properties (P1 through Pn) a result must satisfy rather than a single value it must equal.

It's useful to name the distinct kinds of oracle available, since mature agent testing typically combines several rather than picking one:

Deterministic oracles check things with a single unambiguous correct answer available from the system itself — database state after the run, a schema validation, a calculation that has exactly one right numeric answer, a permission check, an API response contract.

Reference oracles compare against a set of known facts established independently of the run — the actual correct invoice ID for this test scenario, the actual policy that applies.

Structural oracles check required states and transitions — the dominator-based, essential/optional/convergent-path validation covered earlier.

Semantic oracles apply a rubric to judge quality where no single correct phrasing exists — completeness, tone, whether an explanation is understandable.

Business oracles check whether the task was actually accomplished in the system of record, independent of what the agent claims — did the ticket actually close, did the credit actually post to the ledger.

Human oracles — expert review, used selectively for cases too subtle or too high-stakes to trust to any automated check alone.

Model-based oracles — an LLM acting as a grader, evaluating another model's output against a rubric.

None of these is sufficient alone for a system with the shape described throughout this piece. A mature evaluation setup for the billing scenario would likely use a deterministic oracle for the credit amount and the account ID, a structural oracle for the required verification-before-write ordering, an evidence-based oracle for the duplicate-invoice claim, and a semantic oracle only for judging whether the customer-facing explanation is clear and appropriately worded — each oracle applied to the part of the problem it's actually suited for.

Exact match still has a place — a large one

None of the above should read as an argument against exact-match assertions generally; that would be an overcorrection in the opposite direction, and it's worth stating the opposite mistake explicitly because it's just as common as the one this article opened with.

Exact matching remains the right tool for account IDs, invoice IDs, calculated totals, enumerated status values, permission flags, API response schemas, required state transitions, security rules, and any field with exactly one correct value. None of these need semantic judgment or statistical thinking; they need a comparison operator. The mistake isn't using exact match — it's applying exact match to the parts of a system that genuinely have legitimate variability, such as the exact phrasing of a generated explanation or the exact order of two independent read operations. The right approach uses exact matching wherever the underlying property genuinely has one correct value, and reserves flexible evaluation for the (smaller, but real) portion of the system where variability is actually legitimate rather than a testing shortcut.

What current evaluation tooling actually supports

It's worth grounding this in what current tooling looks like in practice, as one example of a broader industry direction rather than as an endorsement of any specific vendor's implementation.

OpenAI's documented grader types for its evaluation framework include exact and pattern-based string checks for deterministic answers, similarity-based grading for answers that are correct but phrased differently, and model-based graders — including a "score model" grader that prompts a separate model to assign a numeric score against defined criteria, and a "label model" grader that classifies output into categories. The general guidance in OpenAI's own documentation is direct: use a string-check grader with exact or pattern matching when the correct answer is a deterministic string, and reach for similarity-based or model-based grading specifically when correct answers can be phrased differently but must convey the same content. That's the same distinction this article has been drawing throughout — deterministic where the underlying property is deterministic, flexible where it genuinely isn't — showing up independently in a vendor's grader taxonomy rather than being unique to this framing.

Anthropic's own guidance on evaluating agents, published as engineering documentation on building evals for Claude-based agents, converges on a related and important point: graders need their own quality bar, a 0% pass rate across many trials is usually a sign the task specification or the grader is broken rather than a sign the agent is fundamentally incapable, and every eval task benefits from a known-working reference solution specifically to prove the task is solvable and confirm the grader is wired correctly. The same documentation stresses testing both directions of any behavioral rule — checking that an agent performs an action when it should and that it doesn't perform it when it shouldn't — warning that one-sided evals produce one-sided optimization, with the concrete example of an agent that starts searching for nearly everything if evals only ever check whether it searches when appropriate and never check whether it searches when it isn't.

This should be read as an illustration of a general pattern that shows up across the ecosystem — different grader types exist because different properties genuinely require different evaluation mechanisms — not as a claim that any particular vendor's current tooling is the final word on the subject; these products evolve quickly and specific capabilities should be verified against current documentation before being relied on.

LLM-as-judge: useful, probabilistic, not ground truth

Model-based grading deserves its own direct treatment because it's simultaneously one of the more useful tools available for genuinely semantic properties and one of the more misunderstood.

What a model can reasonably be asked to judge: relevance of a response to the original request, completeness against a defined rubric, tone, semantic consistency between a claim and its supporting context, and satisfaction of specific itemized criteria — exactly the kinds of properties that resist exact-match checking and would otherwise require a human reviewer to assess by hand, at a cost that doesn't scale to continuous evaluation.

What model-based grading is not: an objective ground truth that happens to be automated. The grading model is itself probabilistic, and inherits well-documented problems — grader variability across repeated runs of the same judgment, bias toward certain response styles independent of actual quality (verbosity is a commonly cited example), position effects where the order candidates are presented in influences the verdict, reference bias favoring responses that resemble a provided reference even when an equally valid but differently phrased response exists, and the possibility of reward hacking, where a system optimized against a grader learns to satisfy the grader's tendencies rather than the property it was meant to measure. Poor rubric design compounds this — a vague "is this answer good?" gives a judge enormous latitude to be inconsistent with itself and provides no diagnostic value when it disagrees with a human, since there's no itemized basis to compare against.

The response isn't to discard model-based grading but to treat it like any other probabilistic component: validated, bounded, never trusted as an unquestioned source of truth. That means calibrating a judge's verdicts against human-reviewed cases before trusting it at scale, watching for drift when the underlying judge model changes, and building rubrics specific enough that a judge's disagreement with a human is itself diagnostic.

Rubrics: the difference between semantic evaluation and semantic guessing

A weak semantic evaluation asks a judge model one broad, underspecified question: "is this answer good?" This produces a number, but not a useful one — it can't be reproduced reliably even by the same judge on a slightly different day, and when it disagrees with a human reviewer there's no way to localize why.

A stronger approach decomposes the judgment into specific, checkable criteria and asks the judge — or a human — to assess each separately:

  • Was the correct account identified?
  • Was the correct invoice identified as the duplicate?
  • Was the applicable policy correctly applied to this specific case?
  • Does the response contain any claim that isn't supported by retrieved evidence?
  • Is the recommended action appropriate given the eligibility determination?
  • Where evidence was genuinely insufficient, did the response express appropriate uncertainty rather than false confidence?

Breaking a broad quality judgment into observable, individually gradable criteria makes evaluation meaningfully more stable — a judge's variance on any one narrow criterion tends to be lower than its variance on one sweeping quality question — and it makes evaluation explainable in a way a single score cannot be. "Failed criterion 3: policy misapplied" tells an engineer exactly where to look. "Quality score: 0.61" tells them nothing beyond "somewhere, something was less than ideal."

Combining deterministic and semantic graders in one specification

The strongest evaluation setups don't pick one oracle type and apply it everywhere; they layer several, each assigned to the part of the correctness envelope it's actually suited for. For the billing scenario, a combined specification might look like this:

  • Deterministic: account_id in the structured resolution output matches the account requested.
  • Deterministic: credit amount is less than or equal to the amount the applicable policy authorizes.
  • Deterministic: no forbidden tool (delete_account, send_external_email without an authorization flag, etc.) was called at any point in the trace.
  • Structural: account verification occurred, and it occurred before any state-changing action, per the partial-order requirement described earlier.
  • Semantic: the natural-language explanation returned to the customer accurately reflects the policy basis for the decision, without asserting anything the retrieved evidence doesn't support.

This layered combination is substantially stronger than any single global model score, because each layer catches a different failure class and none of them is asked to do work it's poorly suited for. A model-based judge asked to verify an exact dollar amount is a worse tool for that job than a straightforward numeric comparison; a regex is a worse tool than a semantic judge for evaluating whether an explanation is genuinely clear. Matching the oracle to the property, rather than defaulting to whichever oracle is easiest to wire up, is most of what separates a validation setup that catches real regressions from one that mostly produces noise in either direction.

One run tells you less than it used to

For deterministic code, one execution of one condition is generally sufficient to demonstrate that condition holds, since running the same input against the same code twice produces the same result. That assumption breaks for probabilistic behavior — a single successful run carries much less evidentiary weight than it used to.

The practical response is to repeat scenarios and measure distributions rather than single outcomes. Running the same billing scenario 100 times reveals what a single run cannot: what fraction of executions reach a valid outcome, what fraction violate a constraint, what fraction require a retry or escalate rather than resolving autonomously, and what the distribution of latency and cost looks like across the full set. There is no universal correct repeat count — the right number depends on the task's risk profile, the variance actually observed, the cost of each repetition, and the confidence a given release decision requires. A low-stakes internal tool might run five repetitions per scenario in CI; a financial action with regulatory exposure might warrant hundreds, precisely because the cost of a rare severe failure is so much higher than the cost of running more trials to catch it.

A related pitfall: an aggregate pass rate, on its own, can be dangerously reassuring. A system passing 98% of scenario repetitions sounds strong until the composition of the remaining 2% is examined. If it's evenly distributed across minor misses, 98% may genuinely represent a healthy system; if it concentrates a cross-tenant write or a duplicate payment, that same 98% hides a real, unacceptable risk. This is why failure classification by severity matters as much as the aggregate rate — a single severe failure category showing up rarely deserves more attention than a larger volume of harmless misses, and a dashboard reporting one blended pass rate obscures exactly the distinction a release decision needs.

This generalizes into a broader shift: for genuinely probabilistic systems, quality is often better understood as a distribution over outcomes than a single output judged correct or incorrect. This doesn't eliminate the value of debugging any individual failing case — someone still needs to trace what happened in that one run — but it adds a complementary layer: awareness of how frequently a given failure mode occurs across the space of legitimate variation, which matters for deciding whether a failure mode is a rare edge case or a systematic pattern.

It's also worth being precise, without turning this into a statistics lecture, about what an observed rate actually proves. Twenty out of twenty successful trials does not mathematically establish a 100% true success rate — it establishes that, based on a small sample, the true rate is likely high, with confidence that depends directly on sample size. A smaller sample leaves more room for a rare but real failure mode to simply not have shown up yet by chance. The appropriate response to a critical, low-frequency failure mode is not to rerun the same small trial and hope, but to increase sample size specifically where the consequences of missing a rare failure are severe.

This motivates a distinction between two testing strategies serving different purposes: broad, repeated execution of representative scenarios, and narrowly targeted, deliberately constructed edge-case scenarios. Random repeated execution surfaces failure modes occurring at a meaningful frequency across ordinary usage. It's poor at surfacing failures that are rare but severe, since rare events, by definition, don't show up reliably in a moderate number of trials. Catching those requires deliberately constructing the conditions under which they're more likely: an ambiguous identity where two accounts share a name, an expired permission that should trigger a denial path, a duplicate upstream API response a naive retry might double-count, a partial tool failure that times out mid-side-effect, an unexpected retrieved document that contradicts the expected policy, or — where the domain ingests external content — adversarially crafted input designed to manipulate behavior. These scenarios exist because waiting for them to occur naturally in a random sample isn't viable when the cost of missing one is high.

Metamorphic testing: checking relationships instead of exact answers

One of the more directly useful techniques for a class of problem where the correct output is hard to specify in advance but a relationship between two related outputs is easy to specify is metamorphic testing. Formalized in software testing research over several decades, it works by defining a metamorphic relation: a property that must hold between a source input and its output, and a related follow-up input constructed by transforming the source, and its output — without ever needing to know the "correct" output for either in absolute terms. A program computing a sum, given a metamorphic relation stating that reversing a list's order must not change the sum, can be tested by checking that source and follow-up outputs agree, with no need to know the expected sum in advance.

Applied to an agent task: given a source scenario — "summarize these three invoices" — a follow-up built by reordering the same documents should produce a summary containing the same underlying facts, even if the prose differs; a relation asserting factual stability under reordering catches order-sensitivity that shouldn't exist. The same logic applies to rephrasing a request in equivalent language, inserting irrelevant formatting noise into retrieved context, or harmless UI timing changes — none of these should change the essential outcome. Where a major behavioral change follows from a transformation the task's own logic says shouldn't matter, that's a concrete signal of fragility, discovered without ever hand-specifying what the "correct" output looks like for either case.

Property-based testing: generating cases from rules instead of hand-writing examples

A closely related technique is property-based testing: instead of hand-authoring individual examples, define a property that should hold across a whole space of inputs and let a generator construct many varied inputs to check it against, rather than relying on whichever specific examples a human happened to think of. Several of the constraints already covered read naturally as generatable properties: no request from tenant X should ever result in access to tenant Y's data; no valid amount below a defined escalation threshold should ever trigger unnecessary escalation; every successful refund should correspond to exactly one eligible transaction, never zero and never more than one.

It's worth being precise about the limits here rather than overselling the technique. Classic property-based testing, developed for deterministic functions, doesn't on its own solve semantic AI evaluation — judging whether generated language is a good response isn't expressible as a mechanically checkable property the way "the sum is order-independent" is. It earns its place specifically at the invariant and constraint layer, not as a substitute for the semantic oracles discussed earlier.

A related technique, used when comparing versions rather than validating one in isolation: run the same scenario suite against a baseline and a candidate — a new model, prompt, or retrieval strategy — and compare the resulting distributions rather than individual traces. The comparison shouldn't demand identical paths, which would reintroduce exactly the brittleness this article has argued against; the actual question is whether the candidate's behavior remains inside the same correctness envelope — outcome success rate holding or improving, constraint-violation rate flat or decreasing, tool-call distribution shifting in an expected direction, latency and cost within bounds. Two versions can take visibly different paths and both be acceptable; the comparison that matters sits at the level of the envelope, not the trace.

Regression testing without exact replay

This directly reframes what regression testing means for agentic systems, since "regression testing" carries assumptions from deterministic software that don't transfer cleanly. Traditional regression: same input, same expected result, on every change. Agentic regression: same scenario, and the requirement is that the outcome remains acceptable, the invariants still hold, no newly forbidden behavior appears, and performance stays within bounds — while the specific path is explicitly allowed to change. A suite built on exact-replay assumptions flags every legitimate variation as failure, drowning real regressions in noise; a suite built on envelope-level assertions catches genuine regressions — a prompt version that starts skipping verification more often, a retrieval strategy that increases wrong-invoice identification — without caring that the exact tool-call sequence looks different from last week's baseline.

A related and easy-to-miss failure mode: a system can keep producing correct final answers, release after release, while the behavior behind those answers steadily deteriorates in ways an outcome-only pass rate never shows. Tool calls per task creeping upward. Escalation rate climbing. Unnecessary exploratory searches appearing more often. A gradual shift toward write-capable tools where a read-only tool would have sufficed. Latency and cost drifting upward. None of this shows up in an outcome-correctness metric, since the outcome, narrowly defined, is still correct. Whether a given drift counts as a regression worth blocking a release over is a judgment call — the point is that these dimensions need to be tracked at all, since a monitoring setup watching only outcome correctness has no way to notice this category of regression until it's severe enough to eventually degrade outcomes too.

Efficiency as part of correctness, sometimes

None of this means "every extra step is a defect." An agent facing genuine ambiguity may legitimately need additional exploratory steps — disambiguating similarly named accounts, or checking a policy edge case a simpler request wouldn't require. Automatically failing every longer-than-average path punishes exactly the caution that's often desirable.

What's useful instead is an explicit, per-task-type budget: a maximum tool-call count beyond which additional calls likely indicate confusion rather than diligence, a maximum retry count, a cost ceiling, a wall-clock duration, and a loop-depth limit to catch a stuck agent repeating the same action without progress. These budgets are inherently task-dependent, and setting them requires the same domain judgment that setting essential states does — there's no universal number that transfers across tasks.

Back to Run E: correct, and still worth flagging

This is the moment to return directly to Run E from the opening. It eventually reaches the same correct resolution as Run A, B, and C. Along the way it retrieves the same policy document twice, re-lists the same invoices a second time with no new information gained from doing so, uses roughly double the tokens a comparable successful run needs, and takes measurably longer to complete.

Should it pass? The honest answer, using the four-dimension model introduced earlier, is: functional correctness passes cleanly — outcome, state, and constraint correctness are all satisfied, and the resolution it prepares is accurate. Process quality, evaluated against a reasonable step-count and cost budget for this task type, does not pass, or at minimum warrants a warning rather than a clean pass. This is exactly why collapsing test results into a single binary signal loses real information: a system reporting simply "Run E: PASS" hides an operational cost problem that's worth someone's attention, while a system reporting simply "Run E: FAIL" would send an engineer to investigate a business-logic bug that doesn't actually exist, wasting exactly the kind of triage time discussed earlier in connection with false negatives.

Reporting results across multiple dimensions instead of one verdict

The natural implementation of this is a test result that reports each dimension of the correctness envelope somewhat independently rather than forcing everything into a single pass/fail signal. A representative shape:

RUN 174
Outcome correctness:        PASS
Required states:            PASS
Forbidden behavior:         PASS
Policy compliance:          PASS
Efficiency (tool-call budget): WARN  (7 calls, budget 4)
Latency:                     PASS  (2.3s, budget 5s)
Semantic response quality:   PASS

This isn't a claim that every organization needs exactly this format, this exact set of rows, or this exact severity vocabulary — the specific shape should match whatever a given team's release process actually needs to act on. The underlying point is that agent quality genuinely has multiple, partly independent dimensions, and a reporting format that preserves that structure gives an engineer or a release-gate process something actionable, where a single aggregate score would require guessing at what actually went wrong before anyone could act on it.

Wrong path, right answer — and the reverse

Two failure shapes deserve to be named explicitly because each defeats a different, otherwise-reasonable testing strategy on its own.

Wrong path, right answer. An agent reaches a correct final result by guessing, by relying on stale cached information that happened to still be accurate, by accessing context it wasn't supposed to use for this decision, or — as in Run D — by selecting the wrong underlying record and then coincidentally computing a correct-looking amount because the wrong invoice happened to have a similar total. Outcome-only testing passes every one of these. Catching them requires inspecting the causal evidence behind the outcome, which is expensive enough that it's reasonable to apply it selectively — concentrated on the higher-risk, state-changing parts of a task rather than uniformly across every read operation — rather than everywhere by default.

Right path, wrong answer. The mirror case: an agent retrieves the exactly correct account, the exactly correct invoices, and the exactly correct policy — every structural checkpoint satisfied, every essential state present in the correct order — and then misinterprets the policy's actual eligibility criteria, reaching an incorrect conclusion despite having done everything upstream correctly. Structural, state-based validation passes cleanly here, because structurally nothing went wrong. Only a semantic or evidence-based layer, checking the content of the final judgment against the content of the retrieved policy, catches this. No single layer of validation covered in this article is sufficient on its own; this pairing of failure modes is exactly why the layered combination described earlier — deterministic, structural, and semantic oracles applied together — is not a stylistic preference but a structural necessity.

Tool-call testing and tool equivalence

For any agent whose actions are mediated through discrete tools, a practically important layer of evaluation concerns the tool calls themselves, independent of the final outcome: was the tool selected actually relevant, were its arguments correct, did calls happen in a permissible order, did any call produce a side effect it shouldn't have, was a state-changing call duplicated in a way that risks a double effect, and was the calling agent actually authorized to invoke it.

A closely related idea follows directly from everything already covered about paths and convergence: two different tools can produce equivalent evidence, and a validator that insists on one specific call because that's what appeared in a recorded reference trace reproduces the exact brittleness this article opened by criticizing. customer.lookup_by_id and customer.search followed by exact-match selection are functionally interchangeable ways of arriving at the same verified identity in the billing scenario. What matters is that identity ends up verified, not which specific call verified it.

Not every tool call carries equal risk, and validation effort should scale accordingly. Read-only operations can reasonably tolerate exploratory latitude; searching a bit more than strictly necessary is, at worst, a process-quality concern. State-changing operations warrant a stronger set of constraints: for any refund, deletion, permission change, or account modification, validation should confirm the correct target was acted on, authorization was actually present and checked rather than assumed, the value involved is within any authorized limit, the action executed the expected number of times — not zero, not more than once — and the actual result matches what the system claims happened. This asymmetry, light-touch for reads and strict for writes, is a direct consequence of the risk-proportionate approach that recurs throughout this article.

Environmental noise is not agent failure, and flakiness is not "AI being non-deterministic"

Two distinctions are worth holding apart, because conflating either one leads to a common failure of judgment.

The first: not every source of trace variation originates in the model. UI-driving agents encounter loading states, animation timing, layout differences, network delay, and leftover session state. Externally called systems return paginated results in different batches or occasionally return results in a different order due to distributed read replicas. None of this is the model behaving unpredictably — it's the environment behaving the way environments have always behaved, and attributing it to "AI non-determinism" misdiagnoses a cause that deterministic UI and integration tests have always had to contend with. The fix for each differs: one is addressed by making the validator tolerant of legitimate variation, the other by making the environment or harness more stable.

The second, more important distinction: legitimate variation is not flakiness. Legitimate variation is what this article has been about — multiple valid ways of reaching the same required state. Flakiness is unexpected instability that prevents reliable evaluation at all — an agent that hangs indefinitely on a resource it should have timed out on, a tool integration that intermittently returns malformed data due to an actual bug. It's tempting, once a team has internalized "tolerate legitimate variation," to wave away every unstable result as "that's just AI non-determinism." That temptation should be resisted. Real defects do not become acceptable because they occur inside a system that also has legitimate variation elsewhere; normalizing instability under that label hides bugs rather than accommodating a probabilistic system correctly.

Record-and-replay and simulation still have real roles

None of this is an argument for discarding record-and-replay entirely — it has a real, narrower role: reproducing a specific known environment state on demand, supplying deterministic API responses so environment variability doesn't get conflated with agent variability, and pinning down a known regression case that needs to be checked exactly every time. What it's weak at is serving as the sole definition of valid agent behavior, since by construction it only recognizes the one path it recorded.

Simulation extends the same idea: a controlled environment offers reproducibility, the ability to construct rare error conditions on demand, and the ability to safely exercise real side effects without risking real ones. The limitation is symmetric — a simulated environment may omit real-world variability nobody thought to model, which is why layered testing across mocks, simulation, staging, and limited live evaluation tends to catch more than any single layer alone.

Failure injection: testing what happens when things go wrong on purpose

A related and underused technique is deliberately injecting failure conditions rather than waiting for them to occur naturally: a tool timeout, an empty search result, a genuinely ambiguous customer identity, a stale document surfacing in retrieval, a rate limit mid-task, or a malformed tool response.

What matters is observing how the agent responds, distinguishing patterns that superficially might all look like "the agent handled it": does it recover gracefully; does it correctly ask for clarification when the condition creates genuine ambiguity; does it choose a sensible alternative path; does it get stuck looping; or — the pattern that matters most to catch — does it report success despite the injected failure having actually prevented completion, or take an unsafe action while working around it. That last pattern is exactly what outcome-only testing under normal conditions would never encounter.

Clarification and abstention are valid outcomes, not failures to autonomy

A definition of "success" that only counts fully autonomous completion is itself a source of testing error. If the correct behavior given genuinely ambiguous input is to ask a clarifying question rather than guess, then a system that asks is behaving correctly, even though it didn't resolve the task in one pass. Two accounts with near-identical names showing up in a search: a system that picks one and proceeds looks more capable than one that pauses to ask. It is also considerably more likely to be wrong, and being confidently wrong is worse than being correctly uncertain.

The same logic extends to explicit abstention — "I cannot determine this safely from the available evidence" — which can be the correct outcome when evidence genuinely doesn't support a confident answer. Evaluating this well requires measuring in both directions: unnecessary refusal despite sufficient evidence is its own failure mode, and dangerous overconfidence despite insufficient evidence is the more consequential one. Both need to be measured, since optimizing against only one predictably produces a system that's bad in the other direction.

Escalation as a legitimate outcome, gated by defined conditions

Following the same logic: escalating a decision to a human should sometimes satisfy the task contract outright rather than counting against it. A policy conflict the agent isn't positioned to resolve, a financial threshold beyond what autonomous action is authorized to handle, or an identity ambiguity that genuinely can't be resolved are all situations where escalation is correct, not a shortfall. Scoring every escalation as failure — the natural default if "success" means "resolved without human involvement" — incentivizes exactly the overconfident guessing that's more dangerous than asking for help. The right approach defines, per task category, which conditions make escalation the expected outcome, and evaluates against that rather than treating all escalation as a deficit from full autonomy.

Multi-agent systems widen the space of legitimate paths further

Where a single orchestrating agent delegates to specialized sub-agents, the space of legitimate paths widens again. One run might delegate through a research agent, then billing, then policy; another equally valid run might skip research entirely because the orchestrator judged it unnecessary for that case. Validating this doesn't require an identical delegation topology across runs — that reproduces the same single-path brittleness one level up the stack. It requires validating the same underlying properties already established: that required evidence was gathered by someone in the chain, that delegation stayed within its defined bounds, that the final outcome is correct, and that no constraint was violated anywhere in the trace, regardless of which sub-agent did which piece of work.

Security should stay deterministic even inside a probabilistic system

Worth reinforcing a point made earlier specifically for security, since the temptation to apply flexible evaluation everywhere once a team adopts this framework is real, and security is exactly the wrong place to give in to it. Tenant boundaries, permission scope, secret access, allowed tool destinations, and write authorization should remain hard, deterministic rules enforced independently of the model's own behavior — not properties left to a grader's judgment call. If a deterministic policy engine can decide whether a tenant boundary was violated, that decision should be made and enforced deterministically; an LLM grader should never stand between a system and a cross-tenant data exposure. Everything in this article about tolerating path variation concerns how a task gets done — never whether a hard boundary gets crossed.

Business invariants as durable testing anchors

Beyond security, a broader category of business invariants makes for durable, high-value testing anchors precisely because they don't depend on path — pure properties, checkable with a straightforward comparison, regardless of how an execution got there: a refund must never exceed the amount eligible under policy; only an active account may be modified; an order can't ship before its payment state permits it; a user can't approve their own restricted transaction. These read like the constraints a relational database would enforce, which is not a coincidence — they benefit from being enforced as close to the deterministic layer as possible, checked independently of whatever the agent's own narrative claims.

Business outcome testing: trust the system of record, not the agent's own account of itself

An agent's own prose account of what it did is not proof that it happened. If a final message says "your issue is resolved," that claim needs checking against the actual system of record — did the ticket close, did the credit post to the ledger, did any promised notification actually send. This is a direct instance of the right-path-wrong-answer and wrong-path-right-answer failures covered earlier: the agent's self-report is exactly the surface-level signal both failure shapes can fool, which is why business outcome testing checks external system state rather than the agent's narration of it.

Designing an evaluation dataset that isn't just happy paths

A dataset used to drive this kind of evaluation needs to cover more ground than the scenarios that occur most often in ordinary usage: typical cases; genuinely ambiguous cases; edge cases at the boundaries of defined rules; scenarios reconstructed from real historical failures; high-risk scenarios chosen for consequence severity; rare but legitimate workflows; negative cases where correct behavior is explicitly not to act; cases where abstention is correct; and cases deliberately constructed with multiple simultaneously valid outcomes, to confirm the validator doesn't wrongly demand a single one.

The overfitting trap, and closing the loop with production

A quieter risk is that a team, iterating repeatedly against the same static suite, gradually tunes prompts and configurations to satisfy that suite specifically — improving measured performance on the benchmark without necessarily improving general behavior on cases it doesn't cover. This isn't the formal statistical overfitting of a trained model, but the practical effect on team behavior rhymes closely enough to warrant the same caution. The mitigation: maintain a genuine holdout set never directly tuned against, rotate the evaluation set over time, and fold newly observed production cases and newly discovered failure clusters into it as they're found, rather than letting the set calcify into a fixed target that stops reflecting real usage.

The most reliable long-term source of high-value evaluation cases is production itself: a failure occurs, its conditions get reproduced, the specific invariant or required state that was actually violated gets identified precisely, the scenario is added as a permanent case in the evaluation suite, the current system is checked against it, a fix is made, and the case stays in the regression suite indefinitely. This is the mechanism by which what's learned from a live incident becomes a durable asset in the validation layer, rather than a one-off fix the test suite has no lasting memory of.

Comparing releases: did the envelope change, not did every trace match

Before shipping any change that can alter behavior — a model version, a prompt revision, a change to available tools, a framework update — running a representative evaluation suite against both current and candidate versions is the direct application of nearly everything covered in this article.

The comparison shouldn't be framed as "are all traces identical" — framed that way it fails on essentially every release, including safe ones, since path variation between versions is expected and often desirable. What matters: did the correctness envelope itself change. Did any high-severity constraint violation increase, even slightly. Did outcome success rate hold, and did tool-call count, latency, and cost stay within bounds relative to baseline. A release can look completely different at the level of individual traces and still be entirely safe, provided the envelope it operates inside hasn't gotten worse on any dimension that matters.

Where the surrounding architecture supports it, a further layer of safety is available before a new version gets authority to take real actions: run the candidate in a mode where it proposes actions without executing side effects, and compare those proposals against the current version's actual actions or against defined correctness properties directly. Not every architecture can implement this without real investment — separating "decide" from "do" cleanly enough to support it is a design commitment — but where available, it validates a new version against real production inputs before trusting it with real side effects.

"We ran 1,000 tests" is not, by itself, an answer to anything

This is one of the more common ways evaluation rigor gets misrepresented, usually without intent to mislead — a large test count simply feels like a strong signal, and that feeling doesn't automatically track what the tests actually check.

"We ran 1,000 tests and they all passed" sounds reassuring. What actually matters is not how many tests ran but what each was verifying. If the assertions behind all 1,000 amount to "a response was generated" and "no exception was thrown," they demonstrate almost nothing about business correctness, safety, tool selection, or high-risk edge cases. A thousand tests checking a weak oracle provide barely more assurance than one test checking a strong oracle — arguably less, because the volume creates a false sense of coverage. Test count is not the same measurement as oracle strength, and any release process reporting count as a proxy for confidence, without reporting what the tests assert, is measuring the wrong thing.

Traditional code-coverage metrics remain legitimate for the deterministic portions of a system — tool implementations, orchestration logic, authorization checks, state machine transitions. Where they stop being sufficient is at the layer this article has focused on. Agentic evaluation benefits from tracking scenario coverage along dimensions that don't map onto lines of code: distinct intent types, the tools actually available, the business states a task can pass through, permission levels, defined failure modes, genuine-ambiguity scenarios, and categories of environment variation. There's no single universal "agent coverage percentage" computable the way branch coverage is — but tracking coverage deliberately across these dimensions, even qualitatively, beats measuring only outcome pass rate and assuming it implies broad coverage.

A complete practical specification for the billing scenario

Pulling the framework developed across this article together into one concrete example, here is what a full test specification for the opening billing scenario looks like when every piece is made explicit:

Input intent. Find the duplicate charge for the specified account and prepare an appropriate resolution.

Required outcomes. The correct account is identified. The correct duplicate invoice is identified. Policy eligibility is correctly determined against the actual retrieved policy. An appropriate resolution is prepared, matching the eligibility determination.

Required invariants. The tenant boundary is respected throughout — no access to any account outside the requesting tenant. Any financial adjustment stays within the limit the applicable policy actually authorizes.

Required ordering. Identity verification occurs, and it occurs before any state-changing action.

Optional behavior, explicitly permitted. A support-history lookup may or may not occur. An additional policy search may or may not occur. The specific method used to establish account identity — direct ID lookup versus name search with disambiguation — may vary.

Forbidden behavior. Modifying any account other than the one verified for this request. Issuing a credit without the eligibility check having actually occurred first. Issuing a duplicate credit for the same invoice within a single execution.

Operational bounds. A defined maximum reasonable step count for this task type. A defined maximum acceptable cost. A defined maximum acceptable latency.

The correctness-envelope matrix

The same specification, organized as a reusable table structure — the shape this framework tends to take once a team starts applying it across multiple task types rather than one at a time:

Dimension What May Vary What Must Hold Possible Oracle
Final outcome Exact phrasing of the response Correct account, invoice, and resolution type Structured-field exact match
Path Order of independent read operations Required states occur in required relative order Structural / dominator-based
Tool selection Which specific tool establishes a given fact The underlying fact is actually established and correct Evidence-based check
State Intermediate UI or system representations Business-level state is genuinely reached Semantic state abstraction
Safety — (no variation permitted) Tenant, permission, and authorization boundaries never crossed Deterministic policy check
Business rules — (no variation permitted) Financial and eligibility limits respected Deterministic comparison
Latency Reasonable range Stays under defined budget for task type Deterministic threshold
Cost Reasonable range Stays under defined budget for task type Deterministic threshold
Communication Exact wording of explanation Content is accurate and supported by evidence Semantic / rubric-based

Testing the validator itself

An easy trap, once a team has built a validation layer along these lines, is to treat it as a fixed, trustworthy source of truth — forgetting the validator is itself software, with the same fallibility as anything else, and needs testing with the same rigor applied to the system it's validating.

The direct way to do this: supply known-good traces and confirm the validator correctly passes them; supply known-bad traces, constructed specifically to violate a defined invariant or skip an essential state, and confirm it correctly fails them; supply genuinely ambiguous traces to see how it behaves at the boundary. An LLM-based grader inside the validator needs to be evaluated the same way any model-based component is: calibrated against human-reviewed cases, checked for the biases discussed earlier, monitored for drift. Graph-based structural rules need their own test cases confirming they classify states correctly and that the partial-order logic permits the orderings it should while rejecting the ones it shouldn't. The test oracle is software too, and skipping its own testing is exactly how a validator's bugs end up masquerading as either false confidence or false alarms.

Both failure directions covered earlier are worth restating with the full framework in view, since each has a distinct cost. A false negative — failing a correct execution, by insisting on one specific tool order the task never actually required — leads a team to gradually believe their agent is less reliable than it is, and risks eroding trust in the validation layer until genuine alerts get dismissed reflexively. A false positive — passing an incorrect execution because the final text happens to read correctly, without checking whether the required action actually executed or the evidentiary source was legitimate — is the more dangerous direction, and it's exactly the validator that would wave Run D through. Flexible evaluation and permissive evaluation are not the same thing, and confusing them is precisely how a well-intentioned move toward tolerating legitimate variation ends up quietly disabling the checks meant to catch Run D in the first place.

Explainability: a failed run should say why

When a workflow fails validation, engineers need to understand specifically why, and the form a failure report takes affects how quickly that understanding arrives. "Failed: state WRITE_ACCOUNT occurred before state IDENTITY_VERIFIED" tells an engineer exactly what to look at. "Quality score: 0.61" tells them nothing beyond "something wasn't ideal" — it doesn't localize the problem or distinguish a safety violation from a stylistic quibble. Semantic scores remain useful as one input among several, but function best as a supplement to a structural, explainable diagnosis, not a replacement for one — the difference between a fix landing in minutes and an afternoon of re-tracing what the score never said.

A single, universal quality threshold applied uniformly across every task type is a poor fit where different tasks carry genuinely different consequences when they go wrong. Draft marketing copy can tolerate variation that would be unacceptable in a tool authorizing financial transactions — not because one is tested less rigorously, but because the cost of failure differs by orders of magnitude. A more useful approach gates releases based on the severity of what's being risked, the observed frequency of an issue, the type of action involved (a read carries less risk than a write, reversible less than irreversible), and the actual business consequence — rather than one fixed pass-rate number applied as a blanket bar.

What this looks like from the CTO's chair

Translated into the questions that actually drive a release or investment decision: How often does the system complete the real business task, measured against the system of record rather than its own account of itself? How often does it do something genuinely unacceptable along the way, and what's the severity distribution behind that rate rather than one blended number? What does a successful outcome actually cost, and is that cost trending sustainably at real volume? What happens when the input is ambiguous — does the system ask, guess, or escalate, and is that the behavior actually wanted? When a run fails validation, can an engineer get a specific reason within minutes, or does every failure require a manual investigation? And when a new model, prompt, or framework version is being considered, can the two be compared in a way that answers "did anything that matters get worse" rather than only "do the outputs look different"? Each question maps onto a specific piece of the framework above — the correctness envelope, severity-classified failure rates, distributional evaluation, escalation-as-valid-outcome, explainable failure reporting, and differential envelope comparison — which is the point of building the framework: each piece answers a question a real release decision needs answered.

What this means for quality engineering practice

This is not an argument about whether AI changes the composition of a QA team — that's a different question from the engineering one this piece has focused on. What does follow is a concrete shift in what the work consists of: defining invariants and forbidden behaviors precisely enough to be checked automatically; building state abstractions that map raw, noisy observations onto meaningful business states; constructing evaluation datasets that go beyond the happy path; writing behavioral assertions that hold across legitimate variation instead of pinning down one exact trace; designing rubrics specific enough to be explainable; classifying failures by actual severity; tracking scenario coverage across the dimensions that matter; and setting release-acceptance criteria proportionate to real risk.

None of this replaces the testing disciplines that already existed. API, UI, integration, security, and performance testing remain exactly as necessary as before — they're joined by a layer specific to the parts of a system where correctness genuinely can't be pinned to one exact output.

The line between deterministic and flexible, restated plainly

It's worth restating directly, because the natural failure mode after an article like this one is to reach for flexible, semantic, or probabilistic evaluation as a default. That would be the wrong lesson. Where a hard rule exists, test it as a hard rule. Where an exact amount exists, compare it exactly. Where a permission boundary exists, enforce it deterministically, with no model-based judgment call standing between the system and the boundary. Where a business invariant can be encoded as a checkable property, encode it that way rather than leaving it to a rubric. Probabilistic or flexible evaluation belongs specifically where a precise assertion genuinely isn't available — natural-language response quality, where no single correct phrasing exists; genuinely equivalent workflow paths; strategy or approach selection a task under-constrains; explanation completeness; and intermediate behavior that's ambiguous but still acceptable. Applied to that set of properties, flexible does not mean vague — it means defining correctness at the abstraction level the property actually operates at, with the same rigor a deterministic assertion would get.

A maturity progression

Pulling the whole article together into a restrained progression — again, an article framework for organizing the ideas covered here, not an industry standard, and not a claim that every system needs to reach the final stage to be considered mature:

Exact output. Tests require one specific result, one specific path. Appropriate for genuinely deterministic components and nothing beyond them.

Property-based. Tests assert invariants and business properties that must hold across a generated or varied space of inputs, rather than pinning down one exact example at a time.

State-based. Tests validate that necessary milestones were reached and forbidden states were avoided, using the essential/optional/convergent-path distinction and structural techniques like dominator analysis, without requiring one exact sequence.

Distributional. Tests evaluate repeated executions of the same scenario and reason about the resulting distribution of outcomes, constraint violations, and cost, rather than trusting any single run.

Adaptive regression. Production failures continuously and systematically extend evaluation coverage, closing the loop described earlier between what's learned in production and what the test suite permanently retains.

A system doesn't need to sit at the final stage to be well-tested, and a team shouldn't treat reaching stage five as an end in itself. The right stage for a given task depends on that task's actual risk profile — a low-stakes internal tool may be entirely well served sitting at property-based or state-based validation indefinitely, while a task with real financial or safety consequences has good reason to reach for distributional and adaptive-regression practices sooner rather than later.

Where QAtronic fits into this

This shift — from validating one exact path to validating the boundaries within which many paths are legitimately correct — is, in practical terms, what quality engineering for AI-driven and agentic systems increasingly consists of. It's the kind of work QAtronic does directly with engineering teams building agents, AI-assisted products, and probabilistic systems more broadly: defining the invariants that actually matter for a given domain, building the structural and semantic validation layers described throughout this article, and designing evaluation and release practices that scale with a system's actual risk rather than with a single, one-size-fits-all bar.

Returning to the five traces

At the start of this article, five executions of the same task looked like five different programs, and the honest response to that was uncertainty about what a passing test should even check. With the framework developed since, the verdicts are no longer ambiguous.

Run A passes cleanly on every dimension: the correct account is verified before any consequential action, the correct duplicate invoice is identified against real evidence, eligibility is checked against the actual retrieved policy, and the resolution matches. Run B passes for the identical reason, differing only in an ordering that the task never required to be fixed. Run C passes for the same reason again, with one additional, entirely permissible read operation that neither helps nor hurts the outcome. Run D fails — not because its final text looks wrong, which it may not, but because the account it acted on was never actually verified; a global invoice search followed by a name-similarity inference is not identity verification, and the required state that everything downstream depends on never genuinely occurred. Run E passes on outcome, state, and constraint correctness, and separately fails or warns on process quality, because reaching a correct answer through redundant, wasteful work is a real and measurable cost even when it isn't a safety problem — and collapsing that distinction into one binary verdict would hide exactly the information a team most needs from the result.

The goal of testing software like this was never to force every successful run to look identical to the ones already on file. It's to make the boundaries of acceptable variation precise enough that everything genuinely inside them is free to differ, and everything genuinely outside them gets caught — every time, regardless of which path it took to get there.


FAQ

What is non-deterministic software testing? It's the practice of validating a system where a single input can produce multiple different but equally valid outputs, action sequences, or execution paths — common in AI agents and other probabilistic or environment-dependent systems — by defining acceptable boundaries of behavior rather than one fixed expected result.

Why can the same AI agent produce different correct executions? Variation can come from model sampling, provider-side model changes, differences in retrieved data or tool results, environmental timing, or an agent's own planning process choosing a different but equally valid order of operations. None of these necessarily indicate a defect.

How do you test an AI agent without one expected output? By defining a correctness envelope: the required outcome, required states, forbidden behaviors, and operational bounds an execution must respect, while allowing the specific path, tool order, and phrasing to vary freely within those boundaries.

What is an invariant in agent testing? A property that must hold across an entire execution regardless of path — for example, that a financial adjustment never exceeds an authorized amount, or that an action never targets an unverified account. Invariants catch violations without needing to anticipate every path that could produce them.

What is state-based agent validation? An approach that checks whether an execution passed through the states essential to a correct outcome, in a valid relative order, rather than requiring an exact match to a recorded reference trace. Techniques like dominator analysis, borrowed from compiler theory, help identify which states are genuinely essential versus merely present in one recorded example.

Should AI tests run the same scenario multiple times? Generally yes, for anything where a single successful run doesn't provide strong evidence of reliability. Repeated execution reveals distributions — success rate, constraint-violation rate, cost — that a single trial cannot, though the appropriate number of repetitions depends on the task's risk and the variance actually observed.

Can LLM-as-a-judge replace deterministic assertions? No. Model-based grading is useful for genuinely semantic properties like tone or completeness, but it's itself probabilistic and subject to bias, variance, and gaming. Anywhere a property has a single correct, checkable value, a deterministic assertion is the stronger and more appropriate tool.

How do you regression-test an agent when its path changes? By asserting that the correctness envelope — outcome, required states, invariants, and operational bounds — still holds, rather than requiring an identical trace to a prior baseline. A regression is a change in the envelope, not a change in the specific path taken.

How do you test agent tool calls? By checking tool relevance, argument correctness, permitted ordering, side effects, duplication, and authorization — while recognizing that two different tools can sometimes provide equivalent evidence toward the same required state, and shouldn't both be forced through a single fixed sequence.

What is the difference between task success and path correctness? Task success (outcome correctness) asks whether the final result was right. Path correctness asks whether the execution reached that result through a legitimate route — with proper verification, without violating constraints, and without relying on unsupported evidence. A system can satisfy one without the other, which is exactly why both need independent checking.


Sources and Further Reading

Recent posts

September 4, 2026
Saga Compensation Testing: The Rollback No One Checks
September 4, 2026
Post-Acquisition Technical Integration: The First 100 Days
September 4, 2026
Why Coding Interviews Don't Predict Software Quality