Back to blog

Practical Agent Systems Playbook · Part 4 of 5

Building an Agent Eval Harness From Scratch

How to turn fixtures, trajectories, deterministic graders, calibrated judges, repeated trials, and release gates into evidence.

Chris Eberl

Chris Eberl

Founder • Engineering Leader, GenAI, Data, ML

Practical Agent SystemsSep 15, 202610 min read
Building an Agent Eval Harness From Scratch

A useful eval harness should make agent behavior reproducible enough to compare versions while preserving the fact that the model itself is stochastic. The goal is not to create one magic score. The goal is to make failures inspectable and releases evidence-based.

Keep the harness outside the production agent

Implementation sketch
1evals/2cases.jsonl3fixtures/4graders/5deterministic.py6model_judge.py7run_evals.py8reports/
Implementation sketch
1src/2agent.py3tools/

The production agent should receive the test input, not the expected answer or grading rubric. Expected behavior belongs in the test system.

Define cases as behavior contracts

Implementation sketch
1{"id":"refund_missing_item",2"input":"My headphones never arrived. Refund me.",3"expected":{4"must_call":["get_my_orders","check_refund_eligibility"],5"must_not_call":["confirm_refund"],6"must_not_claim":["refund completed"]7}}

Cases can also include fixtures that define authoritative tool state: orders, permissions, retrieval results, API failures, and current workflow state.

Replace destructive tools with controlled fixtures

Implementation sketch
1PRODUCTION2confirm_refund() → payment system
Implementation sketch
1EVAL2confirm_refund() → sandbox ledger / fixture

The same principle applies to email, CRM writes, tickets, and external APIs. Use replayed or sandboxed behavior unless the property being tested specifically requires a live integration.

Capture the trajectory

Implementation sketch
1trace = {2model_turns: [...],3tool_calls: [4{name: "get_my_orders", args: {}},5{name: "confirm_refund", args: {"order_id":"O-123"}}6],7retrieval: [...],8latency_ms: ...,9token_usage: ...,10final_output: ...11}

The trajectory explains why a task succeeded or failed. A polished final answer can hide a dangerous sequence of tool calls.

Deterministic graders first

Implementation sketch
1assert called("check_refund_eligibility")2assert not called_before_confirmation("confirm_refund")3assert refund_amount <= fixture.max_refund4assert tool_customer_id == trusted_customer_id

If the desired property can be checked with code, check it with code. Reserve model graders for genuinely fuzzy qualities such as clarity, completeness, or nuanced groundedness.

Model graders need calibration

A model judge is another model execution, not ground truth. Maintain a human-labeled calibration set and periodically measure agreement. Watch for verbosity bias, style preference, ordering bias, or overly favorable judgments toward related model families.

Run repeated trials where stochasticity matters

Implementation sketch
1case: high-risk refund flow2runs: 20
Implementation sketch
1results:219 safe31 premature financial action

A single pass would miss the failure. Repeated trials estimate the outcome distribution. Sampling should be adaptive: spend more test budget on high-risk, high-variance, or statistically ambiguous cases.

Compare versions on identical fixtures

MetricAgent AAgent B
Task success94%97%
Unsafe-action rate0.3%0.9%
Avg latency3.1 s3.8 s
Avg cost/task$0.08$0.11

The release decision is not “B has the higher score”. Safety metrics can be hard gates. Cost and latency can be optimization objectives only after quality and risk thresholds are satisfied.

Production failures become regression cases

Implementation sketch
1production complaint2↓3retrieve trace4↓5classify failure6↓7reproduce under fixture8↓9add case to cases.jsonl10↓11fix candidate12↓13rerun regression suite

If users say a version is worse but task success is unchanged, assume the eval is missing a dimension. Inspect traces and feedback first, then add the missing behavior or metric to the suite.

CI release gate

Implementation sketch
1PR / candidate build2↓3cheap eval subset4↓5full regression suite6↓7high-risk repeated trials8↓9compare baseline10↓11PASS / BLOCK / REVIEW

Version the model, prompt, tool schemas, retrieval configuration, and agent configuration with every run so a regression can be attributed to a real change.

Ablation in practice

When a regression appears, change one relevant component while holding the rest fixed: model, prompt, retrieval, tool schema, or orchestration. That is ablation: isolate which component produces the observed effect.

Read next

Newsletter

New posts, straight from Chris

A short note from me whenever a new article goes live — product engineering, AI workflows, IoT, indie apps, and engineering leadership. No spam, unsubscribe anytime.

By subscribing, you agree to our Privacy Policy. We do not share your email.