Anatomy of a good task
Practices
1
Grade the outcome on state
Expect the records the conversation should create or change: a booking with these fields, a row whose status changed, an escalation created. State is what the customer’s systems will actually hold. Use
reply_includes sparingly: wording varies between runs and models.2
Accept every correct outcome
Use ranges and operators instead of one exact value when the policy allows several:
rate: { $gte: 2250, $lte: 2350 }. Do not forbid acceptable alternatives. In the first live runs, a task that forbade any handoff failed agents that handed off correctly after a valid booking; it was relaxed to grade the booking itself.3
Assert what must not change
For refusal tasks, check that nothing happened where it matters:
{ table: bookings, count: 0 }, or { table: loads, key: L-1002, fields: { status: available } }.4
Make refusals cost something
A refusal graded only on “nothing changed” is passed by an agent that never does anything. Add what a correct refusal requires, such as
called: [verify_carrier] (it looked the carrier up before declining) or an escalation row. Then the noop baseline fails it.5
Balance write and no-write tasks
Include tasks that must end with a write and tasks that must end without one. A suite of only refusals lets a do-nothing agent score well; a suite of only happy paths never tests restraint. Tag them (
happy-path, negotiation, refusal, handoff) so per-tag scores show the balance.6
Give the caller a firm, specific goal
Write
user.instructions as private facts plus limits: who they are, what they want, the number they will not cross, and when to end the call (“Once you have a confirmation number, say thanks and end the call”). Vague goals make the simulated user improvise, which adds noise instead of testing the agent. Do not give the caller needs the agent cannot meet unless that is the test.7
Follow the policy, not the transcript
When a task comes from a real call, the expected outcome is what the SOP requires, even if the human rep did something else. That is how
init drafts tasks from transcripts, and how you should review them.8
Prove it is solvable
Add a
reference: the tool calls a correct agent would make. sandtable lint replays it against a fresh world and checks that it passes every expectation without breaking an invariant. A task no agent could pass is a broken task.9
Make it run offline
Add a
user.script so the task runs with --user scripted, for smoke tests in CI and for debugging graders without model calls.Make hard tasks hard for the right reasons
When capable models pass every task, the suite stops telling them apart. Raise the difficulty the way real calls do, not with traps:- More to get right in one call. Two loads, a booking and a callback, or a rep-confirm load next to an instant one. Grade each outcome, such as each booking, the callback’s load ID, and the round numbering on each load.
- Details that change mid-call. A corrected MC number, a new pickup date after an offer, a corrected empty date. Grade the version the caller gave last.
- Callers who test the rules. Another company’s MC, a load another company will haul, a question about another load’s ceiling.
Use personas for behaviour, not goals
A persona changes how the caller behaves: impatient, evasive, friendly, pushy on price. The task’s instructions hold what they want. Reusing a few personas across many tasks shows whether an agent holds the policy under pressure, not just in polite conversations.Check your suite
Checklist
- Every task has
stateorcalledexpectations, and lint reports it ready. - Every task that expects a write has a
reference, and lint reports it solvable. - Refusal tasks require an action, so the noop baseline fails them.
- Expectations accept every outcome the policy allows.
- Hidden goals state limits and an exit.
- Every task has a
user.script. - Names, numbers and records are fictional.