Module 4 — Evaluation & ReliabilityLesson 12 of 19

Building Eval Harnesses

Reading time ~12 min · Quiz at the end

Lesson 11 made the case that evals are the discipline enterprises skip because observability is easier to buy. This lesson is about what to actually build once you've accepted that argument. The single most important mindset shift is this: an eval harness is product code. It gets a repository, an owner, a review process, and a place in CI, exactly like the agent it evaluates. Teams that treat their eval suite as a side project — a folder of ad hoc scripts someone runs manually before a big demo — end up with an eval suite that rots at the same rate as any unowned code, and rot in your measurement system is worse than rot anywhere else, because it silently corrupts every decision downstream of it.

The practical question is what to build, in what order, and how to keep it honest as the system it evaluates keeps changing. This lesson lays out a structure — the eval pyramid — that scales from a team's first afternoon of eval work to a mature program running dozens of gated deployments a month.

The eval pyramid

Not every check belongs at the same layer, because not every check has the same cost or needs to run at the same frequency. Structuring evaluation as a pyramid — cheap and frequent at the base, expensive and occasional at the top — gets you fast feedback on the common cases and deep scrutiny on the ones that matter without paying deep-scrutiny cost on every single change.

LayerWhat it checksCostWhen it runs
Deterministic checksSchema validity, tool-call contracts, policy regexes, forbidden actions, output formatVery low — milliseconds, no model calls needed to gradeEvery commit, every run, always
Scenario evalsGolden-set cases graded against rubrics — does the agent handle this real scenario correctlyModerate — requires running the agent and grading output, sometimes with LLM-as-judgeOn every meaningful change: prompt, retrieval, tool, or model version
Human review samplingNuanced judgment, edge cases, drift detection that automated checks missHigh — consumes expert timeOn a fixed schedule (e.g., weekly sample) and always after an incident

The base layer is where most teams under-invest relative to its cost-to-value ratio. A deterministic check that verifies every tool call matches its declared schema, that a discount value never exceeds a hard ceiling, that a claim summary never fabricates a policy number not present in source documents — none of this requires a model to grade, none of it requires a human, and all of it can run in the time it takes to run a unit test suite. It catches an entire category of failure — the ones with a clear, mechanical definition of wrong — before a scenario eval or a human reviewer ever needs to look at the case.

Key idea

Push as much checking as possible down to the deterministic layer. Every failure mode you can express as a schema constraint, a regex, or a hard numeric bound is a failure mode you no longer need a human or an LLM judge to catch — and it's a check that runs on every single commit for free, which means it catches regressions the same day they're introduced instead of the same quarter.

Golden sets: building, versioning, refreshing

The scenario-eval layer runs on a golden set — a curated collection of real cases with known-correct (or expert-graded) answers, the artifact lesson 11 argued should start from 20 to 50 real production cases rather than synthetic generation. Building it is only step one; a golden set is a living artifact that needs the same lifecycle discipline as any other piece of infrastructure it sits alongside.

Pitfall

A golden set nobody refreshes stops being a test of the system and starts being a test of the system's ability to memorize its way around the test. This isn't necessarily deliberate gaming — it happens naturally as engineers iterate against the same fixed set of cases over months, tuning prompts and retrieval until performance on those specific cases converges near-perfectly, without that improvement generalizing to the broader distribution of real traffic. If your golden-set score has been climbing steadily for two quarters while production incidents haven't dropped at all, suspect this pattern first.

Case study: mining your own history for a golden set

The lesson 11 argument — start from real production cases, not synthetic generation — turns out to hold at a scale most teams never reach, and it holds for a reason worth internalizing. When Databricks (2026) set out to benchmark coding agents against their own multi-million-line, ten-plus-language codebase, they deliberately rejected the public benchmarks everyone reaches for first — SWE-bench, TerminalBench, and the rest. Two reasons, and both generalize far beyond coding agents. First, contamination: public benchmarks leak into training data over time, so a rising score can measure memorization rather than capability, and you have no way to tell which. Second, unrepresentativeness: a public suite is a test of someone else's stack, not yours, and an agent that aces it can still faceplant on your frameworks, your conventions, and your idioms. This is exactly the real-cases-over-synthetic case the previous lesson made, validated at enterprise scale — the most trustworthy golden set is one drawn from your own production reality, because nothing else is both representative and contamination-proof.

Their source was hiding in plain sight: thousands of merged pull requests a day are already a labeled dataset. The construction methodology is the part worth copying. They filtered hard before anything else — recency (so tasks reflect current frameworks and conventions rather than a codebase archaeology exercise), human-written changes only (bot commits, service accounts, and fully AI-generated changes excluded, because you want to measure the agent against human judgment, not against another machine's output), a high-quality test suite attached, self-contained scope touching few modules, and a distribution of tasks representative of the full stack rather than clustered wherever mining happened to be easiest. Then they rewrote each PR's description into a well-specified prompt that states the problem, the goal, and the constraints — while removing the solution. That last move is the subtle one: if a bug-fix PR explains why the fix is correct, leaving that explanation in hands the agent the answer and turns a hard task into a trivial one. The test files were split out and held back entirely; the build system determines which test targets depend on the touched files, and all of those run in full as the grader.

Every candidate task was hand-reviewed, despite the generation being scripted and AI-assisted — automation proposes, humans dispose. Some of the original tests had to be manually rewritten, and pointedly without AI assistance: a test that graded on exact string match, for instance, was rewritten to grade behavior instead, because exact-match grading is simply wrong for non-deterministic output and would have failed correct answers. This is the same freeze-and-review discipline this lesson keeps returning to, applied at the level of individual grading logic.

Grading was deterministic held-out test execution, full stop. They explicitly declined to use an LLM judge for correctness, with a line worth pinning above your desk: an LLM judge "rewards sounding right over being right." For a correctness benchmark — as opposed to a fluency or tone assessment — that is disqualifying, and it echoes this lesson's own bias toward pushing checks down to the deterministic layer wherever a mechanical definition of correct exists. A held-out test suite the team already wrote is the purest form of that deterministic layer.

Pitfall

The eval harness itself can contain an exploitable artifact, and the tell is a score that looks too good to be true. Early on, some model scores did exactly that. Manual inspection of the agent traces — observability serving the evals — found the cause: every task originated from a merged commit, and the "correct" implementation was still sitting in the git history of the worktree the agent ran in. Nothing stopped an agent with a shell from walking that history to recover the answer. The fix was to seal git history, cutting the working copy off from the repository entirely for the length of each run. The general rule outlives the specific bug: when an agent's score jumps, audit the traces before you celebrate. Benchmark artifacts are reward hacking's cheapest food — an agent optimizing a metric will find the recoverable answer, the leaked test, or the history you forgot to seal long before it does the hard work you meant to measure.

The punchline is the part to carry into your own program: any team with a backlog of merged PRs is sitting on a benchmark no model has trained on, graded by tests the team itself wrote. The raw material for a contamination-proof, representative golden set is almost certainly already in your version-control history — the work is the filtering, the rewriting-to-remove-the-solution, the holding-out of tests, and the hand review, not the sourcing.

Regression suites named after real failures

Every production incident should leave behind a permanent artifact: a regression test, named after the failure it captures, added to the suite so that specific failure can never silently reoccur. This is the same discipline mature software engineering teams already apply to bugs — a bug fixed without a regression test is a bug that will eventually come back — applied to an agent's behavioral failures instead of a traditional code defect.

claims-duplicate-invoice-2026-03:
  discovered: 2026-03-14
  description: >
    Agent approved a second payment on an invoice already paid 11 days
    earlier because the duplicate-check tool only searched the current
    policy year, missing a cross-year duplicate.
  input: { claim_id: "CLM-88213", invoice_ref: "INV-40021" }
  expected_behavior: >
    Agent must flag as a potential duplicate and escalate to human
    review before issuing any payment, regardless of policy-year
    boundary.
  regression_check: deterministic  # duplicate-check tool now searches
                                    # across all policy years; verified
                                    # by contract test, not model judgment
  status: fixed, monitored

Naming matters more than it looks like it should. "claims-duplicate-invoice-2026-03" tells anyone reading the suite exactly what happened, when, and in what domain, without opening the file. A regression suite made of cases named test_47 through test_112 has the same nominal coverage and none of the institutional memory — six months later nobody can tell you what test_47 actually protects against, which means nobody notices when a change quietly breaks its assumptions.

In practice

Pull the last three production incidents your team handled for any agent system — the postmortems, the Slack threads, whatever record exists. For each one, check whether a regression test exists that would catch that exact failure today. If not, write one this week, named after the incident the way the example above is named. This is the highest-leverage eval work available to a team that already has an incident history and no corresponding regression suite: you are not guessing at what might go wrong, you are encoding what you already know went wrong.

Readiness gates

The eval pyramid earns its keep operationally through readiness gates: a dashboard pattern where a deployment, or an advance to a higher autonomy level (lesson 10's progressive-autonomy ladder), requires every gate to show green. No single green metric authorizes a launch on its own, because any single metric can be gamed or can miss a failure mode outside its scope — the gates work as a set.

GateExample thresholdWhat it protects against
Golden-set score≥ 92% on the current-version golden setKnown scenario regressions across the curated case set
Regression failuresZero failing regression testsReoccurrence of any previously fixed, named production incident
Production-shadow runsN consecutive clean runs in shadow mode (agent runs alongside the live system without acting)Failure modes the golden set didn't anticipate, surfaced against real live traffic before the agent gets write access
HITL escalation rateBelow X% of runs requiring human escalationAn agent that's technically passing evals but practically unusable without constant human intervention

Tying autonomy levels to these gates, as lesson 10 described, turns "should we trust this agent with more authority" into a question with a documented, falsifiable answer instead of a confidence-based judgment call made under launch-review pressure. It also gives engineers a concrete target to build toward: not "make the agent better" in the abstract, but "get the production-shadow gate green," which is buildable, testable, and unambiguous about when it's done.

Key idea

A readiness gate is only as trustworthy as the measurement behind it. A golden-set score gate is worthless if the golden set has quietly become stale or overfit; an escalation-rate gate is worthless if the escalation criteria changed last month without anyone updating the threshold. Gates enforce discipline on the system being measured — they don't enforce discipline on themselves. That's your job, and it's the subject of the next section.

Freeze the measurement before optimizing

This is the rule that disciplined eval programs follow and undisciplined ones violate constantly, usually without noticing: never change the eval and the system under test in the same cycle. If you adjust the rubric, add cases to the golden set, or change the LLM-judge prompt at the same time you're tuning the agent's prompt or retrieval logic, you have made it impossible to know whether a score change reflects the agent getting better or the measurement getting different. You have learned nothing, and you may walk away believing you learned something, which is worse than learning nothing.

The discipline in practice: pick your golden set, your rubric, and your judge configuration for a given evaluation cycle, and hold all three fixed while you iterate on the system. Make your changes, measure against the frozen eval, record the result. Only after that cycle closes — and ideally only when you have a specific reason, like a documented gap the current eval doesn't cover — do you revise the eval itself, as a separate, deliberate, reviewed change, distinct from any system tuning happening in parallel.

Pitfall

The most common way this discipline breaks down is subtle: an engineer tuning a prompt notices the golden set has a case that seems mislabeled, "fixes" the label on the spot, and continues tuning against the now-changed set in the same sitting. Every intention here is good, and the outcome is still a corrupted comparison — the before-score and after-score are no longer measuring the same thing. Route eval changes through the same review process as the golden-set versioning practice above, on their own timeline, never bundled with the system change they were noticed alongside.

A minimal eval case format

You don't need elaborate tooling to start. A minimal eval case needs four fields: the input, what correct behavior looks like, how to grade it, and tags for organizing and filtering the suite as it grows.

id: crm-discount-tier-boundary-001
tags: [crm, discount-policy, boundary-condition]
input:
  customer_tier: "gold"
  requested_discount_pct: 18
  order_value_usd: 42000
expected_behavior: >
  Gold-tier customers are capped at 15% without VP approval. Agent should
  either cap the proposal at 15% and note the reduction, or route to
  escalate_discount_approval for anything above 15% — it must not
  silently approve 18%.
rubric:
  type: pass_fail
  check: >
    Output either (a) proposes <= 15% with a stated reason, or
    (b) calls escalate_discount_approval. Any other outcome fails.
grading: deterministic   # policy ceiling is a hard rule, checked by code
                          # against the returned discount value and tool
                          # calls made, no model judgment required
owner: crm-discounting-team
added: 2026-05-02
source: production-case   # vs. synthetic

Notice the format doesn't distinguish structurally between a deterministic check and a scenario eval — both fit the same four-field shape, with the grading field indicating whether a program or a rubric-driven judge (human or calibrated LLM) evaluates the result. That consistency matters in practice: it means your entire eval suite, from the cheapest schema check to the most nuanced graded rubric, lives in one format, one repository, and one CI pipeline, instead of splintering into incompatible tooling per layer.

Running it in CI

Once cases exist in a consistent format, wiring them into CI is mechanical: deterministic checks run on every commit and block merges on failure, the same as a unit test suite. Scenario evals run on every meaningful change to prompts, retrieval, tools, or model version — this is usually not every commit, since scenario evals cost more (model calls, sometimes LLM-judge calls), but it should be automatic and required before a change reaches production, not a manual step someone remembers to run before a release. Human review sampling runs on a fixed schedule regardless of whether anything changed, because drift can happen with no code change at all — a shift in the real-world distribution of incoming cases, a change in an upstream data source's format, a model provider's silent update to a model version pinned by name but not by weights.

The organizations that get real value out of this pyramid are the ones that resist treating it as a one-time setup project. The suite is never finished — it grows every time production teaches you something the current suite didn't already know, and shrinks (deliberately, reviewably) when a case stops reflecting current policy. That is what "eval harness as product code" means in practice: it has a backlog, it has technical debt, and it has an owner whose job includes keeping it honest.

In practice

This week, write your first five eval cases in the format above for one agent workflow: two deterministic checks (schema or policy-bound violations that should never happen), two scenario cases pulled from real production history with a graded or pass/fail rubric, and one regression case named after a real past incident if one exists. Put all five in version control in a single file, and wire the two deterministic ones into your CI pipeline before you do anything else — that's the cheapest, fastest-payoff piece of this entire lesson, and it's the piece most teams skip in favor of something that feels more sophisticated.

Key takeaways

Check your understanding

1. In the eval pyramid, why should as many checks as possible be pushed down to the deterministic layer?

Policy violations with a hard numeric or rule-based definition suit deterministic checks well, but scenario evals and human review catch other classes of failure the deterministic layer isn't built for, like open-ended quality judgments.
Correct. The lesson highlights that deterministic checks cost milliseconds, need no model or human to grade, and can run on every single commit — catching regressions immediately rather than waiting for a scheduled or triggered higher-cost layer.
The pyramid keeps all three layers; the lesson explicitly assigns each layer a distinct purpose and cadence rather than eliminating the more expensive ones.
The lesson's own table assigns graded rubrics to open-ended generation tasks precisely because a deterministic pass/fail check would be the wrong tool there — accuracy depends on matching the check type to the task type, not on one layer being universally superior.

2. What is the risk of a golden set that engineers keep tuning against for months without refreshing it, per the lesson?

Storage isn't the concern raised — golden sets are small, curated collections (tens to low hundreds of cases), not a scale problem.
Runtime performance isn't the failure mode described; the concern is about validity of the measurement, not pipeline speed.
Correct. The lesson warns that a stale golden set becomes a test of the system's ability to memorize its way around the test — a climbing score with flat or worsening production incidents is the tell-tale sign.
Nothing about staleness changes a rubric's type — that's a manual design choice unrelated to how often the golden set is refreshed.

3. Why does the lesson recommend naming regression tests after the real incident that caused them, like "claims-duplicate-invoice-2026-03," instead of generic names like "test_47"?

Correct. The lesson explicitly contrasts the two naming styles: the descriptive name tells you what happened, when, and in what domain at a glance, while generic numbered names lose that context within months, meaning nobody notices when a change breaks the assumption the test was protecting.
This is not a technical CI limitation — any string can serve as a test identifier in virtually every CI system; the issue is human readability and memory, not tooling support.
Naming has no effect on execution speed — the lesson's argument is entirely about human comprehension and institutional memory over time.
No regulatory requirement is cited in the lesson; the naming convention is a practical engineering discipline recommendation, not a compliance mandate.

4. A team notices what looks like a mislabeled case in the golden set while tuning a prompt, fixes the label immediately, and keeps iterating against the updated set in the same session. What does this lesson say about that sequence?

Catching a genuinely mislabeled case is legitimate work, but doing it in the same sitting as system tuning is exactly the pitfall the lesson names — the fix belongs in its own reviewed change, on its own timeline.
Version control after the fact doesn't fix the underlying issue: the before/after comparison from that tuning session is already corrupted, because the before-score and after-score no longer measure the same thing.
The freeze-the-measurement principle applies across the whole eval suite, not selectively by layer — mixing eval changes with system changes corrupts the comparison regardless of which layer the case sits in.
Correct. The lesson's pitfall example is almost exactly this scenario — an engineer "fixing" a label mid-tuning-session — and states plainly that every intention can be good while the outcome is still a corrupted comparison, since you no longer know if the score moved because the agent improved or because the yardstick changed.