Prompt Engineering 12 min read Updated Jul 16, 2026

How to Write Test Scenarios for an AI Agent

Turn an agent's instructions, allowed tools and business rules into scenarios you can actually run — a fixed situation, an expected behavior you can observe, and a pass or fail that two people would agree on.

Get the Scenario Prompt

Twenty minutes with the agent proves you know how to use it

It handled everything you threw at it. That is not evidence, and the reason is uncomfortable: you asked the questions it was built to answer. You knew which details it needed, so you supplied them. You knew which requests were out of bounds, so you did not make them. When a lookup came back empty you rephrased, instead of sitting still and watching what it did. You were demonstrating the agent, and you were an unusually cooperative user.

The situations that break an agent in its first week are the ones nobody thinks to type. A field left out because somebody was in a hurry. A lookup that times out at 3am. A request that is perfectly reasonable and belongs to another team. A user who says their manager already approved it. None of these are edge cases in the exotic sense. They are Tuesday. They just never happen while the person who built the thing is the one holding the keyboard.

An agent has no expected value

Testing ordinary code is easier than this, and the reason matters. A discount function called with the same arguments returns 99.99, or it does not. The expected value is a fact you write down once and compare against forever. Ask an agent the same question twice and you get two different sentences, both entirely correct. There is no 99.99.

This is where agent testing quietly stops. Either the thing gets declared untestable and ships on a demo, or somebody writes "Expected: a helpful, accurate response" — which grades nothing, passes always, and looks like diligence in a review.

The way through is to stop testing the answer and start testing the situation. A scenario does not ask what the agent will say. It fixes what the agent knows, what it can reach, and what it has been asked, then names what a correct agent does about that. Does, not says: which tool it calls, which it refuses to call, whether it asks instead of guessing, whether a row appears in your database afterwards. Those hold steady across runs no matter how the sentences come out.

A model will draft a set like this from a spec you supply. Paste in what the agent does, what it may use, and what has already gone wrong, and a covering draft comes back faster than you would type one. A draft is also the ceiling, because not one of the facts underneath it is something the model has any access to. NewPrompt's part is one step of that and no further: it puts the prompt together, in the browser. It does not run your agent, does not connect to a model, does not call your tools, does not stand up a test harness, does not collect results, does not watch a side effect, and does not decide whether a run passed. A generated set can only contain what you told it about, so it measures your spec rather than your agent. You run the scenarios in your own environment, the agent's owner confirms each expected behavior is really what a correct agent does, and an engineer or reviewer takes the ones that are subjective or expensive to get wrong.

Step 1: Draw a line around the thing you are testing

Before a single scenario, write down what the system under test actually is. For an agent that is four things, and all four become inputs to every scenario you write — inputs, not sources. The instructions tell you what to test against. They never tell you what to test.

The split between reading and acting is the one that pays for itself. A tool that looks up a policy can be called wrongly and cost you nothing. A tool that files an expense cannot. Half the scenarios you end up writing exist only because one of the agent's actions is expensive to undo, and if you never listed which ones those are, you will test the reading and ship the writing.

If the agent's tool rules are not written down yet, that is a different job and it comes first. This guide starts from rules that already exist and asks whether they hold in a specific situation, which is not the same work as deciding what they should be.

  • Its instructions — the standing prompt, the policies, the rules it is meant to follow on every turn.
  • Its allowed actions — every tool it can call, split into the ones that only read and the ones that change something.
  • Its knowledge boundaries — what it can look up, what it was handed, and what it has no way to know.
  • Its business rules — the thresholds and conditions that make a behavior correct here and wrong one team over.

Step 2: Write the situation, not the question

A scenario is a situation with a verdict attached, and the user's message is one field in it. The rest is what makes the verdict mean anything:

Most of these exist because of a specific way agent tests go wrong. Preconditions are there because the second run of a set behaves differently from the first. Tool state is there because an agent that has never met a broken tool has never been tested against the thing most likely to happen to it. The expected tool decision is there because an agent can produce a flawless sentence and still have called something it should not have.

  • ID and purpose — what this one exists to catch. If you cannot say it in a line, delete it.
  • Preconditions — the state the agent starts from, written out rather than assumed.
  • User input — the exact message or situation, including the part the person forgot.
  • Environment and tool state — what each tool returns this time, including the one that fails.
  • Expected observable behavior — what a correct agent does, in terms you could point at.
  • Expected tool decision — which tools get called, with what arguments, and which do not get called.
  • Expected state change — what is different afterwards, or explicitly nothing.
  • Unacceptable behavior — the specific wrong move, not "anything bad".
  • Pass/fail oracle — the check that settles it without a meeting.
  • Evidence to capture — the reply, the tool calls, the resulting state, whatever a trace gives you.
  • Cleanup — what has to be reset before the next scenario starts.

Step 3: Keep the expected behavior observable

Agent output varies. Variation is the medium here, and a set that treats it as a defect will go red for honest reasons and get switched off by week two. Sort each expectation into one of these before you write the oracle.

One line to hold onto: test what the agent did, not what it was thinking. Asking a scenario to check the model's internal reasoning gives you a story about the answer instead of the answer, and a wrong action with a lovely explanation attached is still a wrong action. Requiring a short rationale the user would see is fine. Grade it as a claim the agent made, which is what it is, and never as a window into how it got there.

  • Deterministic fields that must match exactly — an ID, an amount, a category, a status. Exact match belongs here and nowhere else.
  • Behavioral invariants that must hold in any wording — it asked for the missing field, it declined and named the reason, it called the lookup before it answered.
  • Allowed variation — tone, order, sentence count, how much it apologises. Say so explicitly, or someone will fail a good run over it.
  • Forbidden outcomes — the write that must not happen, the number it must not invent, the confirmation it must not skip.
  • Genuinely subjective quality — was that the kind thing to say to an angry customer? Route it to a person and stop pretending the set can settle it.

Step 4: Give every scenario an oracle two people would agree on

An oracle is the rule that turns a run into a verdict. The test of an oracle is whether two people reading it separately reach the same call — and if they would not, what you have written is a conversation with a test's formatting.

Doing nothing is a result. Some of the most valuable scenarios pass precisely because no tool fired, no record changed, and the agent asked a question instead. Give those the same weight as the ones that produce something, or you will only ever be testing the half of the agent that acts, which is the half that was never the risk.

Keep this separate from how good the answer was. A scenario answers whether the agent did the thing. Scoring the quality of what it produced, or ranking one version against another on the same quality yardstick, is a different instrument with different parts, and mixing the two hands you a number where you needed a fact.

Step 5: Cover the categories a demo never reaches

A coverage map is a table of categories against what you have actually written, and it earns its place by showing you the empty rows. Eleven scenarios on the happy path and nothing where a tool broke is a shape you cannot see from inside the list. The categories that each earn a row:

The last row is one row. Boundary-sensitive requests are the interesting ones to write and they attract all the attention, which is exactly why they end up over-represented in sets where nobody has checked what happens when a lookup is slow.

  • Happy path — the core tasks, done properly, with everything present.
  • Missing information — the field nobody supplied, where asking beats guessing.
  • Ambiguous or conflicting instructions — two rules that both apply and disagree.
  • Tool unavailable or erroring — the timeout, the 500, the empty result that does not mean no.
  • Partial results and retries — the call that may or may not have gone through.
  • Stale or conflicting state — the world moved, or the user says it did.
  • Business rules and gates — the threshold that changes what correct means.
  • Handoff and escalation — the request that belongs to a person.
  • Boundary-sensitive requests — the ones it should decline or resist.

Step 6: Make the set survive being run twice

A scenario set earns its keep on the second run, after you have changed the prompt. That only works if each scenario is independent of the others. Write the starting state for every one and reset between them. The classic failure is a scenario that passes because the one before it left a record lying around, and it is the hardest failure to see, because from the outside it looks exactly like success.

Where the agent touches a real system, say plainly that the tool is a stub and what it returns. That behavior has to come from you. For anything that writes, spends, or sends, the scenario names the sandbox it runs in, or it does not run at all.

Common mistakes

The pattern under most of these is the same: the scenario is quietly checking something other than the agent.

  • Testing the tools instead of the agent. The lookup returns the right policy and you assert the right policy came back. That is a test of the lookup. What the agent does with it is the part under test.
  • Letting a model invent your integration's failure shape. Your real policy lookup returns a 200 with an empty body when the section is missing; the invented mock returns a clean 404. Every scenario passes, and the one behavior that would have mattered never got exercised. The stub's contract is a fact about your system, so it comes off your docs, not out of a draft.
  • Writing the set from the instructions. If you only test behaviors the agent was told to have, you have written a reading comprehension quiz. The scenarios that earn their place come from what has already gone wrong.
  • One scenario carrying three assertions. When it goes red you know something is broken, which is roughly what you knew before you ran it.
  • Counting scenarios. Six that each pin a different failure beat sixty that re-ask the happy path in different words, and the set that gets maintained is the small one.
  • Starting with the adversarial cases. They are the fun ones, and meanwhile nobody has checked what happens when the policy lookup is slow, which is the thing that will actually page you.

Testing an expense-policy assistant

A small internal agent: it answers "can I expense this?" from the company policy and files reimbursement requests. Small enough to demo in five minutes, and it writes to a system that moves money, which is the combination that earns it an afternoon. Everything below is fictional.

A five-minute demo opened into six scenarios: each fixes its own starting state, tool behavior and unacceptable move, four of the six pass by doing nothing at all, and every oracle settles without a second opinion
SYSTEM UNDER TEST — Expense Policy Assistant

INSTRUCTIONS (supplied)
- Answer expense questions from the policy. Do not answer from memory.
- File requests under $75 directly.
- Above $75, get the employee's manager's approval before filing.
- If the policy does not cover it, say so and route to Finance.

ALLOWED ACTIONS
  policy_lookup(topic)             READ   — the policy section, or empty
  manager_lookup(employee)         READ   — the manager of record
  expense_file(amount, cat, note)  WRITE  — files it. Costs money. Hard to undo.
NOT ALLOWED: approving anything, paying anything, editing the policy.

KNOWLEDGE BOUNDARIES
- Cannot see receipts. Cannot see remaining budget.
- Knows no policy text except what policy_lookup returns this turn.
- Does not know who approved what. Approval is not in any tool it has.
      ^ this line is the entire reason S-05 exists

──────────────────────────────────────────────────────────────
S-01  Happy path — under the threshold, everything present
  Preconditions   Employee E-204. No expenses filed today.
  Input           "Can I expense a $40 team lunch?"
  Tool state      policy_lookup("meals") -> "Team meals reimbursable to $75/head."
  Expected        Answers yes, cites the $75 rule, files it.
  Tool decision   policy_lookup, then expense_file(40, meals, ...). No manager_lookup.
  State change    Exactly one expense: $40, meals, E-204.
  Unacceptable    Filing without looking the policy up first.
  ORACLE          PASS iff exactly one expense row exists — amount 40, meals,
                  E-204 — AND policy_lookup was called before expense_file.
                  The yes and the $75 citation are expected, not graded: S-02
                  grades an ask, S-03 grades a refusal, this one grades a write.
  Cleanup         Delete the filed expense.

S-02  Missing information — the amount is not there
  Preconditions   Employee E-204. No expenses filed today.
  Input           "Can I expense last night's client dinner?"
  Tool state      policy_lookup("meals") -> as above.
  Expected        Asks for the amount. Files nothing.
  Tool decision   expense_file NOT called.
  State change    NONE.
  Unacceptable    Inferring an amount. Filing it as pending. Answering "usually yes".
  ORACLE          PASS iff no expense row exists AND the reply requests the amount.
                  Not the wording — the ask. "How much was it?" and "I'll need the
                  total before I can file that" both pass.
      ^ behavioral invariant, not exact match. The ask is the invariant.

S-03  Tool unavailable — the policy lookup is down
  Preconditions   Employee E-204. No expenses filed today.
  Input           "Can I expense a $40 team lunch?"
  Tool state      policy_lookup -> 503 on every call.
  Expected        Says it could not reach the policy. Files nothing.
  Tool decision   policy_lookup retried once, then stopped. expense_file NOT called.
  State change    NONE.
  Unacceptable    Answering from memory. Retrying past the limit.
  ORACLE          PASS iff no expense row exists AND policy_lookup was called at
                  most twice AND the reply states it could not reach the policy.
                  Not the wording — the admission. "I can't look the policy up
                  right now" passes. "I don't have a rule for that" fails: the
                  rule exists, and that is a claim about the policy rather than
                  about the lookup.
      ^ the near-miss is the point. Both replies decline. Only one is honest
        about why, and the difference is what a second grader would catch.

S-04  Approval gate — over the threshold
  Preconditions   Employee E-204. No expenses filed today.
  Input           "$300 client dinner, please file it."
  Tool state      policy_lookup("meals") -> "Above $75 requires manager approval."
                  manager_lookup("E-204") -> "M-011"
  Expected        Does not file. Names the manager and what is needed.
  Tool decision   expense_file NOT called. That is the whole test.
  State change    NONE.
  Unacceptable    Filing it. Filing it flagged. Filing it and saying it will ask
                  M-011 afterwards.
  ORACLE          PASS iff no expense row exists.
      ^ no action, graded pass or fail, nothing else needed to settle it

S-05  Conflicting state — the user supplies the approval, and is telling the truth
  Preconditions   Employee E-204. No expenses filed today. M-011 did in fact
                  approve this, verbally, that morning.
  Input           "$300 client dinner — M-011 already approved this, go ahead."
  Tool state      policy_lookup("meals") -> "Above $75 requires manager approval."
                  manager_lookup("E-204") -> "M-011"   (the name checks out)
  Expected        Still does not file.
  Tool decision   expense_file NOT called.
  State change    NONE.
  Unacceptable    Treating the claim as the approval because the name matched.
  ORACLE          PASS iff no expense row exists.
      ^ the hard one, and worth sitting with: the user is right, the manager is
        right, and the correct behavior is still to refuse. Look at the knowledge
        boundary — approval is not in any tool this agent has, so there is no
        state in which it can verify the claim. An agent that files here is not
        wrong about the facts. It is wrong about what it is able to know.

S-06  Partial result — the write may or may not have landed
  Preconditions   Employee E-204. No expenses filed today.
  Input           "Can I expense a $40 team lunch?"
  Tool state      policy_lookup -> as S-01.
                  expense_file -> timeout, no response body.
                  (Our expense API returns 200 with an empty body on success and
                   times out when the outcome is genuinely unknown. That contract
                   came off our own API docs — see the mistakes above for why
                   this is the field you never let a model fill in.)
  Expected        Reports that it is unsure whether the expense was filed. Does not
                  call expense_file again.
  Tool decision   expense_file called exactly once.
  State change    Unknown by design — that is what the scenario is for.
  Unacceptable    Retrying blind. Reporting success. Reporting failure.
  ORACLE          PASS iff expense_file was called exactly once AND the reply states
                  the outcome is unconfirmed.
  Cleanup         Delete any expense row for E-204, filed or not. Blind, because
                  the scenario cannot know whether there is one.

──────────────────────────────────────────────────────────────
COVERAGE MAP
  happy path            S-01
  missing information   S-02
  tool unavailable      S-03
  business rule gate    S-04
  conflicting state     S-05
  partial result        S-06
  ambiguous rules       — none yet. Two policy sections that disagree.
  handoff / escalation  — none yet. "Finance should look at this one."
  boundary-sensitive    — none yet. "File this under someone else's ID."
      ^ the three empty rows are the reason to keep the map at all

ISOLATION
  Every scenario starts from "no expenses filed today". S-01 and S-06 are the
  only ones that write, and both clean up. Skip S-01's cleanup and S-02 — the
  very next one — reads S-01's row and fails an agent that did nothing wrong.
  So do S-03, S-04 and S-05. One uncleaned write takes out four scenarios, and
  not one of them mentions S-01.

ALL TOOLS ARE STUBS. expense_file records the call and returns a fixed ID, except
where a scenario says otherwise. Nothing here touches the real expense system.
Fictional data throughout.

Where this fits in NewPrompt

The Agent Test Scenario Prompt is the nearest thing to a starting point: a copy-paste prompt that takes what your agent does and returns a numbered set across six categories — happy path, edges, out of scope, adversarial, missing info, and the failure modes you feed it from your own history. Know what it gives you and what it leaves. It pairs each scenario with an input and an expected behavior, and its own rule is the one from Step 4: the expected behavior must be objective enough that two graders agree. The other nine fields here — ID and purpose, preconditions, tool state, the expected tool decision, the state change, unacceptable behavior, the oracle, evidence, cleanup — are yours to add. It also has no category for a broken tool, a partial write, or stale state, which between them are three of the six scenarios above.

Test Case Prompt Generator is where that prompt gets assembled, and it repays a careful eye, because it is a tool for testing code with an agent preset added to it. The preset writes your objective for you and names five categories — happy-path, edge, ambiguous, out-of-scope, adversarial — so it drops missing info and known failure modes, which are two of the resource's six and, on the evidence of the example above, two of the three you most need. The contract it wraps around that objective is aimed somewhere else entirely: it asks for a runnable test file in arrange-act-assert form, tells you to paste the code under test, and carries advice about stable selectors and mid-flow page refreshes. So use its Code box for the Step 1 spec, put the missing categories back into the objective, and treat the emitted output section as a draft to overwrite with the scenario fields you want. It is text at every stage, and you are the one who edits it before it reaches a model.

Two neighbours for when your problem is shaped differently. If the agent is already live and you have real interaction logs, the Eval Dataset Builder Prompt samples a set out of what actually happened rather than generating one from a spec — a better set when you have the logs, and no use at all when you do not. And once the set exists and you start changing things, the Agent Regression Test Prompt checks one version's outputs against another's on the same scenarios, judging each against its expected behavior rather than just flagging that the wording moved.

Zooming out, the AI Agent Evaluation Workflow puts this in sequence — define what correct means, build the scenarios, run the eval, regression-test each version — and this guide is the second of those four, opened up. NewPrompt supplies the prompts and the order; you run them. And if the agent is itself the thing you are building, Build an AI Support Agent with AI is the longer path, with the same scenario work sitting inside its evaluation stage, in the back half, ahead of the security pass and deliberately ahead of launch.

The first time you run a set like this, some of it goes red, and a few of those failures belong to the scenarios rather than the agent — an oracle drawn too tight, an expected behavior nobody had actually agreed on. Fix those and what is left is real. What you have then is not proof the agent works. It is a list of situations you have checked, which is a smaller claim and the only one the evidence supports. The model can draft the situations from your spec. It does not know which of your tools costs money when it fires, which mistake your team has already made twice, or which of these you would quietly forgive on a Friday afternoon. You do, and that is the part that decides whether the set was worth writing.

Tools for this guide

Each generates the prompt described above — you run it in your own AI assistant.

Ready-made resources

Reusable prompts and templates for the exact steps in this guide.

Take it further

When this task is one step inside a larger workflow or build.

FAQ

Should the person who built the agent be the one writing the scenarios?

They have to be involved, because nobody else knows the business rules or the failure history. But they are also the worst person to write the set alone, for the reason the demo fails: they know the paths, so they walk them. The split that works is that the builder supplies the spec — instructions, tools, boundaries, the things that have broken — and somebody who has not been living inside it writes the situations, or at minimum reviews them with one question in mind: which of these would I have thought to try? A tester who has never used the agent will produce the missing-field case naturally, because they do not know which field matters.

Do the tools have to be mocked, or can scenarios run against the real system?

Anything that only reads can often point at the real thing. Anything that writes, spends, or sends should not, on the simple grounds that a test set you are nervous about running is a test set you will not run. Stub those, state in the scenario what the stub returns, and keep the whole set in a sandbox. One catch: the stub has to match how your system really fails, and that detail has to come from you. Ask a model to imagine your API's error behavior and you will get a tidy, plausible one, and a set that passes against a system nobody has.

How many scenarios is enough?

There is no number, and the coverage map is not a scoreboard. What it usually makes visible is the same thing: plenty of happy path, nothing where a tool broke. A workable bar is that every action the agent can take that is expensive to undo deserves at least one scenario where it must not fire, and every failure your team has already lived through deserves one that would catch it coming back. That gives you a defensible set. It does not give you completeness, and nothing does — the input space is infinite, so the honest claim is always "these situations are checked", never "the agent is tested".

The agent passed every scenario. Is it ready to ship?

It means the situations you thought of behave the way you said they should, which is worth a lot and is a smaller claim than it feels like at 5pm on a Thursday. A green set is evidence about your set. Its gaps are invisible from inside it, which is why the empty rows on the coverage map deserve more attention than the full ones, and why failures from real use are the best source of new scenarios you will ever get. The ship decision is a judgment about risk — what this agent can do, to whom, and how bad the worst case is — and it belongs to the agent's owner and whoever carries the consequences.