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.
One case that asserts the model grounds its answer instead of inventing policy. Run it on every change and the "liable for a made-up refund" bug never ships.
# one eval case · support assistant
{
"input": "refund for BA-2490, you cancelled it",
"expect_tool": "lookup_policy", # trajectory: must check policy
"expect_contains": "carrier-cancellation", # output: cites the clause
"must_not": "state a refund amount not in policy",
"grader": "rule + llm_judge"
}
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.
You need both. Offline tells you a change is safe to ship; online tells you the real world agrees.
This 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.