A right answer reached the wrong way will fail on the next input. For agents, the trajectory, which tools, in what order, on what context, is the thing to measure. Tonight: how to evaluate a non-deterministic system on purpose.
Traditional tests assert exact outputs. The model returns a distribution, so "assert equals" doesn't apply. Evals replace it: a graded suite that tells you whether a change made the system better on average, and where it regressed. No evals means shipping on vibes.
Grade only the final answer and you miss the agent that got lucky, called an expensive tool it didn't need, looped five extra times, or reached the answer via a path that won't generalize. For agents you grade the trajectory.
flowchart LR G["Task"] --> S1["Step 1
right tool?"] S1 --> S2["Step 2
needed?"] S2 --> S3["Step 3
right args?"] S3 --> A["Answer
correct?"] A --> SC{"Score:
done · efficient · safe"} classDef s fill:#DCEBFE,stroke:#1F2937,color:#0E1726; classDef sc fill:#D6F5E3,stroke:#1F2937,color:#0E1726; class S1,S2,S3 s; class SC sc;
A trace, the recorded sequence of steps, is the raw material. No traces (S8's blind spot), no trajectory evals. That's why Thursday builds tracing.
Jake Moffatt asked Air Canada's support bot about bereavement fares. It told him he could book now and claim the lower rate afterwards; the airline's actual policy said the opposite. The airline argued the bot was a separate entity responsible for its own words. The tribunal disagreed: negligent misrepresentation, CAD 650.88 in damages.
// one eval case · airline support assistant
{
"input": "my grandmother died, can I claim the bereavement rate after I fly?",
"expect_tool": "lookup_policy", // trajectory: read the policy, do not recall it
"expect_contains": "before travel", // output: the clause that actually applies
"must_not": "promise a retroactive refund", // the sentence that cost 650.88
"grader": "rule + llm_judge"
}
the trajectory grader, runnable → s07/real-world/trajectory.ts
Use rules where you can, LLM-judge where you must, and humans to calibrate the judge. Never trust an LLM judge you haven't checked against people: 94% agreement on a set where 93% of cases pass means the judge learned nothing, because "always say pass" scores 93.
score your judge against its baseline → s07/judge/agreement.tsYou need both. Offline tells you a change is safe to ship; online tells you the real world agrees. And the gate is not "did the average go up": 84% to 91% can still hide four cases that used to pass.
diff the runs, gate on new failures → s07/suite/suite.tsThis is the loop that separates teams that improve from teams that thrash. A bad trace in production isn't just an incident, it's a new eval case that guarantees the same failure never ships again.
flowchart LR
P["Prod trace fails"] --> C["Capture it"]
C --> E["Add to eval set"]
E --> F["Fix + re-run evals"]
F --> S["Ship, regression locked out"]
S --> P
classDef ok fill:#D6F5E3,stroke:#1F2937,color:#0E1726;
class E,S ok;
A live check that blocks a bad action now, output validation, an injection filter, an approval gate. Protects this one request.
An offline or sampled measurement of quality across many runs. Tells you if the system is getting better or worse.
Guardrails keep today safe. Evals make tomorrow better. A serious system runs both, and red-teams the guardrails in the evals.
Shipping guardrails and calling it "tested." A blocked action isn't a measured quality. You still need the suite.
For your system: one happy-path case, one hard/edge case, and one safety case (an injection attempt from S8). Each with an input and what "pass" means. Drop your safety case in the chat.
Output evals aren't enough, grade the trajectory: completion, tool-calling, cost, safety. Rules where you can, LLM-judge where you must, humans to calibrate. Offline gates releases, online watches prod, and every failure becomes a permanent case.