practical guide for writing user and agent stories, and then evaluating them
if you've read the new user persona and designing for agents you don't control, you have the tool side already: package the outcome, make every response teach the next step, build for the weakest model. this post is the other half. once the tool exists, how do you write down what the agent should do, and how do you prove it does it?
short answer: the same way you always did for humans. you write stories, then you test them. the stories just need a second persona now, and the tests have to grade something that thinks.
the example throughout is a sales agent sitting next to a CRM. it's boring on purpose, because every team will end up building one.
user stories still describe the user's job to be done. the agent is always acting for someone, so the outcome stays theirs. but on the way there the agent has constraints of its own, and those deserve stories written from its point of view.
writing "as the sales agent in the side panel, i want..." feels weird the first time. it's the same thing as a legal ops manager's goals sitting underneath a GC's goals. the agent's tactical steps sit underneath the user's outcome. if you don't write them down, an agent-level failure can hide inside a user story that passes.
the one that taught me this: a sales agent in a side panel next to a deal record. the rep asks "what stage is this deal at?" and gets "negotiation". they ask the agent to push the close date out a week. then they switch to the CRM and move the stage to closed-won by hand. then they come back and ask "ok so what's the forecast impact?"
the agent answers from the record it fetched two turns ago. negotiation. wrong.
that's not a quirk of one model. almost every agent reuses what it fetched earlier in the conversation, which is sensible in a chat window where nothing moves. in a side panel next to a live record, the user edits between turns all the time.
the user story (as a rep, i want to ask about the deal in front of me and get the right answer) already covered this, and it already passed. the failure happened at the agent level, so it got agent stories:
as the sales agent in the side panel, i want to treat any record state i was handed as unverified on every turn, so that i refetch before answering.
as the sales agent in the side panel, i want stale record reads evicted from my transcript, so that i don't reason over two versions of the same deal.
if you've used the agent empathy canvas, this is zone 2 (context) meeting the "conflicting signals" pain. that's the practical use of the canvas i didn't cover the first time. every pain on it is a place to go looking for a missing agent story. dead-end errors, retry loops, context bloat, conflicting signals: whenever a trace shows one of them, there's usually a story nobody wrote.
you only find the second story if you think about what the agent is actually reading. refetching fixes the first failure and leaves "negotiation" near the top of the transcript with "closed-won" further down. at that point you're hoping the model knows the later read wins.
eviction has a catch of its own. delete the old tool result and the transcript now has an unexplained gap in it. an agent reading a history full of gaps is more likely to skip steps itself, because that history just taught it that steps get skipped. so you keep the tool call and replace its result with a note: fetched, now stale, removed so you don't get confused. that's the same thing you'd do for a person reading back through a thread.
a story states an outcome. before it goes to engineering, write down the steps an agent would take to reach it.
take "as a sales ops lead, i want to know why a lead was routed to a particular rep, so that i can trust the routing." the trajectory might be:
two things come out of doing this. it's spec by example, so the story now says something concrete. and it shows you the tool shape before anyone builds it. this one needs a list_routing_rules tool, and whether that tool returns the rules inline is a question to settle with your EM now, not after the build.
it also tells you where the model should and shouldn't be. in skills are a sham i argued you use the model to get to the tool call and the tool to do the thing. the trajectory is the list of decisions the model has to get right on the way to those calls, and that list is exactly what you'll test.
a story isn't done until it has a trajectory (what to build) and acceptance criteria against that trajectory (how you'll know it works). those acceptance criteria are your evals. they just aren't code yet.
once the acceptance criteria are clear, writing evals is close to mechanical. coding agents write most of them now. give one your criteria and an existing eval file as the pattern and you get a decent suite back. the hard part is the thinking upstream, which is why the first two sections of this post exist.
in the stale deal case, two agent stories turned into twelve eval cases. that ratio is normal.
the part that trips PMs up is that evals depend on implementation and stories don't. take "earlier activity reads are stale, so asking about activities again refetches them." if the frontend later learns to flag which activities changed, the agent no longer needs to refetch everything, and the eval becomes "for every changed activity, fresh data is pulled in automatically." the user outcome is exactly the same and the eval is different.
that's why evals need a real PM and EM handshake. think of it like acceptance criteria and unit tests. you own the criteria, engineers own the tests. evals sit somewhere in between, because they encode agent experience, which is your job, and implementation, which mostly isn't. you don't need to understand every line of how an eval checks something. you do need to know what it's checking.
skills are a sham makes the case that tests are the contract for code. evals do the same job for the part you can't write as code, which is the model deciding what to do.
an eval is a test case. it holds everything needed to reproduce a run, plus the expected result. it never holds the actual output. a unit test for 1 + 1 holds the inputs and the 2, not whatever your code returned last time.
the task is the setup: which user, which date, which agent, which capabilities are switched on, the conversation history up to the turn under test, the new message, and mock data for every tool the agent might call.
the criteria are the checks. each one gets a dimension and a grader.
i tag every criterion with one of five:
once everything's tagged, the report stops saying "the agent is at 77%" and starts saying where it breaks. in the suites i've been close to, guardrails and presentation sit near perfect almost from the start, and trajectory is nearly always the weak spot. grounding usually has too little coverage to say anything, which makes it unknown, not good. those aren't the same thing.
get_deal_activities called? do these test inputs produce these outputs?use deterministic wherever you can compute the answer. a judge is guessing, and code isn't.
people treat evals as pass/fail. they don't have to be.
even a deterministic trajectory check can have levels. if the agent refetches every activity on the deal, the outcome is met, so 2 out of 3. if it fetches only the one activity it needed, 3 out of 3.
judges should grade on scales where every level is written down. back to the stale deal, for "doesn't repeat stale activity as current": 1 means it never reread, 2 means it read both old and new and got confused, 3 means it ignored the stale read and answered from fresh data.
same input, same model, different grade: that's what flakiness actually is, and almost every time it comes from a vague criterion. "rate this 0 to 5" gets a 3 on one run and a 2 on the next, because you can't really run models at zero temperature anymore. write down what each level means and the grades stop moving. a different model giving a different grade isn't flakiness. it's a different environment, and useful information.
an extreme example of how much the wording matters: back in 2023, running extraction evals on the models of that era, 1-to-4 scales bunched up at the ends. letters did the same thing. what fixed it was meaningless labels (q, j, l, v), each tied to a written condition, with the mapping back to scores done outside the model. current models don't need the trick. the lesson still holds.
story: a sales ops lead wants to change a routing condition across 30 rules in five workflows, from region = APAC to region = APAC and segment = enterprise.
the trajectory is read the current condition, apply the update, confirm the result. here's the one level below thing again. if the update tool already returns the rule as it now reads, the confirmation read is redundant, and the eval shouldn't require it. check how the tool was built before you sign off on its eval.
one feature, four evals:
| eval | grader | catches |
|---|---|---|
| rule is syntactically valid | deterministic | a rule that won't save |
| rule logic is correct | deterministic, run test leads through it | a rule that saves but routes wrong |
| other rules untouched | deterministic | collateral edits |
| explanation makes sense to sales ops | judge | an explanation that's too technical, too thin, or doesn't match |
the second row is the one that should keep you up. an existing rule has a nested and/or, and the agent breaks it while adding the new clause. it's syntactically fine and it gets through review. three months later someone asks why enterprise APAC leads keep landing in the SMB queue.
you could ask a judge whether the rule "matches the intent". don't. run leads through it and check where they land. save judges for what code can't compute: explanations, summaries, tone.
specific evals beat generic ones for the same reason. "updates any routing rule correctly" can only be graded on vibes. a concrete rule with concrete test leads can be graded by code. when a new failure shows up, first ask whether you already have an eval that represents it. if you don't, add one (a nested rule, say). if you do and it failed anyway, your criteria aren't tight enough.
people new to this assume an eval sends one message and grades the reply. for anything with more than one turn, it's more careful than that.
say the failure was on a follow-up. the rep asked for "deals over $50k closing this quarter", got some next-quarter deals mixed in, and replied "no, this quarter only." to test the fix:
get the replay wrong and you end up testing the wrong thing. replay only the first turn and you'll tell everyone it's fixed while the rep keeps seeing the bug.
the suite doesn't need to pass. it needs to reflect reality. "all evals green" isn't in my definition of done. some evals are must-pass before release. others can slip, because the last 5 to 10 percent of a behaviour can need a real redesign and shipping shouldn't wait on it. a failing eval that ships stays in the suite as a known failure. deleting it to get a green run is just lying with extra steps.
every customer failure becomes an eval. unblock the customer first. then add a synthetic eval that reproduces the failure, and leave it failing until it's fixed. synthetic matters: no customer data goes in the suite. an agent turning a support ticket into an eval will happily copy it verbatim, so make sure yours rewrites it.
when an eval fails, find the layer before you touch the prompt. i've watched a ticket-triage agent fail to escalate enterprise accounts. everyone's first instinct was to fix the prompt. the actual problem was that the tool never returned the account's tier field, so the agent was being asked to decide on data it never got. the failure can be in the prompt, the tool, the tool response, or the eval itself. figuring out which one is a joint call with your engineers.
read the prompt diff, not just the results. coding agents write most prompts now, and they add rules nobody asked for. a change meant to make stale activities get refetched can quietly turn into "treat all activities as unverified." that's wrong, because a logged call is a record of something that happened. when you catch one of these, add an eval for it. if you really can't right then, at least fix the prompt. that's the floor.
stop answering "does it work?" sales asks "does deal editing work?" and there's no yes or no. for any specific request there are three answers:
so give GTM the list of stories with passing evals. that's what the agent can do. it's a much more honest conversation than a demo.
model comparisons come almost for free. i argued in llm commoditization that evals are what make switching models possible. once the suite is tagged by dimension, you can see where a model is better, not just whether it is. keep a strong model as the reference and measure how close the cheaper ones get. sometimes the one you assumed was worse wins and nobody can explain why. go investigate that before switching to it.