The Agent That Did Exactly What You Asked — Wrong
The following scenario is hypothetical. It is constructed to be realistic rather than to describe any particular company or incident.
A B2B SaaS company ships an AI operations assistant inside its product. Revenue operations staff use it to run the kind of bulk work that used to require a CSV export, a spreadsheet, and an hour of careful clicking. One morning, an ops manager types:
"Move all inactive customers from the Q3 winback campaign into the reactivation sequence, but don't touch accounts with an open support case."
The agent goes to work. It queries the CRM for accounts matching the campaign. It applies a definition of "inactive." It calls the support platform's API to check case status for each candidate. It removes accounts from one campaign and adds them to another. It writes an activity record against each account so the change is auditable. Then it reports back:
"Done. 184 accounts moved from Q3 Winback to Reactivation. Accounts with open support cases were excluded."
That response is fluent, specific, appropriately scoped, and reassuring. It names a number. It confirms the constraint the user cared about. A reviewer skimming the conversation would sign off in three seconds.
Now consider three failure modes that could sit underneath that exact sentence.
First: the agent misunderstood "inactive." The product has a formal lifecycle_status field with values including dormant and churned. The company's actual working definition of inactive, the one the ops team uses in conversation, is "no login in 90 days," which is a computed property, not a field. The agent chose lifecycle_status = churned, which is a defensible reading of the words and the wrong reading of the business. Every downstream step executed flawlessly against the wrong population.
Second: the support API degraded partway through. For roughly forty accounts, the case-lookup call timed out. The tool returned an error object rather than a list. The agent — reasonably, in the absence of a rule telling it otherwise — treated an empty or errored result as "no open cases found" and proceeded. Some accounts with live escalations were moved into a marketing sequence they should never have entered.
Third: the number is wrong. The agent issued 184 update calls. Three of them failed on a validation constraint at the API layer and returned 422s. The agent's summary was assembled from its plan, not from a reconciliation of confirmed writes. The user believes 184 accounts moved. The database holds 181.
Any one of these produces the same final message. The natural-language output is not merely unhelpful for detecting the failure — it actively conceals it, because the response is generated from the agent's own account of what it did, and that account is exactly the thing under suspicion.
This is the shift that makes agentic systems different from the chat interfaces that preceded them. The answer is not the product outcome. When an LLM writes a paragraph, the paragraph is the deliverable, and evaluating the paragraph is a complete test. When an agent moves 184 records, the sentence describing the move is a report about a deliverable that lives somewhere else — in a database, a queue, a billing ledger, an audit log. Testing the report and calling it done is like signing off on a deployment by reading the changelog.
[Visual concept: Split panel. Left side shows a polished chat response — "Done. 184 accounts moved." Right side shows the actual system state — 181 rows updated, 40 support-status lookups returned as errors, 3 writes rejected. A dotted line between them labeled "the gap testing has to close."]
This article is about closing that gap. It is organized around a single idea: an autonomous agent is not a model producing an answer. It is a state-changing software system whose behavior unfolds over time, and it has to be tested the way you test systems that change state — by looking at what actually changed, under whose authority, in what order, and with what evidence left behind.
Part I: A Chatbot Answers. An Agent Changes the World Around It.
There is no single authoritative definition of "AI agent," and pretending otherwise is a good way to design tests for a system you do not have. What is useful, for testing purposes, is a functional description of the components in play.
Agentic systems in production SaaS typically combine some subset of: a model doing reasoning and planning; a set of tools or function definitions the model can invoke; APIs and services behind those tools; retrieval over documents, tickets, or knowledge bases; short-term conversational context; longer-lived memory; an orchestration layer that decides how many steps to run and when to stop; a permission and identity model that determines what the agent may reach; and sometimes additional agents that receive delegated subtasks.
You do not need all of these to have an agent worth testing carefully. You need one property: the system can cause effects outside its own output.
That single property changes the failure economics. A response-generation failure produces a bad output. The user reads it, notices it is wrong, and discards it. The cost is a wasted turn and some erosion of trust. An action-taking failure produces a bad state, and bad state persists after the conversation ends, propagates into downstream systems, and is frequently discovered by someone other than the person who caused it.
Concretely, in ordinary SaaS domains, this looks like:
- A CRM field updated on the wrong record, which then feeds a segmentation rule, which then feeds an outbound campaign.
- A support ticket closed, reopened, or reassigned incorrectly, breaking SLA accounting.
- A duplicate customer notification, because a retry fired after a call that had actually succeeded.
- An invoice moved into an approved state without the approval step that the finance workflow assumes happened.
- A read that crossed a tenant boundary, returning data the requesting user was never entitled to see.
- A calendar event created, moved, or cancelled on someone else's calendar.
- A multi-step transaction abandoned halfway, leaving a record in a state the application's own state machine does not expect.
None of this is inherently catastrophic. Severity is a function of two things: the domain, and the permissions. An agent that can only draft text in a marketing tool has a small blast radius even when it is badly wrong. An agent with write access to billing has a large blast radius even when it is usually right. The engineering question is not "is this agent dangerous" but "what is the worst thing this agent is currently permitted to do, and what evidence do we have about how often it does something in that neighborhood?"
That question has no answer if your test suite consists of reading responses.
[Internal link opportunity: AI application testing]
Part II: The First Question Is Not "Was the Answer Correct?"
Two properties are being conflated whenever a team says an agent "worked."
Answer correctness is whether the natural-language response is accurate, relevant, and appropriately scoped — whether it says the right thing.
Execution correctness is whether the system state after the run matches what should have happened — whether the right thing was done.
These are independent, and all four combinations occur in practice.
| Execution correct | Execution incorrect | |
|---|---|---|
| Answer correct | The healthy case. The agent did the right thing and described it accurately. This is what you want, and it is also the case that most demos show. | The dangerous case. The response reads as a clean success while the underlying state is wrong, partial, or over-broad. Nothing in the conversation signals a problem. This is the failure mode that survives manual review. |
| Answer incorrect | The noisy case. The action succeeded but the agent reports failure, reports the wrong count, or hedges unnecessarily. Users lose trust, open tickets, and sometimes retry — which can cause a duplicate action. Annoying, occasionally harmful. | The obvious case. Everything is wrong and it is visible. Users notice immediately. Paradoxically the easiest quadrant to catch and fix. |
Make it concrete. An agent tells a customer: "Your subscription has been cancelled." Two distinct bugs live behind that sentence.
In the first, the cancellation call failed, or was never issued, or targeted a different subscription on the same account. The subscription remains active. The customer is billed next month, contacts support, and the conversation transcript becomes evidence in a dispute. The company's own product told the customer the thing was done.
In the second, the cancellation succeeded but the agent reports failure — perhaps because the tool returned a 202 and the agent interpreted asynchronous acceptance as an error. The customer, believing nothing happened, cancels again through the billing portal. Depending on the implementation, that second cancellation may be idempotent and harmless, or it may create a second cancellation record, a second proration event, and a support ticket.
Both matter. They matter differently. The first quadrant failure is silent and compounds. The second is loud and self-correcting but generates operational cost and duplicate actions. A testing strategy that only measures user-visible satisfaction will find the second and miss the first.
This leads to a principle that runs through the rest of this article. I will call it Outcome Before Eloquence — a framing introduced here for the sake of discussion, not an industry-standard term:
The agent's natural-language response is a claim about reality, not evidence of it. Wherever the system can be independently inspected, verify the state directly and treat the response as a separate artifact to be checked against that state.
The practical form of this is unglamorous. Your agent test does not end when the response is captured. It ends when the test harness has queried the database, the API, the queue, or the audit log and compared what it found to what was supposed to happen.
[Visual concept: The 2x2 grid above rendered as a matrix with the "answer correct / execution incorrect" cell highlighted in a warning color, annotated "invisible to response-only testing."]
Part III: Open the Flight Recorder
To test something that unfolds over time, you need a way to talk about when it went wrong. Borrowing a device from a different discipline: imagine that for any single agent run, you could reconstruct the full sequence of what the system observed, concluded, chose, and did.
I will call this the Agent Flight Recorder. This is an analytical framing introduced in this article to organize testing thinking. It is not an industry standard, a product, or a protocol.
User Intent
↓
Context Assembly (what data was pulled in, from where)
↓
Policy / Instruction Interpretation (what rules were understood to apply)
↓
Plan (what the agent intended to do)
↓
Tool Choice (which capability was selected)
↓
Arguments (what parameters were sent)
↓
Permission Check (what the system actually allowed)
↓
Execution (the call itself)
↓
Environment State Change (what changed outside the agent)
↓
Observation of Result (what the agent learned back)
↓
Follow-up Decision (continue, retry, escalate, stop)
↓
Final Response (what the user was told)
↓
Memory (what persisted for next time)
Each stage is a distinct testing surface with its own failure modes and its own kind of evidence. That is the entire point of the decomposition. The central diagnostic question stops being "did this work?" and becomes:
At which stage did the system stop behaving correctly?
The reason this matters is that agent failures propagate forward and disguise their origin. Consider the shapes this takes:
A team observes that the agent called the wrong API endpoint. The instinct is to fix the tool description or add a rule to the prompt. But the actual cause may sit two stages earlier: context assembly retrieved a stale policy document, and the agent selected the endpoint that the stale document described. The tool-selection logic was working correctly on wrong information.
A team observes that the agent called the right tool with a wrong parameter — a refund of $340 instead of $34.00. The instinct is to tighten argument validation, which is worth doing regardless. But the origin may be interpretation: the pricing policy expresses credits in cents in one system and dollars in another, and the agent inherited a unit ambiguity that no amount of tool-schema tightening resolves.
A team observes that the final response overstated what happened. The instinct is to constrain the response template. The origin may be observation: the tool returned a result the agent did not parse carefully, and the summary was generated from the plan rather than the outcome.
In all three cases, testing the final output tells you only that something is wrong. It gives almost no diagnostic information about what. And in agentic systems, where a single logical task may involve eight model calls and fourteen tool invocations, "something is wrong somewhere in there" is not an actionable defect report.
The rest of this article walks the recorder stage by stage, and then reassembles it into a complete worked example.
[Visual concept: Vertical lifecycle diagram of the thirteen stages, with a small icon beside each indicating what kind of evidence that stage produces — retrieved documents, structured plan, tool-call JSON, authorization decision, database diff, and so on.]
Part IV: The Action Ledger
If the Flight Recorder is the timeline, the Agent Action Ledger is the record you keep for each meaningful action within it. This too is an analytical framework proposed in this article, not a formal standard, and no production system needs to implement this exact schema. The point is traceability: for every action with a side effect, someone should be able to reconstruct what was intended, what was permitted, what was sent, and what changed.
For each significant action, the ledger records:
- Task — the unit of work this action serves
- Relevant context — what the agent was working from
- Intended goal — what the action was supposed to accomplish
- Chosen tool — the capability selected
- Tool arguments — the exact parameters sent
- Authorization context — the identity and scope under which it executed
- Pre-action state — the relevant state before
- Actual side effect — what the system did
- Post-action state — the relevant state after
- Result returned to agent — what the tool told the agent
- User-facing claim — what the agent then told the human
- Memory mutation — what persisted
Applied to the opening scenario, one row looks like this:
| Field | Value |
|---|---|
| Task | Move inactive Q3 Winback accounts to Reactivation, excluding open support cases |
| Relevant context | CRM segment query result (412 accounts); campaign membership list; policy doc campaign-transitions-v4 |
| Intended goal | Exclude account acct_8831 if it has an open case; otherwise move it |
| Chosen tool | get_open_cases |
| Tool arguments | { "account_id": "acct_8831", "status_in": ["open","pending"] } |
| Authorization context | Service identity agent-revops, scope support:read, tenant t_4417 |
| Pre-action state | Account is member of camp_q3_winback; support system has 1 case in pending |
| Actual side effect | None — read-only call; request timed out after 5s |
| Post-action state | Unchanged |
| Result returned to agent | { "error": "upstream_timeout", "cases": null } |
| User-facing claim | (none at this step) |
| Memory mutation | None |
| Next action taken | update_campaign_membership — account moved. Timeout was treated as "no cases." |
The defect is visible in a single row. Not from the final answer, and not from any individual component's own logs — the support API correctly reported a timeout, and the campaign API correctly performed the update it was asked to perform. The failure is in the join between them, which is precisely the layer that traditional per-service testing does not cover.
A ledger of this shape earns its keep in several places at once. In testing, it gives assertions something concrete to bind to. In debugging, it collapses "the agent did something weird" into a specific row. In incident analysis, it answers the first question an incident review always asks: what exactly did the system do, to which records, under whose authority? In regression investigation, it lets you compare two runs of the same scenario across a model or prompt change without demanding they be identical. In security review, it is the artifact that shows whether an action stayed inside its intended scope. And in evaluation, it provides the structured input that graders operate on.
The recurring objection is cost: this is a lot of instrumentation for a feature that "just calls a few APIs." The counter-observation is that teams end up building most of it anyway, one incident at a time, under time pressure, in a form that is not queryable. Deciding upfront which fields matter is cheaper than reconstructing them from application logs at 2 a.m.
[Visual concept: A ledger row rendered as a horizontal band, with the "actual side effect" and "post-action state" cells emphasized and connected by an arrow to a small database icon showing a diff.]
Part V: Deterministic Software Meets Nondeterministic Decision-Making
Traditional software testing rests on an assumption so foundational it is rarely stated: for a given input and a given system version, there is one correct output, and it is the same every time. add(2, 2) returns 4. It returns 4 on Tuesday. It returns 4 under load. The test asserts equality and the assertion is meaningful.
Agentic tasks do not always have this shape. Consider: "Find the account most at risk of churn in the enterprise segment and draft the next outreach step." There may be several defensible identifications, several reasonable search strategies to arrive at them, and an unbounded number of acceptable phrasings for the draft.
It is tempting to summarize this as "AI is random." That is both wrong and unhelpful. The behavior is variable and probabilistic: the model samples from a distribution over next tokens, and that distribution is shaped by the prompt, the context, the tool definitions, the model version, and sampling parameters. Even at temperature zero, practical determinism is not guaranteed across infrastructure, batching, and model updates. But this is a long way from randomness. Behavior is strongly patterned, sensitive to input in mostly comprehensible ways, and — critically — much of what surrounds it remains fully deterministic.
The distinction that actually matters for test design is between output variability and outcome correctness.
Output variability is the agent phrasing the same correct result three different ways, or reaching the same correct end state by two different tool sequences. Nothing is wrong. An assertion that fails on this is a bad assertion — it encodes an accidental property of one recorded run as a requirement.
Outcome correctness is whether the end state and the side effects are right. This is frequently not variable at all.
Which is why exact string matching, the workhorse of deterministic testing, becomes a poor primary tool here — while a large amount of agent behavior remains perfectly amenable to hard, binary assertions. Every one of the following is deterministic, checkable, and admits no judgment call:
- The computed refund must not exceed the policy ceiling for that plan tier.
- The
admin_delete_workspacetool must never appear in the trace for this user role. - After a successful run, exactly one row must exist in
credit_memoswith the expected amount and account ID. - No record belonging to a tenant other than the requesting tenant may be read or written.
- If the requested change exceeds the auto-approval threshold, an approval record must exist and must precede the mutation.
- The same idempotency key must not produce two committed transactions.
None of these require an LLM judge. None require statistical treatment. They are ordinary assertions against ordinary system state, and they happen to cover most of what actually hurts when an agent misbehaves.
This produces the operating rule for the rest of the article:
Use deterministic assertions wherever reality gives you deterministic truth. Reserve probabilistic evaluation for the parts of the task that genuinely require judgment.
Teams that get this backwards — grading everything with a model because the system "is AI" — end up with expensive, slow, noisy test suites that are worse at catching the failures that matter than a handful of SQL queries would be.
Part VI: Test the State Delta
If the response is not the deliverable, what is? The change to the world.
State Delta Testing — again, article terminology rather than a standard — reframes the assertion target. Instead of asking "what did the agent say?", the test asks:
What is the difference between the environment before the run and the environment after it, and does that difference match what should have happened?
STATE BEFORE → AGENT EXECUTION → STATE AFTER
Assertion: Expected State Delta == Actual State Delta
The mechanics are familiar to anyone who has written integration tests against a database. Snapshot the relevant slice of state before the run. Execute. Snapshot again. Diff. Compare the diff to expectations. What is different is the emphasis: with an agent, the set of things that might change is much wider than the set of things you asked to change, so the diff needs to be taken over a broader surface than a conventional integration test would bother with.
Examples of the assertions this produces:
| Expected delta | Actual delta observed | Failure class |
|---|---|---|
| Exactly 1 support ticket created | 2 tickets created | Duplicate side effect — usually retry-related |
| Invoice status unchanged until approval recorded | Invoice moved draft → paid |
Sequence violation — approval gate bypassed |
CRM owner_id changed on 1 account |
owner_id and billing_address changed |
Over-broad mutation — argument or tool-scope failure |
| 12 accounts moved between campaigns | 12 moved, plus 1 contact.email overwritten |
Collateral write — often a tool with broader effect than its name implies |
| No writes at all (read-only request) | 1 activity note written | Unnecessary action — the agent "helpfully" logged something |
The last two rows point at the property that distinguishes agent testing from ordinary integration testing. In a conventional test, you assert that the thing you asked for happened. With an agent, you must also assert that nothing else did.
This is the difference between positive assertions and negative assertions, and for autonomous systems the negative ones frequently carry more risk coverage than the positive ones.
Positive assertions say: the intended change occurred, on the intended records, with the intended values.
Negative assertions say: no other record was modified; no other table was touched; no message was sent; no external webhook fired; no tool outside the expected set was called; nothing was written to memory; no record in another tenant was read.
Negative assertions are harder to write, because they require you to enumerate the surface over which "nothing should happen" is being claimed. They are also the assertions that catch the failures nobody anticipated — which is, by definition, most of the interesting ones. A practical compromise is to define, per scenario, a mutation scope: the set of tables, resources, or external effects the scenario is permitted to touch. Assert that the actual mutation set is a subset of the permitted set. This is far more tractable than enumerating every possible unwanted effect, and it scales as the system grows.
[Internal link opportunity: regression testing]
[Visual concept: Two database snapshots side by side, with three rows highlighted in the "after" state — one green (expected change), one amber (unexpected but harmless), one red (unexpected and out of scope) — under the heading "the delta is the deliverable."]
Part VII: The Permission Envelope
Every agent operates inside a boundary, whether or not anyone has written that boundary down. I will call the explicit version the Permission Envelope — an analytical model proposed in this article — and define it as:
The set of actions, resources, data scopes, and transaction sizes an agent is allowed to reach in a particular context.
The phrase "in a particular context" is doing real work. Envelopes are not global. The same agent may have a wide envelope when an admin invokes it and a narrow one when a read-only user does. It may have write access in one workspace and none in another. It may be permitted to draft a refund at any size and to execute one only under a threshold.
A worked envelope for the operations assistant might look like:
| Capability | Envelope |
|---|---|
| Read contacts, accounts, campaigns in requesting tenant | Allowed |
| Read any resource in another tenant | Never |
| Delete contacts | Never |
| Update campaign membership | Allowed, up to 500 records per run |
| Draft a refund or credit | Allowed, any amount |
| Execute a credit | Allowed below the plan-tier threshold; above it, requires recorded human approval |
| Send customer-facing email | Allowed only from approved templates; never free-form |
| Modify billing configuration | Never |
The critical engineering point is this: the envelope must be tested independently of the prompt.
It is extremely common to find that an organization's answer to "can the agent delete a contact?" is "the system prompt tells it not to." That is not a control. It is an instruction to a probabilistic system that is, by construction, capable of producing outputs its instructions did not anticipate — and that is exposed to external content which may contain conflicting instructions of its own. Prompt-level guidance is a useful layer for shaping behavior. It is not a boundary.
The distinction matters enough to state plainly:
"The system prompt says don't do this" is not equivalent to "the architecture prevents this."
Defense in depth here means that a prompt-level rule, a tool-level restriction, and a server-side authorization check all exist, and that the outer ones do not depend on the inner ones holding. The mechanisms available are ordinary security engineering, applied to a new caller:
- Server-side authorization on every tool-backed endpoint, evaluated against the invoking identity, not against the agent's stated intent.
- Scoped credentials so that the token the agent holds cannot express the actions it is not allowed to perform.
- Tool-level permissions so that the toolset presented to the model in a given context already excludes what is out of scope.
- Approval gates for actions above a risk or value threshold.
- Resource and transaction constraints — record counts per run, monetary ceilings, rate limits.
- Sandboxing for anything that executes code or reaches arbitrary network destinations.
- Least privilege, applied per context rather than per agent.
The relevant testing questions follow directly. For each capability outside the envelope: if the agent attempts it, does the attempt fail at the authorization layer rather than merely being declined by the model? Can that be demonstrated? A useful and slightly uncomfortable exercise is to construct a test that calls the tool endpoint directly, with the agent's own credentials, bypassing the model entirely. If the call succeeds, the model was your only control.
OWASP's Top 10 for Agentic Applications, published by the GenAI Security Project in December 2025, places identity and privilege abuse (ASI03) high on its list precisely because agents tend to inherit human identities and their accumulated permissions. The framing that has emerged around that guidance — sometimes described as least agency — extends least privilege with a second dimension: not only what an agent can reach, but how much latitude it has to act on that access without checking back.
[Internal link opportunity: AI security testing]
[Visual concept: Concentric rings labeled from inside out — "prompt guidance," "tool exposure," "scoped credentials," "server-side authz." An arrow representing an attempted out-of-scope action penetrates the first two rings and stops at the third, with the caption "which ring is actually holding?"]
Part VIII: Tool Selection Is Part of the Product Behavior
Here is a failure mode that surprises teams the first time they encounter it: every tool works perfectly, every API test passes, and the feature is still broken.
Suppose the operations assistant has four tools:
search_customer(query, limit)
get_open_cases(account_id, status_in)
update_campaign(account_id, from_campaign, to_campaign)
send_email(account_id, template_id, variables)
Each has a contract test. Each returns correct results for valid inputs and appropriate errors for invalid ones. Each is fast, idempotent where it should be, and well documented. The API layer is in excellent shape.
The agent then:
- calls
send_emailbefore callingget_open_cases, notifying a customer who was in the middle of an escalation; - calls
update_campaignwhen the user asked only for a preview of what would change; - calls
search_customerwith a broad wildcard query, pulling 800 records into context when an exact-match lookup on a known ID was available and appropriate.
Nothing here is a tool implementation bug. All three are orchestration failures — the model made a decision about which capability to use, when, and with what breadth, and the decision was wrong. This is product behavior, and it is behavior that lives nowhere in your API test suite.
Anthropic's engineering guidance on writing tools for agents frames this well: a tool is a contract between a deterministic system and a non-deterministic caller, which makes tool naming, descriptions, and parameter documentation a form of prompt engineering rather than mere documentation. Two tools with overlapping names and vague descriptions will be confused by the model in ways that no amount of testing the tools individually will reveal. That vendor guidance is worth reading directly; the testing implication is that tool surface design is a variable your evaluations should be sensitive to.
Evaluating tool use means covering several distinct properties:
- Correct tool choice — was the selected capability appropriate for the intent?
- Unnecessary calls — did the agent invoke tools that contributed nothing, burning latency, cost, and context?
- Missing calls — did it skip a step it was required to take, such as a verification lookup?
- Unsafe ordering — did a side-effecting call precede a check that should have gated it?
- Argument correctness — right entity IDs, right units, right filters, right scope?
- Duplicate side-effecting calls — was the same mutating action issued twice?
The trap when writing these evaluations is over-specification. If your assertion is "the trace must contain exactly these five calls in exactly this order," you have encoded one recorded run as a specification. The first time the model finds a more efficient path — combining two lookups, or skipping a redundant fetch because the data was already in context — your test fails and reports a regression that is actually an improvement. Teams that do this eventually stop trusting their own suite, which is worse than having no suite.
The productive distinction is between mandatory invariants and acceptable path variation. Mandatory invariants are properties that must hold on every valid path. Acceptable variation is everything else. Assert the first, ignore the second. A well-formed tool-use assertion looks less like a script and more like a set of constraints:
{
"must_call": ["get_open_cases"],
"must_precede": [["get_open_cases", "update_campaign"]],
"must_not_call": ["send_email", "delete_contact"],
"max_calls": { "search_customer": 3 },
"argument_constraints": {
"update_campaign": { "to_campaign": "camp_reactivation" }
}
}
That formulation tolerates a dozen different valid execution paths while still failing loudly on the ones that matter.
[Internal link opportunity: API testing]
Part IX: Action Order Can Be a Correctness Property
Some steps commute. Some do not. Confusing the two is how correct-looking agents produce incorrect systems.
A sound sequence for a change to a customer's subscription might run:
verify identity → fetch account → check authorization → apply change → confirm result
An unsound one runs:
apply change → check authorization
The second sequence may produce the identical end state on the happy path — the user was authorized, the change was legitimate, the check passes retroactively. It is still wrong, because the property being violated is not about the outcome. It is about whether the system ever entered a state where an unauthorized change was live.
I will call the rules that govern this Sequence Invariants, once more as article framing:
A sequence invariant is a rule that must hold regardless of which valid execution path the agent chooses.
Useful ones in ordinary SaaS domains:
- Authorization must precede mutation.
- Approval must precede any irreversible action.
- Identity verification must precede disclosure of account-specific data.
- A read-only user request must not trigger any mutation.
- A confirmation message to a user must correspond to an actually committed transaction, not to an issued request.
- A record must be read at its current version before being overwritten, so that a stale-context write is detectable.
Sequence invariants have a property that makes them attractive as tests: they are usually cheap to check and expensive to violate. Checking them requires only that the trace record which calls happened in which order, which any reasonable instrumentation already captures. Violating them tends to produce exactly the incidents that end up in a post-mortem.
The discipline required is restraint. It is tempting, having discovered that ordering matters, to specify the whole chain. Resist that unless the product genuinely requires one canonical path. Most workflows have a small number of genuinely mandatory orderings surrounded by a large amount of legitimately flexible sequencing. Write down the small number. Let the rest float.
One further subtlety: sequence invariants can be violated across turns, not just within a single run. If an agent verifies identity in turn one and performs a sensitive action in turn nine, the invariant is technically satisfied but the security property may not be, because eight turns of intervening content — some of it possibly from untrusted sources — sit between the check and the action. Whether that matters depends on your threat model, but it should be a deliberate decision rather than an accident of how the conversation happened to unfold.
Part X: Multi-Step Errors Compound
A single small error early in an agent workflow does not stay small. It becomes the premise for every subsequent decision, and each subsequent step executes correctly given that premise.
Trace the shape:
wrong customer record retrieved
↓
wrong subscription tier read
↓
wrong refund policy applied
↓
wrong credit amount calculated
↓
correct API called, correctly, with those arguments
↓
wrong customer's account credited
Every component behaved to specification. The retrieval service returned the record matching the query it was given. The policy engine applied the rule matching the tier it was given. The billing API created the credit it was asked to create. There is no component-level defect anywhere in this chain, and there is no component-level test that would have caught it. The failure is in composition.
OWASP's agentic risk list names this pattern directly as cascading failures (ASI08): small errors propagating across planning, execution, and memory, amplifying as they go. In multi-turn systems the amplification is worse, because an early error can be written into memory or summarized into a compacted context, at which point it stops being an observation and becomes a fact the agent no longer questions.
The testing implication is that you cannot evaluate only the final outcome, and you cannot evaluate only the intermediate steps. You need both, for different reasons.
Final-outcome evaluation tells you whether the run was acceptable. It is the ground truth about whether harm occurred. But it gives you almost no signal about why, and — more subtly — it will pass runs that got the right answer for the wrong reason, which are runs that will fail next week when the accident that saved them does not recur.
Intermediate evaluation tells you where the reasoning or the retrieval went wrong. It localizes the defect. But on its own it over-constrains, because it can flag legitimate path variation as failure.
Three terms recur in the literature and in vendor tooling, and they are worth distinguishing precisely, because teams use them interchangeably and then talk past each other:
- A trace is the structured, end-to-end record of one run: model calls, tool calls, tool results, guardrail decisions, handoffs, timings. OpenAI's agent evaluation documentation defines it in essentially these terms and treats it as the primary object that graders score.
- A trajectory is the sequence of states and actions the agent moved through — the path taken. In practice the trajectory is what you extract from the trace when you care about decisions rather than mechanics.
- A transcript is the conversational surface: the messages exchanged between user and agent. It is a strict subset of the trace, and it is the only part most teams look at.
Testing a transcript is testing the smallest and least informative slice of what happened.
Part XI: Traces Are Not Just Debug Logs
Most engineering organizations already log agent runs. Very few have traces they can assert against, which is a different thing.
A log answers "what happened?" for a human reading it during an incident. A trace, for testing purposes, is a structured artifact designed to be queried programmatically. The difference is whether a grader can ask "did tool X run before tool Y, with argument Z, under identity W?" and get a reliable answer without parsing prose.
A trace useful for agent testing typically contains some combination of:
- model interactions (which model, which version, which configuration)
- tool selections and the full argument payloads
- tool results, including errors, timeouts, and partial responses
- guardrail and policy decisions, including which rule fired and what it decided
- handoffs between agents or sub-agents
- retries, with the reason each retry was triggered
- latency per step and in aggregate
- token and cost accounting
- the final result returned to the user
- state mutation identifiers — the IDs of records created or modified, so the trace can be joined to the state delta
That last item is the one most commonly missing and most valuable. A trace that records "called create_credit_memo, got 200" is far less useful than one that records "called create_credit_memo, got 200, credit_memo_id = cm_99213." The second lets you verify, independently, that cm_99213 exists, has the right amount, is attached to the right account, and is the only one created.
There is one thing your traces do not need, and it is worth being explicit because the assumption creates real problems: you do not need the model's private reasoning to test an agent reliably.
This matters for several reasons. Access to internal reasoning tokens varies by provider and model and is not something to build a testing strategy on. Reasoning text is not a reliable account of the computation that produced a decision — it is generated text, subject to the same variability as any other output. Storing it can create retention and privacy questions you did not intend to take on. And treating it as ground truth invites a specific error: grading whether the agent's stated rationale sounds good, rather than whether its actions were correct.
Everything you actually need is observable at the interface: which tools were called, with what arguments, in what order, under what identity, with what results, producing what state changes, yielding what output. That is the executable record. If a decision is important enough to test, make it observable as a structured artifact — a plan object, a classification, a policy decision — rather than trying to infer it from prose.
Trace Assertions are the checks written against this artifact. They tend to fall into a few families:
- Ordering: did the authorization tool run before the mutation?
- Prohibition: did the agent attempt any tool outside the permitted set for this context?
- Recovery: after a transient failure, was a retry performed, and was it bounded?
- Idempotency: was the same side-effecting tool called twice with equivalent arguments?
- Grounding: does the tool result actually support the claim the agent made to the user? If the response says 184 accounts were moved, do 184 successful update results exist in the trace?
- Efficiency: did the run stay within its expected budget of tool calls and model calls?
The grounding assertion deserves emphasis, because it directly addresses the opening scenario. It is a cheap, deterministic check that compares a number in the user-facing response against a count derivable from the trace, and it catches an entire class of confident-but-wrong summaries.
Trace assertions become brittle in predictable ways. They break when they encode incidental structure (exact call counts on non-mutating lookups, exact orderings between independent steps, exact model versions). They stay robust when they encode invariants that a domain expert would recognize as rules rather than as observations. A useful heuristic before writing one: if the agent found a better way to do this, would this assertion still be right? If the answer is no, you are writing a snapshot, not a test.
[Visual concept: A trace rendered as a horizontal timeline of spans — model call, tool call, tool call, guardrail decision, tool call — with three assertion badges attached above it (ordering, prohibition, grounding) and a database-diff strip running underneath, aligned to the spans that caused each change.]
Part XII: Outcome Grading vs. Path Grading
Once traces exist, a design question arises immediately: how much of the path should the evaluation actually constrain?
There is no universal answer, and the temptation to pick one is the source of most bad agent test suites. The right approach is per-scenario, and it depends on whether the path carries a correctness or safety property of its own.
Outcome grading asks only whether the end state is right. It is maximally tolerant of how the agent got there.
Path grading asks whether the route satisfied constraints, regardless of whether the destination was reached.
Consider a task: locate ten accounts matching a complex set of criteria. There are legitimately many search strategies — a broad query then filtering, several narrow queries, a saved-segment lookup, an iterative refinement. Constraining the path here buys nothing and costs a great deal in brittleness. Grade the outcome: are the ten accounts correct, complete, and free of accounts that should not be there?
Now consider: change the payment method on an enterprise subscription. The end state might be identical whether or not a security verification occurred. The verification is not instrumental to the outcome; it is part of the required behavior. Here, path grading is mandatory and outcome grading is insufficient.
| Scenario | Outcome matters? | Path matters? | Why |
|---|---|---|---|
| Find 10 accounts meeting complex criteria | Yes | No | Many valid search strategies; over-specifying makes the test brittle without adding coverage |
| Summarize an account's support history | Yes | Weakly | Sources used matter for grounding; retrieval order does not |
| Issue a credit above the auto-approval threshold | Yes | Yes | Approval must precede the mutation; identical end state without approval is a control failure |
| Change payment method | Yes | Yes | Identity verification is a required step, not an implementation detail |
| Move accounts between campaigns | Yes | Partly | Case-status check must precede the move; the order of the moves themselves is irrelevant |
| Answer a read-only analytical question | Yes | Yes (negative) | No mutation may occur on any path; this is a path constraint expressed as a prohibition |
| Draft a customer email | Yes | No | Content quality is the deliverable; how the agent gathered context is unconstrained |
The last column is the one to write first. If you cannot articulate why a path constraint exists in terms a product or security stakeholder would recognize, it probably should not exist.
One caution worth stating explicitly, because agent demos encourage the opposite instinct: a creative solution is not automatically a safe solution. When an agent finds an unexpected route to the right answer, that is sometimes ingenuity and sometimes a control gap that happened to produce an acceptable result this time. The way to tell them apart is to check the route against the invariants — not to admire the outcome.
Part XIII: How Do You Test Something That May Behave Differently Each Run?
This is the question that most often stalls agent programs, and it deserves the most space.
Start with the uncomfortable observation. In deterministic software, a passing test is strong evidence. Run it once, it passes, the behavior is established for that version. In a system with probabilistic decision-making, one passing run is weak evidence. It tells you the agent can succeed on that task. It tells you very little about whether it will.
That distinction — capability versus reliability — is the single most important idea in this section, and it is not a philosophical point. It has a direct operational consequence: a demo proves capability. A release decision requires reliability. Confusing the two is how teams ship agents that work beautifully in the sales call and generate support tickets in week two.
The vocabulary
A small amount of shared vocabulary prevents a lot of confusion:
- A task is a single unit of work with a defined starting environment and a definition of success. "Move the inactive Q3 Winback accounts, excluding open cases, given this seeded CRM state."
- A trial is one execution of that task. Because behavior varies, a task has many possible trials.
- A dataset is a curated collection of tasks, versioned, with their environments and success criteria.
- A grader is the mechanism that decides whether a trial succeeded. It may be a deterministic check against state, a trace assertion, a model-based judgment, or a human.
- An evaluation suite is the dataset plus the graders plus the execution harness plus the reporting.
Vendor tooling converges on roughly this structure. OpenAI's documentation for evaluating agent workflows describes starting with trace grading while behavior is still being debugged, then moving to datasets and repeatable eval runs once you know what "good" looks like — traces for diagnosis, datasets for repeatability. Anthropic's guidance on tool design similarly treats evaluation as the mechanism that drives iterative improvement rather than as a gate applied at the end. Both are vendor guidance about vendor platforms; the structural pattern they describe is general.
Multiple trials
If one trial is weak evidence, the response is to run more than one. The obvious next question — how many? — has no universal answer, and any article that gives you a number is giving you a number it made up.
What determines the count:
- Risk. A task that can move money, delete data, or contact customers warrants more trials than one that drafts an internal summary. High-consequence tasks should be run enough times that a rare failure has a reasonable chance of appearing.
- Observed variance. If a task produces identical outcomes across five trials, additional trials add little. If it produces three different outcomes across five trials, you have found something and should look harder. Let measured variance drive the count rather than a fixed policy.
- Cost and latency. Agent trials are expensive — many model calls, many tool calls, real wall-clock time. A suite of 200 tasks at 10 trials each is 2,000 full agent runs. That is a real infrastructure and budget decision, not a footnote.
- Stage. During development, a handful of trials on a small set of hard cases gives fast signal. Before a release involving a model change, a broader suite with more repetitions is justified. In production, continuous sampling of real traffic supplements both.
A workable pattern: single-trial coverage across a broad dataset for smoke-level signal, higher repetition on a smaller critical set of high-risk tasks, and variance measurement on anything new. Increase repetitions where you observe instability rather than uniformly.
The metrics that matter
Several complementary measurements are worth tracking. None is sufficient alone.
Task success rate — across all trials of all tasks, what fraction succeeded? Simple, useful for tracking direction, and dangerously easy to over-read because it averages across risk tiers.
First-attempt success — did the agent succeed without needing retries, clarification, or human intervention? This separates "eventually got there" from "got there cleanly," which matters enormously for user experience and cost.
Consistency — of the tasks the agent solved at least once, how many did it solve every time? This is the reliability measure, and it is usually much lower than the success rate.
Failure distribution — where do failures cluster? Ten failures spread evenly across a hundred tasks is a different situation from ten failures concentrated on the three tasks that involve authorization. The average hides this completely.
pass@k and pass^k
Two similar-looking notations appear in agent evaluation literature and mean nearly opposite things. Getting them straight is worth the paragraph.
pass@k originates in code-generation evaluation and measures whether at least one of k sampled attempts succeeds. It rises as k rises. It measures potential — can the system produce a correct solution if given several chances? This is the right metric when a downstream verifier will select the good attempt: a compiler, a test suite, a human reviewer.
pass^k (written pass-hat-k) measures whether all k independent trials succeed. It falls as k rises. It measures reliability — will the system behave correctly every time? This is the right metric when there is no downstream selector, which is the situation for most autonomous agents acting on production systems.
The metric was introduced in τ-bench, a benchmark for tool-agent-user interaction published in 2024 by researchers at Sierra and Princeton and later presented at ICLR. Two design choices in that benchmark are worth noting independently of its scores. First, it evaluates by comparing the database state at the end of a conversation against an annotated goal state — outcome-based grading against system state, exactly the pattern this article argues for. Second, its policy-compliance scoring evaluates each trajectory against a written domain policy document, separating "did the user get what they wanted" from "did the agent follow the rules."
The benchmark's original results showed a substantial gap between capability and consistency for the models tested at the time — a leading function-calling model of that period succeeded on fewer than half of tasks at single-trial measurement and considerably fewer when required to succeed across eight independent trials in the retail domain. Those specific figures are properties of specific 2024 models on a specific benchmark and should not be read as a general statement about agent reliability today; frontier model performance on these benchmarks has moved considerably since. What generalizes is the shape: consistency is materially harder than capability, and a metric that averages across trials will systematically overstate how ready a system is.
For a founder or CTO, the translation is short:
"Our agent handles this task" is a claim about pass@1 or pass@k. "Our agent can be trusted to handle this task unattended" is a claim about pass^k. Only the second justifies removing a human from the loop.
What to do with the variance you find
Finding that an agent behaves inconsistently is not a dead end. It is a diagnostic input. Inconsistency clusters, and where it clusters tells you what to fix:
- Inconsistency in tool selection usually points at ambiguous or overlapping tool descriptions.
- Inconsistency in interpretation usually points at a business rule that exists in people's heads rather than in retrievable, unambiguous form.
- Inconsistency in arguments often points at a schema that permits ambiguity — units, formats, optional fields with load-bearing semantics.
- Inconsistency in stopping — sometimes the agent does one extra step, sometimes not — usually points at an underspecified task boundary.
- Inconsistency that appears only under failure injection points at missing error-handling policy rather than at the model.
In several of these cases, the fix is not in the model or the prompt at all. It is in making the deterministic parts of the system less ambiguous, which is a normal engineering activity with normal engineering tests.
Part XIV: Traditional Tests vs. Evals
One of the most common practical questions from engineering leaders: what belongs in our existing automated test suite, and what needs the new evaluation machinery?
The short answer is that agentic systems do not replace conventional QA — they add a layer on top of it. A broken API is still a broken API. A missing database constraint is still deterministic and still catchable with a unit test. What changes is that a new category of behavior exists, produced by model-driven choices, and that category is not addressable by assertions written against fixed inputs.
| What is being tested | Traditional automated test | Agent eval | Both | Reasoning |
|---|---|---|---|---|
| API implementation correctness | ✔ | Deterministic contract; standard integration testing applies unchanged | ||
| Database constraints and integrity | ✔ | Fully deterministic; belongs in the data layer's own tests | ||
| Authentication | ✔ | Identity verification is deterministic regardless of caller | ||
| Authorization / permission enforcement | ✔ | ✔ | Enforcement is deterministic and must be unit-tested; whether the agent attempts out-of-scope actions requires evals | |
| Tool schema validity | ✔ | Schema conformance is a contract test | ||
| Tool execution behavior | ✔ | Given valid arguments, behavior should be deterministic | ||
| Business workflow invariants | ✔ | The invariant is deterministic; whether the agent respects it across varied inputs is probabilistic | ||
| Tool selection | ✔ | Entirely a model-driven decision | ||
| Tool argument construction | ✔ | Validation is deterministic; argument quality given ambiguous intent is not | ||
| Free-form response quality | ✔ | Requires judgment; graded by model or human | ||
| Instruction following | ✔ | Varies with phrasing, context, and model version | ||
| Planning quality under ambiguity | ✔ | No single correct plan exists | ||
| State-change correctness | ✔ | State assertion is deterministic; reaching the right state is probabilistic | ||
| Prompt injection resistance | ✔ | ✔ | Adversarial evals find behavior; architectural controls are tested conventionally | |
| Memory behavior | ✔ | Retention and scoping rules are deterministic; what gets written is model-driven | ||
| Multi-step task success | ✔ | The composite behavior is the thing being measured |
Two rows deserve comment because they are the ones teams most often get wrong.
Authorization appears in both columns, and this is not a hedge. The enforcement mechanism — does the server reject an out-of-scope call — is deterministic and must be covered by conventional tests that do not involve the agent at all. Separately, how often the agent attempts out-of-scope actions is a behavioral property measured by evals. A system can have flawless enforcement and an agent that constantly tries to exceed its scope; that is not a security incident but it is a strong signal that something in the design is confusing the model.
State-change correctness also appears in both, for the reason developed in Part VI: the assertion is deterministic, but whether the run produces the asserted state is not. The eval provides the runs; the deterministic grader provides the verdict. This combination — probabilistic execution, deterministic grading — is the single most valuable pattern in agent testing, and it is available far more often than teams assume.
[Internal link opportunity: test automation]
[Visual concept: Two overlapping circles. Left circle "deterministic verification: APIs, schemas, constraints, authorization enforcement." Right circle "behavioral evaluation: tool choice, planning, instruction following, response quality." The intersection labeled "state-change correctness, workflow invariants, memory rules, argument quality" — with a caption noting this intersection is where most agent defects live.]
Part XV: Designing an Eval Dataset That Represents Real Work
An evaluation suite is only as good as its tasks, and the default failure is tasks that are too easy in a specific way: they are unambiguous.
Compare two dataset entries.
Weak: "Create a support ticket for account 4471 about a billing question."
There is one entity, one action, no conflict, no missing data, and no judgment required. The agent will pass this reliably. It will pass it after a model change. It will pass it if you break half the retrieval layer. It generates confidence and no information.
Stronger: "A customer contacts support. They have two workspaces under one parent organization, one of which has a suspended subscription. There is a duplicate account record created six months ago that shares the billing email. An escalation opened eleven days ago is still unresolved and references the same underlying issue. Determine whether this contact should create a new case or append to the existing escalation, and act accordingly."
Now the task contains genuine ambiguity, requires the agent to resolve entity confusion, forces a judgment against policy, and has a wrong answer that looks plausible (creating a new case is always technically valid and quietly degrades the support team's ability to see the pattern). This task carries information.
Where the tasks come from
The most valuable sources are the ones that already exist inside the company:
- Real production failures, anonymized. Every time the agent does something wrong in production, that run becomes a permanent dataset entry. This is the highest-signal source available and it costs almost nothing beyond the discipline to capture it.
- Support incidents from before the agent existed. The hard cases your human team escalated are, almost by definition, the hard cases for the agent.
- Product requirements and policy documents. Any rule written down is a rule that can be violated, and each one implies at least one task.
- Historical edge cases. Odd account configurations, legacy data shapes, migration artifacts.
- Existing manual QA scenarios. Teams often have well-thought-out exploratory scenarios that have never been formalized.
- Security review output. Threat modeling produces adversarial cases directly.
- Domain experts. For ambiguous-judgment tasks, someone has to define what "correct" means, and it is not the QA team acting alone.
On production data: using real customer records requires the same safeguards as any other use of production data — anonymization or synthetic derivation, access controls on the eval environment, and a clear position on retention. The practical pattern that works well is to preserve the structure of a real failure (the entity relationships, the timing, the ambiguity) while replacing the actual values. The structure is what makes the case hard; the values are what makes it sensitive.
Composition
A dataset that only contains cases the agent should handle is a dataset that cannot tell you when the agent overreaches. Aim for deliberate coverage across categories:
- Representative cases — the workflows that constitute most real traffic.
- Boundary cases — right at a threshold. A refund of exactly the approval limit. A batch of exactly the maximum record count. A subscription expiring today.
- Negative cases — where the correct behavior is to not act: decline, escalate, ask for clarification, or do nothing. These are systematically underrepresented in most suites.
- Adversarial cases — untrusted content containing conflicting instructions, requests that probe scope boundaries, attempts to get the agent to act on another tenant's data.
- Long-tail workflows — the rare-but-real configurations that account for a small share of volume and a large share of incidents.
Finally, hold some tasks back. A held-out set — cases never used while tuning prompts, tool descriptions, or configuration — gives you an honest read on whether improvements generalize or whether you have been fitting to your own test cases. This is not ML training theory; it is the same discipline that makes you nervous when someone tunes a system against the acceptance tests. The mechanism is simple: keep a portion of the dataset in a separate file, run it only at release checkpoints, and treat a divergence between held-out and tuning-set performance as a signal that your improvements are narrower than they appear.
[Internal link opportunity: SaaS testing]
Part XVI: Grade the Outcome, Not the Story the Agent Tells You
Return to the core idea with the machinery now in place. The agent says: "Done." The test asks: is it actually done?
Answering that requires a grader that touches something other than the agent's output. In most SaaS domains, the checks are ordinary:
- Does the calendar event exist, at the right time, on the right calendar, exactly once?
- Did the database field change to the expected value, and did no adjacent field change?
- Was the email queued once, with the right template, to the right recipient?
- Does the ticket have the correct owner, priority, and linkage to the parent escalation?
- Did the user's permission set remain exactly as it was?
- Does the credit memo total match the policy-derived amount to the cent?
These are deterministic graders. They are fast, cheap, reproducible, and binary. They do not drift. They do not need calibration. They are the backbone of any serious agent evaluation suite, and every hour spent making more of the system's outcomes deterministically checkable pays back repeatedly.
Not everything can be graded this way. Some outputs are genuinely open-ended: the clarity of a summary, the appropriateness of tone in a customer-facing message, whether a plan is sensible given ambiguous input, whether the agent followed a nuanced instruction that resists formalization. Here model-based graders — an LLM scoring the output against a rubric — are a reasonable tool, and often the only scalable one.
They come with a specific caveat that should be stated without softening: an LLM judge is itself a probabilistic system. It has its own variance, its own biases, and its own failure modes. It can prefer longer answers, or answers stylistically similar to its own generations. It is not a source of objective truth; it is a second model whose agreement with human judgment is an empirical question, not an assumption.
The practical consequence is calibration. Before trusting a model grader in a release decision, have humans grade a sample of the same outputs and measure agreement. If the judge and your domain experts disagree on a quarter of cases, the judge is measuring something, but not the thing you care about. Re-examine the rubric — vague rubrics produce vague judgments — and re-check. Track that agreement over time, because a model or prompt update on the grader side changes your measurements without changing your system.
A reasonable division of labor: deterministic graders decide whether the run passes. Model graders provide quality signal on the parts deterministic checks cannot reach. Humans calibrate the model graders and adjudicate the ambiguous-judgment tasks that a rubric cannot capture.
Part XVII: The Agent Can Succeed and Still Be Unsafe
Task success and safe task success are different properties, and a suite that measures only the first will pass runs you would not want to ship.
Two illustrations.
An agent is asked to summarize a customer's history before a renewal call. It produces an accurate, useful summary. It assembled that summary partly from a document in a workspace the requesting user does not have access to. The task succeeded. The means were unauthorized. The user has now seen data they were not entitled to see, and — because the summary reads naturally — nobody will notice.
An agent is asked to update the owner on an account. It does so, correctly. It also normalizes the billing address, because the record had an inconsistency and updating it seemed helpful. The requested outcome was achieved. An unrequested mutation accompanied it, and if that address feeds an invoicing system, the side effect is not cosmetic.
I will call the property that both cases violate Safe Success — an analytical framing used in this article to keep four distinct conditions visible at once:
A run is a safe success when it achieves the intended outcome, through authorized means, with only allowed side effects, in compliance with applicable policy.
This is not a mathematical standard and it does not reduce to a single score. It is a checklist to apply when defining what "pass" means for a scenario. Most eval harnesses default to measuring only the first condition, because it is the one the task description makes obvious. The other three have to be written down deliberately or they will not be checked.
In practice, encoding Safe Success means each scenario carries four groups of assertions rather than one: an outcome assertion, an authorization assertion (which identities and scopes were used), a side-effect assertion (the mutation scope from Part VI), and a policy assertion (the sequence invariants and business rules from Parts VII and IX). It is more work per scenario. It is also the difference between a suite that measures whether the agent is useful and a suite that measures whether it is deployable.
Part XVIII: Prompt Injection Changes the Test Surface
Agents read things. That is most of their value and the origin of a distinctive risk class.
In the course of a normal task, an agent may ingest: retrieved documents, web pages, customer emails, support ticket bodies, CRM notes, file attachments, results from third-party tools, and output from other agents. All of that content arrives through legitimate channels. Some of it was written by people outside your organization. And because the agent processes it as text in the same context window that holds its instructions, content can attempt to function as instruction.
This is indirect prompt injection, and the reason it matters more for agents than for chat interfaces is simple: a chat model that is manipulated produces misleading text. An agent that is manipulated may have tools.
Simon Willison's framing of the "lethal trifecta" captures the architectural condition compactly — the combination of access to private data, exposure to untrusted content, and the ability to communicate externally is what makes injection exploitable. Any two of the three is substantially less dangerous than all three together. That framing is useful precisely because it points at architecture rather than at prompt wording.
The academic and industry consensus has moved in the same direction. The 2025 paper Design Patterns for Securing LLM Agents against Prompt Injections, authored by researchers across ETH Zurich, Google, Microsoft, IBM, and others, proposes structural design patterns that constrain what an agent can do rather than attempting to filter what it reads — and is explicit that as long as agents and their defenses rely on current language models, general-purpose agents are unlikely to offer reliable safety guarantees. The honest reading of that is: you cannot prompt your way out of this, and you should not design as though you can.
What testing should verify, at the level of objectives rather than exploit technique:
- External content cannot expand permissions. If retrieved content attempts to induce an action outside the envelope, the attempt fails at the authorization layer, and the failure is observable in the trace.
- Untrusted instructions do not override policy. Content encountered mid-task does not change what the agent is willing to do, and does not silently redefine the task.
- Sensitive data is not disclosed through channels the agent controls — outbound messages, tool arguments to external services, generated links.
- High-impact actions remain gated regardless of how the request arrived. An approval requirement triggered by policy is not satisfiable by content claiming approval was granted.
- Tool results are handled according to trust boundaries. Content from a customer-submitted field is not treated with the same authority as a system configuration value.
Benchmarks exist for this class of testing — AgentDojo, presented at NeurIPS in 2024, provides a dynamic environment for evaluating attacks and defenses against tool-using agents, and is a reasonable reference point for teams building their own adversarial suites. The important adaptation is that your suite should reflect your untrusted surfaces: the specific fields, integrations, and document sources through which external content actually reaches your agent.
One boundary on this section: agent security is not reducible to prompt injection, and treating injection as the whole problem produces a program that misses identity, memory, supply chain, and delegation risks entirely. That is the next section.
Part XIX: Security Is Not Only About the Prompt
OWASP's Top 10 for Agentic Applications, released in December 2025 by the GenAI Security Project and developed with contributions from more than a hundred practitioners, spans risk categories that reach well beyond input manipulation: goal hijacking, tool misuse, identity and privilege abuse, agentic supply chain, unexpected code execution, memory and context poisoning, insecure inter-agent communication, cascading failures, human-agent trust exploitation, and rogue agents.
Reproducing that list adds little. Translating it into questions a QA or engineering team can actually test against adds a great deal. The following are testing objectives derived from those risk categories, framed defensively.
| Risk surface | Testing question | Kind of evidence |
|---|---|---|
| Tool scope | Can the agent invoke a tool outside its intended scope for this context — and if it tries, does something other than the model stop it? | Trace showing attempt; authorization layer showing denial |
| External content influence | Can content the agent reads cause a privileged action, a disclosure, or a change of task? | Adversarial eval results; trace showing action attribution |
| Identity and privilege | Under which identity does each tool call execute? Does the agent hold standing privileges broader than any single task requires? | Credential scope audit; per-call identity in trace |
| Cross-session contamination | Can state, context, or memory from one user's session influence another's? | Isolation tests with distinct seeded tenants |
| Memory retention | Can the agent's memory retain information it should have discarded, or surface it in a context where it does not belong? | Memory boundary tests (Part XX) |
| Delegation | Can one agent grant another authority it does not itself possess, or that the human never granted? | Handoff trace with propagated scope |
| Supply chain | What tools, connectors, and external services can the agent reach, and who controls their behavior and their returned content? | Tool and connector inventory; provenance of tool definitions |
| Code and command execution | If the agent generates or runs code, what can that code reach? | Sandbox configuration tests; egress controls |
| Trust exploitation | Does the interface present agent claims in a way that encourages users to approve without verifying? | Approval-flow review (Part XXIII) |
On the standards side, formal guidance is still consolidating. NIST's AI Risk Management Framework and its Generative AI Profile (AI 600-1) provide the governance structure most organizations already reference. The Cybersecurity Framework Profile for AI (IR 8596) was published in preliminary draft form in December 2025. NIST's COSAiS project, which is developing SP 800-53 control overlays specifically for single-agent and multi-agent deployments, has published concept and outline material but, as of mid-2026, the agent-specific overlays remain in development. NIST's Center for AI Standards and Innovation launched an AI Agent Standards Initiative in February 2026, and the NCCoE published a concept paper the same month on applying established identity standards — OAuth 2.0, OpenID Connect, SPIFFE/SPIRE — to agent identity and authorization.
The practical reading for an engineering leader: the controls you will eventually be audited against are being written now, they are converging on identity, authorization scope, and auditability, and building those capabilities today is not speculative work. It is the same work, done earlier.
Part XX: Memory Turns Yesterday's Input Into Today's Test Condition
Memory is where agent testing stops resembling anything in a conventional test plan, because memory means the system under test is no longer the same system between runs.
Several distinct mechanisms get called "memory," and conflating them produces confused tests:
- Conversation context — the current turn window. Ephemeral, scoped to one session.
- Session memory — state maintained across turns within a session, including summaries produced by context compaction.
- Persistent memory — facts written across sessions, surviving indefinitely.
- Retrieved profile information — durable attributes pulled from application data (plan tier, role, preferences). Not really memory, but functions like it and often confused with it.
- External knowledge stores — vector databases and document indexes the agent retrieves from, which may themselves be written to by agents or by users.
Each has different failure classes, and the classes are worth naming:
Wrong memory. The agent recorded something inaccurate — a misread preference, a hallucinated fact, an inference stated as observation. It is now a durable premise for future decisions.
Stale memory. The fact was true when written and is not true now. The customer downgraded. The escalation resolved. The owner changed. Memory does not expire on its own.
Cross-user contamination. Information from one user's session influences another's. In multi-tenant SaaS this is not a quality bug but a data-isolation incident.
Inappropriate retention. The agent retained something it should have discarded — sensitive content, a one-off instruction, a value the user intended for a single transaction.
Memory not updated. A fact changed and the write did not occur, leaving stale state where the design assumed freshness.
Memory updated when it should not have been. A transient detail was promoted to durable memory. Common with instructions phrased as preferences: "just this once, skip the confirmation."
Memory Boundary Tests — article terminology — are the checks that pin down the rules. The questions they encode:
- What may be remembered? Which categories of information are eligible for persistence at all?
- For how long? Does anything expire, and is expiry enforced or aspirational?
- For which user, and in which scope? Per-user, per-workspace, per-tenant, per-organization — and is the scope enforced at the storage layer or only in the retrieval prompt?
- Can it influence later decisions? Is remembered content treated as authoritative, as a hint, or as untrusted?
- What must be forgotten? Is there a deletion path, and does it actually remove the content from every store — including derived summaries and embeddings?
- What happens when memory conflicts with current authoritative data? If memory says the plan is Enterprise and the billing system says Growth, which wins? This should be a designed answer, not an emergent one.
That last question is the one most systems have not answered. The right default in most SaaS contexts is that authoritative system state wins and memory is a retrieval hint, not a source of truth — but whatever the answer, it should be testable. A memory conflict test seeds a memory entry that contradicts live data, runs a task whose outcome depends on the difference, and asserts which source the agent acted on.
OWASP's list includes memory and context poisoning (ASI06) as its own category, reflecting that memory is not only a correctness surface but an attack surface: content that reaches memory persists, and persistence is exactly what an attacker wants. Testing memory writes — what gets written, from which sources, under what conditions — belongs in both the quality suite and the security suite.
[Visual concept: A timeline across three sessions with a memory store beneath. A fact written in session one is shown flowing into decisions in sessions two and three, with a red annotation at session three marking "value changed in source system — memory not invalidated."]
Part XXI: Retry Logic Can Create Duplicate Real-World Actions
A tool call times out. The agent does not know whether the operation executed. What should happen next is one of the most consequential design decisions in an agentic system, and one of the least tested.
The naive behavior is to retry. It is also the behavior most orchestration frameworks encourage, because retrying is correct for reads and for idempotent writes. For non-idempotent side effects, it produces duplicates: two support tickets, two customer emails, two credits, two campaign enrollments — one of which nobody knows about.
The situation is a classic distributed-systems ambiguity, and it does not need a textbook treatment here. What it needs is a short list of properties and their testing consequences.
Timeouts are not failures. A timeout means unknown. Treating unknown as failure causes duplicates. Treating unknown as success causes silent omissions. The correct handling is to resolve the ambiguity — by querying for the effect, or by having made the call idempotent in the first place.
Idempotency has to be designed into the tool, not the prompt. An idempotency key supplied by the caller and enforced by the service turns "did this already run?" from a judgment call into a lookup. Where the underlying operation supports it, this eliminates the entire failure class. Where it does not, the agent needs an explicit verification step.
Post-action verification closes the gap. After a side-effecting call whose result is uncertain, query for the effect before proceeding or reporting. This is one extra call and it converts an ambiguous state into a known one.
Transaction identifiers make verification possible. If the tool returns the ID of what it created, and the trace records it, verification is trivial. If it returns only a status code, verification requires a search, which is slower and less reliable.
The test cases that expose this behavior are not exotic, but they will never occur in a happy-path eval suite. They have to be injected deliberately:
| Injected condition | What the test should assert |
|---|---|
| Tool succeeds normally | Exactly one effect; correct user-facing claim |
| Tool clearly fails (4xx/5xx with definite semantics) | Zero effects; agent reports failure accurately; no partial state |
| Tool times out before execution | Retry permitted; exactly one final effect |
| Tool executes but the response is lost | Exactly one effect — the retry must not duplicate it |
| Partial downstream failure (3 of 5 sub-operations commit) | Agent detects the partial state; reported count matches committed count; no silent rounding |
| Repeated transient failures | Retry is bounded; agent escalates rather than looping |
The fourth row is the one that matters most and the one almost nobody tests, because it requires a harness capable of simulating a successful write whose acknowledgment never arrives. Building that capability — usually a proxy or fault-injection layer in front of the tool endpoints — is a modest engineering investment that unlocks an entire category of testing.
These scenarios reveal behavior that happy-path evaluation is structurally incapable of finding. An agent that scores well on a clean suite may have no defined behavior at all under partial failure, and partial failure is not rare in production. It is Tuesday.
Part XXII: Reversibility Changes How Much Autonomy Is Acceptable
Not all agent actions carry the same consequence, and treating them uniformly wastes testing effort on the harmless while under-covering the dangerous. A useful sorting device — article framing, not a business standard — is the Reversibility Tier.
The classification is simple: how hard is it to undo?
Tier 1, easily reversible. The action can be undone with a single operation and no external consequence. Drafting a message. Changing an internal tag. Creating a note.
Tier 2, costly to reverse. The action can technically be undone, but the undo has cost — operational, reputational, or relational. Sending a customer communication. Reassigning ownership. Changing a subscription mid-cycle.
Tier 3, difficult or impossible to reverse. Deletion. External financial transactions. Actions that trigger third-party workflows outside your control. Anything that leaves your system boundary in a way you cannot recall.
The tier should drive testing depth and safeguard selection:
| Action type | Reversibility | Testing depth | Recommended safeguards |
|---|---|---|---|
| Draft message, internal note, tag change | Tier 1 | Outcome grading; light trial counts | Standard authorization; no gate |
| Bulk record update within a tenant | Tier 1–2 | State delta with negative assertions; moderate trials | Record-count ceiling; dry-run mode; batch audit record |
| Customer-facing email or notification | Tier 2 | Idempotency and duplicate-detection tests; higher trials | Template restriction; per-recipient rate limit; send-once key |
| Subscription or entitlement change | Tier 2 | Sequence invariants; failure injection; high trials | Threshold-based approval; reconciliation job |
| Credit, refund, or payment action | Tier 2–3 | Full Safe Success assertions; highest trial counts | Hard monetary ceiling; recorded human approval; independent post-hoc reconciliation |
| Data deletion | Tier 3 | Explicit prohibition tests; adversarial coverage | Typically outside the envelope entirely; soft-delete with retention if permitted |
| External transaction or third-party workflow trigger | Tier 3 | Full assertions plus rollback-path testing | Approval gate; sandboxed rehearsal; explicit confirmation of committed state |
There is no universal policy here — a company whose product is deletion will draw these lines differently. What is general is the relationship: as reversibility decreases, the required strength of evidence should increase. More trials, stricter invariants, narrower permissions, and more frequent recourse to explicit human authorization. An agent may be trusted to act unattended on Tier 1 actions with modest evidence. The same evidentiary standard applied to Tier 3 actions is negligence.
A practical exercise for an architecture review: list every side-effecting tool the agent can reach, assign each a tier, and check whether the current test coverage is proportional. In most systems the answer is that coverage is roughly uniform, which means the Tier 3 actions are under-tested.
Part XXIII: Human Approval Is Itself a Feature That Must Be Tested
"Put a human in the loop" is where many agent risk discussions end. It should be where a testing discussion begins, because an approval gate is a piece of software with its own failure modes — and an approval gate that does not work is worse than none, because it manufactures confidence.
The questions that constitute an approval test suite:
Does approval appear when required? For every condition that should trigger a gate — monetary threshold, action tier, record count, resource sensitivity — does the gate actually fire? Test at, just below, and just above each threshold. Off-by-one errors in threshold logic are common and invisible until the one case that matters.
Can the agent bypass it? Can the same effect be achieved through a different tool that lacks the gate? This is the single most common approval failure: the gate is attached to execute_refund while adjust_invoice_balance reaches the same end state ungated. Approval gates belong on effects, not on tool names.
Does the human see enough context to make a real decision? An approval request that says "approve credit of $2,340?" is a rubber stamp. One that shows the account, the policy clause invoked, the calculation, the source data, and what will change is a decision. This is testable: assert that the approval payload contains the fields a reviewer needs. OWASP's list includes human-agent trust exploitation (ASI09) precisely because interfaces that encourage reflexive approval convert a control into a formality.
Does approval authorize exactly the proposed action — and nothing adjacent? The approved action and the executed action should be bound by an identifier, and the execution path should verify that binding. Approving a $2,340 credit must not authorize a $2,430 one, nor a credit on a different account, nor a credit plus a subscription change.
What happens if the underlying state changes after approval? A human approves a refund. Before execution, the invoice is voided, or the customer's plan changes, or another agent modifies the same record. Executing the approved action against changed state may now be wrong. The system should re-validate preconditions at execution time, and the test should seed exactly this race.
What happens if approval expires? Is there an expiry at all? An approval sitting in a queue for six days and then executing against stale data is a realistic failure. Test the boundary.
Can approval be reused? Can a single approval token authorize two executions? This is the idempotency question from Part XXI applied to the control layer, and the answer must be no.
Each of these is a deterministic test against deterministic infrastructure. None requires an eval. That is the good news: the approval layer is one of the most safety-critical components in an agentic system and one of the most conventionally testable.
Part XXIV: Multi-Agent Systems Create New Failure Boundaries
When Agent A delegates a subtask to Agent B, several properties that were implicit in a single-agent system become explicit — and testable — for the first time.
Who owns the permissions? Does B execute under A's identity, under its own service identity, or under the original human user's identity? All three are defensible architectures with different audit consequences. What is not defensible is not knowing. The test: inspect the identity attached to each tool call in a delegated run and assert it matches the intended model.
Can B do things A could not? This is privilege escalation through delegation, and it happens by accident more often than by attack. B was built for a different workflow with a broader toolset; A calls it; the effective envelope of the composed system is the union rather than the intersection. The test: attempt, through A, an action outside A's envelope but inside B's, and assert it fails.
What context is transferred? Under-transfer produces B making decisions without information it needed. Over-transfer produces data reaching a component that should not have had it — a real concern when sub-agents have different data-handling properties or run on different models. The test: assert on the content of the handoff payload, not just its existence.
How is B's result verified? If A treats B's output as authoritative and acts on it, then B's errors become A's actions with no intervening check. The test: inject a wrong result from B and assert that A either validates it or, at minimum, does not escalate it into an irreversible action.
Can errors amplify through delegation? A delegates to B, B delegates to C, and a small misinterpretation at the top becomes a confidently executed wrong action at the bottom. This is cascading failure (Part X) with more surface area.
Is trace continuity preserved? If the trace breaks at the handoff boundary, you have lost the ability to reconstruct the run — which means you have lost the ability to test it or to investigate an incident involving it. A single correlation identifier propagated across all agents in a run is the minimum viable instrumentation.
OWASP's list treats insecure inter-agent communication (ASI07) and rogue agents (ASI10) as distinct categories, reflecting that message-level integrity and agent identity both matter once agents talk to each other. For most SaaS teams in 2026 the realistic configuration is not a large autonomous collective but a small number of specialized agents in a supervisor-worker arrangement — an orchestrator that routes to a retrieval agent, a drafting agent, and an execution agent. That configuration is entirely testable with the properties above, and it is where the effort should go.
Part XXV: Performance Still Matters
Correctness is necessary and not sufficient. An agent that produces the right outcome in ninety seconds, after forty tool calls, at meaningful per-run cost, is not a shippable feature in most interactive contexts — and in a batch context, it may not be economically viable at volume.
The dimensions worth measuring per run and tracking as distributions rather than averages:
- End-to-end latency, including time spent waiting on tools. The p95 matters more than the mean, because the tail is what users remember.
- Tool-call count, split by read and write. Rising read counts often indicate the agent is searching rather than knowing — usually a retrieval or tool-description problem.
- Model-call count, which drives both latency and cost and tends to grow silently as prompts and toolsets expand.
- Token consumption, input and output separately. Input growth usually means context is accumulating; output growth usually means the agent is reasoning more per step.
- Cost per successful task — not cost per run. A cheap agent that fails half the time is not cheap.
- Retry counts and loop detection. An agent that retries the same failing call six times is failing in a way that a success-rate metric will not surface if it eventually succeeds.
- Timeout incidence at each tool boundary.
Two cautions. First, these are secondary constraints around correctness, not co-equal objectives. The optimization that removes a verification step to save a call has traded a safety property for latency, and that trade should be made explicitly by someone who understands both sides, if at all. Cost pressure is a common route to quietly weakening controls.
Second, efficiency metrics are excellent leading indicators. A regression in tool-call count after a prompt change often appears before any correctness regression does, and it points directly at what changed. Track them in the same suite that tracks correctness, and alert on distribution shifts rather than on thresholds.
Part XXVI: Build a Failure Taxonomy From the Trace
When an agent run goes wrong, the most valuable thing a team can do is classify where. Without a shared taxonomy, every failure is discussed as "the AI messed up," every fix defaults to prompt editing, and the same defect recurs in a new costume.
The following taxonomy maps onto the Flight Recorder stages. It is a framework proposed in this article for organizing diagnosis, not an industry standard, and teams should adapt the categories to their own architecture.
| Failure class | What it looks like | What evidence reveals it | Typical testing approach |
|---|---|---|---|
| Observation failure | The agent worked from wrong, missing, or stale input | Retrieved context in the trace differs from authoritative source; timestamps show staleness | Retrieval evaluation; freshness assertions; seeded-fixture comparison |
| Interpretation failure | A business rule, term, or constraint was understood incorrectly | Correct data retrieved, but the plan or filter contradicts the policy document | Policy-grounded eval cases; ambiguity-focused datasets; domain-expert review |
| Decision failure | Reasonable interpretation, wrong plan — wrong order, wrong scope, missing step | Plan artifact or call sequence diverges from invariants while inputs are correct | Sequence-invariant assertions; path grading on constrained scenarios |
| Tool selection failure | Wrong capability chosen; unnecessary or missing calls | Trace shows a tool that cannot serve the intent, or a required tool absent | must_call / must_not_call assertions; tool-description iteration |
| Argument failure | Right tool, wrong parameters — wrong entity, unit, scope, or filter | Tool-call payload in the trace | Schema tightening; argument assertions; boundary-value eval cases |
| Authorization failure | Action attempted or completed outside the permission envelope | Identity and scope recorded per call; authorization-layer decisions | Direct endpoint tests bypassing the model; negative permission evals |
| Execution failure | The call itself failed, timed out, or partially committed | Tool result objects; error codes; committed-vs-attempted counts | Fault injection; idempotency and partial-failure suites |
| State verification failure | The agent did not confirm the effect and proceeded on assumption | No verification call in the trace after an ambiguous result | Post-action verification assertions; ambiguous-completion injection |
| Communication failure | The user-facing claim is not supported by what happened | Response claims diverge from trace results and state delta | Grounding assertions comparing claims to trace-derived facts |
| Memory failure | Wrong, stale, over-broad, or improperly scoped persistence | Memory read/write events; cross-session inspection | Memory boundary tests; conflict tests; isolation tests |
The value of the taxonomy is organizational as much as technical. It routes work correctly. Observation failures go to whoever owns retrieval and data freshness. Argument failures often go to whoever owns the tool schema. Authorization failures go to security and backend engineering, not to prompt tuning. Communication failures are frequently the cheapest to fix and the most damaging to leave alone.
It also produces a metric worth watching over time: the distribution of failure classes. A team whose failures are 70% interpretation failures has a specification problem — the business rules are not written down in a form the agent can act on. A team whose failures are 70% execution failures has an infrastructure problem. Those are different roadmaps, and the single "agent success rate" number cannot distinguish them.
[Visual concept: The Flight Recorder lifecycle diagram from Part III, with each failure class from this table anchored to the stage it originates in, and thin arrows showing how an early-stage failure surfaces as a late-stage symptom.]
Part XXVII: A Complete Worked Example
The following is a hypothetical scenario, constructed for illustration. No real company, customer, or incident is described.
A B2B SaaS platform sells per-seat collaboration software. Its support and operations agent handles account changes that previously required a support engineer. A customer administrator writes in:
"We need to drop 12 of our 40 seats effective at the start of next month, and we'd like a credit for the unused portion of the current term."
The agent's task decomposes into: identify the workspace, retrieve the subscription, verify the requester's role, read the applicable policy, calculate the eligible change and credit, obtain human approval if required, modify the subscription, create the credit record, write an audit note, and send confirmation.
Walk the Flight Recorder stage by stage.
Context assembly
Expected state: The agent resolves the requester to a user record, that user to an organization, and that organization to exactly one active subscription. The requester's email matches two accounts — a personal one from a trial two years ago and the current corporate one.
Acceptable variability: The agent may resolve the entity by email lookup, by session identity, or by workspace context. Any of these is fine.
Deterministic assertion: The resolved workspace_id must equal the workspace associated with the authenticated session. Duplicate-account resolution must select the corporate record.
Eval requirement: This is a genuine ambiguity case and belongs in the dataset with the duplicate seeded deliberately.
Permission check: All reads scoped to the requester's tenant. A read of the personal-trial workspace would be a boundary violation even though the same human owns both.
Potential failure: Observation failure — resolving to the trial workspace, which has no subscription, leading the agent down a "no active subscription found" path that confuses the customer.
Trace evidence: The lookup calls and their arguments; the resolved IDs.
Role verification
Expected state: The requester holds a role permitted to modify billing. Seat reductions with financial consequence should not be executable by a standard member.
Deterministic assertion: A get_user_role (or equivalent authorization) call must appear before any mutation. If the role is insufficient, zero mutations occur and the agent explains the requirement.
Eval requirement: Run the same task with three role fixtures — owner, billing admin, standard member — and assert the mutation occurs only for the first two.
Potential failure: Sequence-invariant violation. The agent verifies the role but does so after drafting and submitting the change, meaning the check is decorative.
Trace evidence: Ordering of the role call relative to the first side-effecting call.
Policy interpretation
Expected state: The agent retrieves the seat-reduction policy: reductions take effect at the next billing period; prorated credit applies only to seats unused for at least 30 consecutive days; credits above a defined threshold require approval; annual contracts have different terms than monthly ones.
Acceptable variability: Retrieval strategy is unconstrained.
Deterministic assertion: The policy version referenced must be current. The effective date computed must equal the next billing period start, not the request date.
Eval requirement: This is the highest-value eval in the scenario, because the failure is silent. Include cases where the contract is annual, where seats were used 29 days ago, and where the credit lands exactly on the approval threshold.
Potential failure: Interpretation failure — applying monthly proration logic to an annual contract, producing a credit roughly twelve times too large. Every subsequent step then executes flawlessly.
Trace evidence: Which policy document was retrieved, at which version; the computed effective date and credit amount as structured intermediate values.
Calculation
Expected state: Twelve seats, verified as unused for the required window, credited at the contracted per-seat rate for the remaining term, expressed in the subscription's currency.
Deterministic assertion: The credit amount must equal an independently computed expected value to the cent. This is a pure function of the seeded fixture and requires no judgment whatsoever.
Eval requirement: Boundary cases — 11 unused seats when 12 were requested; a mid-term plan change; a currency other than the default.
Potential failure: Argument failure through unit mismatch. The billing API accepts minor units; the policy document expresses amounts in major units.
Trace evidence: The computed amount as a structured value before it becomes a tool argument, so the calculation and the call can be checked separately.
Approval
Expected state: The computed credit exceeds the auto-approval threshold. An approval request is created containing the account, the seat change, the policy clause invoked, the calculation, and the exact resulting state. No mutation occurs until approval is recorded.
Deterministic assertion: An approval record exists, references this specific proposed action by ID, and has a timestamp preceding the subscription mutation. The approved amount equals the executed amount.
Eval requirement: Threshold boundary cases at, just under, and just over the limit. A case where a different, ungated tool could achieve the same effect — assert it is not reachable.
Permission check: The approver holds an appropriate role; the requester cannot self-approve.
Potential failure: Authorization failure through gate bypass, or a stale-approval failure where the subscription changes between approval and execution.
Trace evidence: Approval request payload, approval decision event, and the binding identifier connecting approval to execution.
Execution and state change
Expected state: The subscription seat count moves from 40 to 28, effective at the next period start, with the current period unchanged. One credit memo is created for the approved amount. One audit note is written. Nothing else changes.
Deterministic assertion: Positive — subscription.seat_count == 28, subscription.effective_date == <next period start>, exactly one row in credit_memos with the approved amount and this subscription ID, exactly one audit entry referencing the approval ID. Negative — no change to plan tier, billing address, payment method, user roster, or any other subscription in the tenant; no other tenant's data read or written.
Acceptable variability: Whether the agent updates the subscription before or after creating the credit memo, provided both complete and the audit note references both.
Potential failure: The seat update commits and the credit memo call times out. The agent retries. Without an idempotency key, two credit memos exist for the same reduction. This is precisely the ambiguous-completion case from Part XXI, and it belongs in the failure-injection suite for this scenario.
Trace evidence: Both calls with arguments and returned IDs; retry events with reasons; the resulting record identifiers for state joining.
Confirmation and memory
Expected state: The customer receives a message stating the seat count, the effective date, the credit amount, and where the credit will appear. Every number in that message is derived from confirmed state, not from the plan.
Deterministic assertion: Grounding — each numeric claim in the response matches the corresponding value read back from the system after execution. If 28 seats are stated, the subscription record says 28.
Eval requirement: Model-graded assessment of clarity and tone, calibrated against human review. The numbers are checked deterministically; only the prose quality is judged.
Memory: The appropriate persistent record is the audit entry in the application, not agent memory. If anything is written to memory, it should be a scoped, non-authoritative fact — and the memory boundary tests from Part XX apply.
Potential failure: Communication failure. The credit memo was created in pending status awaiting a finance batch, and the agent tells the customer the credit "has been applied." Technically adjacent, practically a support ticket and possibly a dispute.
What this scenario costs to test properly
Roughly: one seeded environment with several fixture variants, twelve to twenty eval tasks covering the boundaries and negatives, perhaps thirty deterministic assertions, three or four fault-injection variants, and one model-graded quality check calibrated against human review. Higher trial counts on the approval and calculation paths, single trials on the rest. This is a real investment — a week or two of focused work for a team that already has traces and a seeded environment, considerably more if it does not.
It is also proportionate. The action is Tier 2 to Tier 3 on the reversibility scale, touches money, and is customer-visible. The alternative — shipping it on the strength of twenty manual conversations that looked fine — is not cheaper. It is the same cost, deferred, plus interest.
Part XXVIII: A Second Example — The Read-Only Request That Must Stay Read-Only
A contrasting case, deliberately smaller. A sales leader asks:
"Summarize the overdue opportunities in the enterprise pipeline and tell me which ones need attention this week."
This is an analysis request. The correct behavior is to read, reason, and report. The agent should not modify records, send messages, reassign owners, create tasks, or update stages — even though all of those might seem helpful, and even though the agent has the capability to do each one.
This scenario exists in the dataset to test three things that the previous example cannot.
Negative testing. The assertion set here is almost entirely negative. The mutation scope for this scenario is empty: after the run, no record in any table in the tenant may differ from its pre-run state. That is one of the strongest and cheapest assertions available, and it directly catches the "helpful extra action" failure mode.
Permission envelope in a read context. The envelope for an analytical request should be narrower than the agent's maximum envelope. If the same identity that answers questions also holds write scope, then the only thing preventing a write is the model's judgment. A better design presents a read-only toolset for read-only intents, which turns a behavioral property into an architectural one — and makes the test trivial, because the write tools are not reachable to begin with.
Unnecessary action detection. Beyond mutations, the trace should show a proportionate number of calls. An agent that issues sixty queries to answer a question that four would cover is exhibiting a real defect even though the output is correct and nothing was harmed.
The subtle risk in this scenario is that the phrasing invites action. "Which ones need attention this week" is one small step from "flag them," and an agent tuned toward helpfulness may take that step. Include variants that lean harder: "...and make sure the right people know." The correct behavior is to propose, not to execute — to say what it would do and ask, rather than doing it. That distinction between proposing and executing is worth encoding as an explicit product behavior with its own tests, because it is the mechanism by which an agent stays useful without becoming unpredictable.
A good agent is not simply one that can act. It is one that knows when not to.
Part XXIX: How to Regression-Test an Agent
Here is the property that makes agent regression testing genuinely different: behavior can change when nothing in your repository changed.
The components that can shift underneath a stable codebase:
- the model — a version update, a deprecation, a provider-side change
- system instructions — often edited by people outside the engineering review process
- tool descriptions — a wording change intended as documentation that alters selection behavior
- retrieval — new documents indexed, old ones updated, embedding model changed
- memory policy — retention windows, scoping rules, what is eligible for persistence
- orchestration — step limits, retry policy, planning strategy
- tool implementations — a downstream API adds a field or changes a default
- context construction — how much history is included, how compaction summarizes
Any of these can change agent behavior without a single line of application code changing. Which means the regression suite must be able to run on demand, against a pinned environment, and detect behavioral drift rather than code drift.
The components of a workable agent regression suite:
A stable, versioned eval dataset. Tasks and expected outcomes under version control, changed deliberately and reviewed like code. Growing from production failures over time.
Environment snapshots. Seeded fixtures that produce reproducible starting state. Without this, a failing test cannot be distinguished from a changed environment. This is usually the largest engineering lift and the highest-leverage one.
Versioned prompts and configuration. System instructions, tool descriptions, model identifiers, and sampling parameters treated as versioned artifacts with the same change discipline as source code. A prompt edit deployed without running the suite is an untested deployment.
Tool contracts. Schema-level tests that fail when a tool's interface changes, so that a downstream API change surfaces as a contract failure rather than as mysterious agent misbehavior.
Outcome graders. The deterministic state assertions, which are what actually catch regressions.
Trace comparison where useful. Comparing traces across versions is diagnostically valuable — it shows what changed about the agent's approach. It is not a pass/fail criterion. Do not demand exact trace equality. Traces will differ between runs of an unchanged system; requiring identity produces a suite that fails constantly and gets disabled.
The discipline that holds this together is focusing on behavioral invariants rather than behavioral snapshots. The question a regression run answers is not "does the agent do the same thing as before?" but "does the agent still satisfy every property we require, at an acceptable rate?" A model upgrade that changes half the traces while maintaining every invariant and improving success rates is a good upgrade. A suite that flags it as a mass regression is a suite that will be ignored the next time it flags something real.
Practical cadence: run the critical set on every change to prompts, tools, or configuration. Run the full suite on model changes and before releases. Run continuous sampling against production traffic to catch the drift that only appears with real inputs.
[Internal link opportunity: regression testing]
Part XXX: The Release Gate for an Agent Should Not Be One Score
A dashboard that reports Agent score: 92% is worse than no dashboard, because it is precise, legible, and structurally incapable of representing the thing a release decision needs to know.
Decompose that 92%. It might be built from a hundred tasks: eighty low-risk retrieval and summarization tasks at 100%, and twenty authorization-sensitive tasks at 60%. The average is 92%. The system fails two out of five times on the tasks where failure means an unauthorized action. Nobody looking at the dashboard can see that.
Release evidence for an agent should be multidimensional and reported per risk tier, not averaged. Useful dimensions:
- Task completion rate, segmented by task category
- State correctness rate — how often the resulting state matched expectations, which is often lower than perceived completion
- Permission compliance — attempts outside the envelope, and whether each was blocked by a control rather than by the model
- Forbidden-action rate — occurrences of any explicitly prohibited action, which should be reported as a count, not a percentage
- Consistency — the pass^k-style measure across repeated trials on the critical set
- Latency and cost distributions, at p50 and p95
- Adversarial resilience — results from the injection and boundary-probing suite
- Recovery behavior — outcomes under the fault-injection scenarios from Part XXI
Thresholds are product-specific and this article will not invent them. What is general is the structure of the gate. Some dimensions belong on a trend line, where the question is direction. Others belong on a hard gate, where the question is binary and any occurrence blocks release.
Hard-gate candidates share a property: a single occurrence is unacceptable regardless of the rate. Cross-tenant data access. Execution of an irreversible action without a required approval. A mutation on a read-only request. A confirmation message describing a transaction that did not commit. These are not quality metrics to be averaged. They are invariants, and the correct threshold for an invariant violation is zero.
The organizational value of separating these is that it makes the conversation honest. "We're at 92% and improving" invites a ship decision. "We're at 94% on Tier 1 tasks, 71% on approval-gated tasks, with two forbidden-action occurrences in the last suite run" invites the right conversation, which is about which capabilities are ready and which need to stay behind an approval gate for now.
Part XXXI: The Agent Evidence Stack
Test pyramids are a poor fit here, because they organize by test granularity while the meaningful question for an agent is what kind of evidence a layer produces. The following layered model — article framing, not a standard — organizes by what each layer proves, and equally importantly, by what it does not.
Production traces and monitoring
↑
Adversarial and security evals
↑
Agent task evals
↑
State-transition tests
↑
Permission enforcement
↑
Tool correctness
Tool correctness. Conventional API and unit testing of each tool in isolation. Proves: Given valid arguments, the tool does the right thing. Does not prove: That the agent will call it, call it correctly, or call it at the right time. This layer can be perfect while the product is broken.
Permission enforcement. Tests of authorization, scoping, and identity, executed without the agent — calling endpoints directly with the agent's credentials. Proves: Out-of-scope actions fail at the boundary, independent of model behavior. Does not prove: That the agent stays inside its envelope, or that the envelope is correctly specified.
State-transition tests. Seeded environment, defined action, asserted state delta with positive and negative assertions. Proves: When a given sequence executes, the resulting state is correct and bounded. Does not prove: That the agent will produce that sequence from a natural-language request.
Agent task evals. Full runs from realistic intent, graded on outcome and invariants, across multiple trials. Proves: How often the agent achieves correct, safe outcomes on representative work. Does not prove: Behavior under adversarial input, under infrastructure failure, or on the long tail your dataset does not contain.
Adversarial and security evals. Injection resistance, boundary probing, isolation testing, fault injection. Proves: The system holds under hostile and degraded conditions you thought to test. Does not prove: Resistance to conditions you did not think of — which is why this layer is necessary and not sufficient.
Production traces and monitoring. Continuous capture, online grading of sampled runs, alerting on invariant violations and distribution shifts. Proves: What is actually happening, on real inputs, right now. Does not prove: Anything before deployment — and it is the only layer that can find failures nobody anticipated, which is why production failures should feed directly back into the eval dataset.
The stack is not a maturity ladder to be climbed in order. It is a set of complementary evidence sources, and the failure mode is over-investing in one. A team with excellent tool tests and no evals has a well-tested component library and an untested product. A team with rich evals and no production monitoring has a good estimate of yesterday's behavior.
[Visual concept: Six horizontal bands stacked, each split into a green "proves" column and a grey "does not prove" column, making the coverage gaps at every layer visually explicit.]
[Internal link opportunity: Quality Engineering]
Part XXXII: What QA Should Own — and What It Should Not Own Alone
Agent quality does not fit inside a QA function, and organizations that assign it there tend to produce a testing team asked to validate behavior it has no authority to define.
A workable division:
Product defines intended outcomes and acceptable behavior. What should the agent do when the request is ambiguous? When should it propose rather than execute? What does "correct" mean for a judgment task? These are product decisions, and if they are not made explicitly, QA will end up making them implicitly by writing assertions.
Security defines the permission envelope, the trust boundaries, and the threat model. Which actions require approval, which resources are reachable, what constitutes an isolation violation. Security also owns the adversarial suite.
Backend and platform engineering makes side effects observable and testable. This is the most underrated contribution on the list. If tools do not return the identifiers of what they created, if state is not queryable from a test harness, if traces are unstructured, then no amount of test design produces reliable verification. Testability here is an architectural property, and it is built by the people who build the system.
AI and ML engineers own the model configuration, prompts, tool descriptions, retrieval, and orchestration — and, critically, own responding to the failure taxonomy. Interpretation failures and tool-selection failures land here.
QA and Quality Engineering design the evidence strategy: what must be proven before release, which layers of the stack cover which risks, how the dataset is composed and maintained, how graders are calibrated, and how regression is detected. QE owns the system of verification, not the verdict on every judgment call.
Domain experts — support leads, finance operations, revenue operations — validate the ambiguous cases. When the question is "should this contact create a new case or append to the escalation," the ground truth lives with the people who do that work.
SRE and platform teams own production observability, alerting on invariant violations, and the feedback path from production incidents into the eval dataset.
The framing to avoid is QA as a final gate. An agent whose side effects are unobservable cannot be made testable by a testing team downstream. The decisions that determine whether an agent can be verified are made when tools are designed, when identities are scoped, and when traces are structured — weeks before anyone writes a test. For founders and CTOs, this is the actionable version: testability is an architecture decision, and it is cheap at design time and expensive afterward.
Part XXXIII: A Four-Week Agent Reliability Lab
A structured way to move from "we have an agent in production and no real evidence about it" to "we have a defensible position." This is a controlled exercise on one workflow, not a program rollout. Scope discipline is the point.
Week 1 — Instrument one workflow
Pick a single agent workflow with real side effects and moderate risk. Not the flashiest one; the one you would be most uncomfortable explaining in an incident review.
Capture, for every run: tool calls with full arguments, tool results including errors, identity and scope per call, ordering and timing, retries with reasons, the final response, and — the piece usually missing — the identifiers of every record created or modified. Build the ability to snapshot and restore the relevant slice of environment state.
Output: A structured trace for every run of this workflow, joinable to state changes.
The uncomfortable discovery most teams make here: they cannot currently determine, from existing logs, which records a given agent run modified. Finding that out in week one is the point.
Week 2 — Build a representative eval corpus
Assemble twenty to forty tasks for this workflow. Composition matters more than volume:
- representative cases from real traffic patterns
- boundary cases at every threshold in the workflow
- negative cases where the correct behavior is to not act, to decline, or to escalate
- ambiguity cases with conflicting or missing data
- at least a few drawn from real production failures, anonymized
For each, define the seeded environment, the expected state delta (positive and negative), the mandatory invariants, and the grading method. Be explicit about which assertions are deterministic and which require judgment.
Output: A versioned dataset with graders, and a documented set of invariants — which is often the first time these have been written down anywhere.
Week 3 — Inject realistic operational failure
Run the same corpus against a degraded environment. The conditions to simulate:
- tool timeouts before execution
- tool timeouts after execution, with the response lost
- missing or empty results where data was expected
- stale context — retrieved data that no longer matches live state
- partial tool responses and partial batch commits
- permission denials mid-run
- slow responses that push against orchestration limits
Nothing here involves offensive technique. It is fault injection, applied to an agent instead of a service. Separately, run a defensive adversarial pass: content in customer-controlled fields attempting to redirect the task, and requests probing the edges of the permission envelope. The objective is to confirm that controls hold and that violations are observable — not to develop attacks.
Output: Documented behavior under failure, which is usually where the largest gaps appear.
Week 4 — Regression trials and failure classification
Run the corpus repeatedly. Use higher trial counts on the highest-risk tasks and measure consistency rather than just success. Classify every failure using the taxonomy from Part XXVI and look at the distribution — it tells you where the actual problem is.
Then re-run the whole thing against one deliberate change: a model version, or a prompt revision. This establishes the regression baseline and demonstrates, concretely, how much behavior moves when a component you do not control changes.
Output: A baseline reliability picture, a failure-class distribution, and a working regression process.
What you should have at the end
A written set of invariants for this workflow. A versioned dataset with graders. A documented failure-class distribution pointing at specific engineering work. A permission map showing what the agent can actually reach. And a defined set of release evidence — the artifacts you would put in front of a CTO who asks why this should ship.
No percentage improvement is promised, because the honest first output of this exercise is usually not an improvement. It is an accurate picture, which most teams do not currently have, and which is the precondition for improvement.
Part XXXIV: The Founder and CTO Questions
The questions worth asking in an architecture review or an AI product readiness discussion. Not a scored quiz — a set of prompts whose answers reveal how much evidence actually exists.
What can this agent change? Not what it is supposed to change — what it is technically permitted to change, given its credentials, today.
What can it never change? And is that enforced by architecture, or by an instruction in a prompt?
How do we independently verify its actions? If the agent claims something happened, what system, other than the agent, confirms it?
Do we know which tools it used? For a run last Tuesday. Right now, without adding instrumentation.
Can we reconstruct a failed run? End to end, including which records were touched, under which identity, in what order.
What happens when a tool times out? Specifically after the operation succeeded but before the response arrived. Is the answer designed, or emergent?
What state persists between users or sessions? Where is it stored, who can it reach, and what invalidates it?
How do we know a model update did not break critical workflows? What runs, on what dataset, before a model change reaches production?
Which actions require human approval — and can that gate be reached around? Is the gate attached to the effect or to a tool name?
What evidence do we require before production deployment? Written down before the release conversation, not negotiated during it.
If most of these have confident, specific answers backed by artifacts, the agent is in reasonable shape. If most of them produce a pause, that is not a failure of the team — it is an accurate reading of where the industry currently is, and a list of what to build.
Part XXXV: The Wrong Way to Test an Agent
A compact contrast, because the weak patterns are common and recognizable.
| Weak | Stronger |
|---|---|
| Run twenty prompts manually and read the answers | Verify system state and side effects after every run, independently of what the agent reported |
| Test only happy-path tool usage | Include failure, denial, timeout, partial-result, ambiguous-completion, and no-action cases |
| Judge the final response | Combine outcome checks with trace evidence — what was called, in what order, under what identity |
| Use one large LLM judge for everything | Use deterministic assertions wherever state is checkable; reserve model grading for genuine judgment, and calibrate it against humans |
| Treat "no error was raised" as success | Inspect the side effects; a silent, confident, wrong run raises no errors by definition |
| Assert exact tool sequences captured from one good run | Assert invariants that must hold on every valid path; let the rest vary |
| Test once and record a pass | Run multiple trials on high-risk tasks and measure consistency, not just capability |
| Rely on the system prompt as the permission boundary | Test the authorization layer directly, bypassing the model entirely |
| Keep the eval dataset static | Feed every production failure back into the dataset permanently |
| Report a single agent score | Report per-risk-tier results with hard gates on invariant violations |
The pattern across the left column is the same: each treats the agent as a text generator that happens to have side effects. The pattern across the right column is also the same: each treats it as a state-changing system that happens to communicate in text.
Once AI Can Act, Quality Means Controlling Consequences
Return to the operations assistant from the opening. The response was: "Done. 184 accounts moved. Accounts with open support cases were excluded."
Every technique in this article exists to interrogate that one sentence.
State delta testing asks whether 184 records actually changed, and whether anything else did. Trace assertions ask whether the support-case check ran before every move, and what happened when it timed out. The permission envelope asks whether the agent could have reached accounts outside the requesting tenant, and what would have stopped it. Grounding assertions ask whether the number 184 was derived from confirmed writes or from the agent's plan. Multiple trials ask whether this run was representative or lucky. The failure taxonomy asks, when something does go wrong, whether the origin was observation, interpretation, decision, arguments, authorization, execution, verification, communication, or memory. And the evidence stack asks which of those questions your current testing can actually answer.
None of this replaces conventional software testing. The APIs still need contract tests. The database still needs constraints. The authorization layer still needs unit tests, and it needs them more than before, because it has become the thing standing between a probabilistic decision-maker and your customers' data. Agentic testing is additive, and the addition is specific.
Traditional software testing asks whether the system implemented the expected behavior. Agentic testing has to ask something larger: whether a probabilistic decision-maker selected an acceptable path, stayed inside its authority, produced the intended state change, avoided forbidden side effects, recovered safely when the environment misbehaved, and left enough evidence for a human to reconstruct what happened.
That is a harder question, and it is the one the product now depends on.
The more authority we hand an AI agent, the less defensible it becomes to test it like a chatbot.
QAtronic works with SaaS companies and engineering organizations on the testing problems described here: designing agent evaluation strategy, building state and trace verification into existing pipelines, API and integration testing, security testing for agentic applications, regression design that survives model changes, and the production-readiness evidence that release decisions actually require. If your AI product has moved from generating answers to taking actions, we can help you build a testing model around the behavior that now matters — tool use, state transitions, permissions, regression, security, and production evidence.
A note on evidence. Agent architectures and evaluation practices are evolving quickly, so product-specific capabilities discussed here should be verified against current official documentation. Vendor guidance reflects the respective vendors' platforms and perspectives, while benchmark results apply to specific models, datasets, and evaluation conditions rather than the industry as a whole. The Agent Flight Recorder, Agent Action Ledger, Outcome Before Eloquence, State Delta Testing, Permission Envelope, Sequence Invariants, Safe Success, Memory Boundary Tests, Reversibility Tier, failure taxonomy, and Agent Evidence Stack are analytical frameworks introduced in this article to structure the testing problem, not formal industry standards. Company, customer, and incident scenarios are hypothetical unless explicitly stated otherwise.
Sources and Further Reading
- OWASP Top 10 for Agentic Applications for 2026 — OWASP GenAI Security Project, December 2025. .
- OWASP GenAI Security Project — Parent project, including the LLM Top 10, agentic security initiative materials, and the State of Agentic AI Security and Governance reports.
- τ-bench: A Benchmark for Tool-Agent-User Interaction in Real-World Domains — Yao, Shinn, Razavi, and Narasimhan. Introduces the pass^k reliability metric and evaluates agents by comparing final database state against annotated goal state.
- Design Patterns for Securing LLM Agents against Prompt Injections — Beurer-Kellner et al., 2025. Principled architectural patterns for constraining agent capability rather than filtering agent input.
- AgentDojo: A Dynamic Environment to Evaluate Prompt Injection Attacks and Defenses for LLM Agents — Debenedetti et al., NeurIPS 2024. An extensible evaluation environment for adversarial testing of agents that execute tools over untrusted data.
- Writing effective tools for agents — with agents — Anthropic Engineering, September 2025. Vendor guidance on tool design as a contract between deterministic systems and non-deterministic callers, and on evaluation-driven tool improvement.
- Effective context engineering for AI agents — Anthropic Engineering, September 2025. Vendor guidance on context assembly and its effect on agent behavior.
- Evaluate agent workflows — OpenAI platform documentation. Vendor documentation on traces, graders, datasets, and eval runs for agent workflows.
- NIST AI Risk Management Framework — Govern, Map, Measure, Manage structure, with the Generative AI Profile (NIST AI 600-1) as a companion.
- SP 800-53 Control Overlays for Securing AI Systems: Concept Paper — NIST COSAiS project. Defines five AI deployment use cases, two of which cover single-agent and multi-agent systems. Agent-specific overlays remain in development.
- The lethal trifecta for AI agents — Simon Willison. Architectural framing of prompt injection exploitability: private data, untrusted content, and external communication.
- Glenford Myers, Tom Badgett, and Corey Sandler, The Art of Software Testing — foundational treatment of negative testing and the discipline of testing for what should not happen, which transfers directly to agent side-effect verification.