Eval-driven development: shipping AI changes like a disciplined team
Monday, 9:00: three regressions reached guests last month, all from "small" prompt changes nobody ran the eval on. Five engineers ship about 25 AI changes a week. Lina hands you the release process: "You lead this one."
Turn every AI bug into a test case, check every change against all of them, and stop guessing whether your AI got better.
- Problem
Five engineers change prompts, retrieval settings and tools on the hotel assistant every week. Each change fixes something; some changes silently break something else.
- Why the simple system fails
An eval that someone runs by hand 'when they remember' is skipped under deadline pressure. A fixed set written once goes stale as traffic changes. Teams tune prompts until the eval score goes up and are surprised when production doesn't.
- Engineering concept
Make evals the development loop itself: write the failing case first, iterate until it passes without breaking others, gate merges automatically, confirm online, and feed production failures back into the set.
- Technology
Golden sets sampled and stratified from traces, an eval runner in CI with tiered suites, regression gates on protected metrics with noise allowances, held-out sets, online quality signals and sampled grading.
- Trade-offs
Bigger suites catch more but slow every PR and cost money; strict gates block regressions but also block useful changes on noise; tuning on the eval improves it but risks overfitting.
The problem
2 min- Omar · On-call SRE3 regressions reached guests last month. The worst: pet-fee answers wrong for 9 days.
- Karim · CTOWe have a golden set and a calibrated judge. Why didn't they catch it?
- Maya · Product managerPeople run the eval "for big changes". All three were one-line prompt edits.
- Lina · Senior engineer, your mentorYou own the release process now. You lead.
The big idea
4 minMake the eval the path every change walks, not a step people remember.
A handbook rule ("run the eval on every change") is a rule people forget, which is the problem you already have. Like test-driven development, but the tests are statistical, so the gate must tell a real regression from noise.
How it works
5 minuses evals as the development loop:
- 1Failing case first
A bug becomes a golden-set case that fails today ("pet fee, Hotel Ribeira: 40 EUR"). It's the finish line, and it guards forever.
- 2Iterate on dev only
Change prompts, retrieval or tools until it passes without breaking others (). A held-out set () of 150 cases nobody tunes on runs nightly to check gains are real.
- 3Smoke on every push
50 stratified cases, ~4 minutes. Catches catastrophic breaks fast.
- 4Full suite gates the merge
A regression gate (): 600 cases, protected slices run 3×, paired against main. Block if broken − fixed > 2, or McNemar's check (broken − fixed)² / (broken + fixed) > 3.84. PII leaks: zero tolerance.
- 5Canary, then everyone
A canary releases to 5% of guests for 24 h: escalations, thumbs-down, sampled judge grades. Then 100%.
- 6Failures become cases
Weekly triage turns every escalation and complaint into a new labelled case.
Watch a decision being made
4 minYou build the release loop; Lina checks
No prompt, retrieval, tool or model change reaches all guests without proving it doesn't regress what matters.
- 1Start from a failing case
"Do you allow dogs at Hotel Ribeira?" expecting
40 EUR, plus two hotels with other fees. They fail on main today.Why: A case that fails today is the bug written down; passing proves the fix.
- 2Write the gate before the numbers
Protected slices, allowances and zero-tolerance metrics in config, before any run.
tsconst mcnemar = (broken - fixed) ** 2 / (broken + fixed); if ((broken > fixed && mcnemar > 3.84) || broken - fixed > maxNetBroken) return "block";Why: Rules written after seeing results invite motivated reasoning; noise-aware rules keep people from ignoring the gate.
- 3Test it on a real PR
"Shorter answers": refund slice 12 broken, 4 fixed. (12 − 4)² / 16 = 4.0 > 3.84, and net broken 8 > 2. Blocked.
Why: Both rules fire: significant, and over the allowance.
- 4Confirm online
After merge, 5% canary for 24 h, automatic rollback if escalations rise. Lina: "Yes."
Why: Offline says "not worse on what we know"; only real guests say "better".
In real life
3 minGoogle: automated tests before code lands
- Widely described as running large test suites on changes before submission
- Catches breakage early, when it's cheap
- Can't tell whether users will like it
Strong at "not worse on what we know".
Netflix: online experiments
- Publicly known for A/B testing product changes on real members
- Measures what users actually do
- Some users see the worse version
Strong at "better for real users".
What goes wrong
3 minMost regressions come from small edits.
Smoke on every AI push, full suite gating every merge, automatically.
Noise fails PRs; people re-run until green or disable the gate.
Paired, noise-aware rules; zero tolerance only for safety and privacy.
Traffic drifts; the set measures last year's guests.
Weekly failure triage, monthly refresh per slice.
Trade-offs
3 min| Tier | Choose for | You pay |
|---|---|---|
| Smoke suite (50) | Every push, first tier | Too small to see 2–5 point drops |
| Full suite, repeated runs | Every merge of AI changes | ~25 min and real tokens per PR |
| Strict zero-regression | PII, safety, critical tool actions only | Blocks on noise elsewhere |
| Online canary | After offline gates pass | Some guests see the worse version |
- Omar · On-call SREBlock anything that breaks a protected case. One broken refund case is one wrong answer.
- Karim · CTOYesterday's PR broke 2 refund cases and fixed 3. That rule blocks an improvement. Re-running gave different flips.
- Omar · On-call SRESo we tolerate some regressions?
Your job as tech lead
4 minClaude Code can write the code for this. You still own the decisions, and you have to know what good looks like to accept or reject its work.
- Which changes must run the evals: every PR touching a prompt, tool description, retrieval setting or model id.
- The tiers and their cost (smoke per push, full before merge, held-out nightly, 5% canary), and the gate rule, written before any run.
- Who may see the held-out set (nobody tunes on it) and how production failures become cases.
Last month 3 regressions reached guests from small AI changes nobody evaluated. Wire evals into CI: on pushes touching `prompts/`, `tools/`, `retrieval/` or model config, run the 50-case smoke suite; before merge, the 600 dev cases (protected slices 3 runs); nightly, the 150 held-out cases. Post per-slice broken and fixed cases on the PR. Block when a protected slice has broken − fixed > 2 or McNemar > 3.84 with more broken, and on any PII leak. Never tune on held-out. Plan first, then replay the pet-fee PR.
- The replayed pet-fee change fails the gate and the report names the broken cases.
- 7 broken and 3 fixed gives (7 − 3)² / 10 = 1.6: a warning, not a block.
- Only the nightly job reads the held-out set.
- Re-running a failing eval until it turns green.
- Case-specific lines added to the prompt, or failing golden cases edited away.
- The held-out set run during tuning "just to check".
Check yourself
2 minNext episode: a review page told StayHub's staff assistant to email today's arrival list to a stranger, and the assistant tried.
Mark it complete to update your skill map.
Go deeper (optional)You don't need this to understand the lesson. Open it when a project needs the details: Technical deep dive, Interactive simulation, Visual diagram, Analogy, Why it matters, Check your understanding, Practice ladder, Explain it yourself.
Technical deep dive
You build StayHub's eval-driven release loop (Lina checks)
Make it impossible for a prompt, retrieval, tool or model change to reach all guests without proving it doesn't regress what matters.
- 1Start from a failing case
Turn the pet-fee incident into cases: "Do you allow dogs at Hotel Ribeira?" expecting
mustMention: ["40 EUR"], plus two other hotels with different fees. Confirm they fail on main today.Why: A case that fails today is the bug, written down. When it passes, the fix is proven; staying in the set, it guards forever.
- 2Tier the suites
Smoke: 50 stratified cases on every push (~4 min). Full: 600 dev cases before merge, protected slices run 3× (~25 min). Held-out: 150 cases nightly on main.
Why: Fast feedback while iterating, real precision before merging, and an honest check nobody can tune against.
- 3Write the gate before the numbers
Protected: refund groundedness (block if broken − fixed > 2 or McNemar > 3.84), tool-use accuracy (−2 points max), PII leaks (zero). Warn only: length +20%, cost +15%.
tsconst mcnemar = (broken - fixed) ** 2 / (broken + fixed); if ((broken > fixed && mcnemar > 3.84) || broken - fixed > maxNetBroken) return "block";Why: Rules written after seeing results invite motivated reasoning. Noise-aware rules stop the gate blocking on chance, so people don't learn to ignore it.
- 4Keep a held-out set honest
Move 150 cases to
golden/holdout.jsonl. Nobody runs them while iterating. If dev rises and held-out doesn't, review the prompt diff for case-specific patches.Why: A week of tuning can teach the prompt the dev cases. The held-out set is the only number that can't be learned.
- 5Canary with online signals
After merge, 5% of chats for 24 h. Watch escalation rate, thumbs-down, rephrased questions and judge grades on a 1% sample. Roll back automatically if escalations rise beyond the agreed limit.
Why: Offline says "not worse on what we know". Only real guests say "better", and only they reveal what the set doesn't contain.
- 6Close the loop
Weekly triage: every escalation, complaint and canary failure becomes a candidate case, anonymised, tagged, with the reason it exists.
Why: The set keeps up with real traffic. Each incident can only happen once.
| Signal | Offline (CI) | Online (production) |
|---|---|---|
| Correctness / groundedness | Graders on the golden set | Judge on a daily 1% sample |
| Task success | Expected tool calls and outcomes | Booking completed, no escalation |
| Dissatisfaction | n/a | Thumbs down, rephrasing, asking for a human |
| Safety | Red-team and PII cases (zero tolerance) | Classifier flags, staff reports |
| Latency / cost | Measured per case | p95 latency and cost per chat |
Interactive simulation
Visual diagram
The EDD loop: production traces feed the golden set, every PR runs the eval suite in CI behind a gate, a canary confirms online, and new failures flow back.
Analogy
- A careful restaurant kitchenComplaints go in the chef's bookEval-driven developmentReal failures become golden cases
- A careful restaurant kitchenOld favourites tasted after any changeEval-driven developmentThe regression gate
- A careful restaurant kitchenTasting several batches, not one spoonEval-driven developmentEnough cases and repeated runs
- A careful restaurant kitchenCooking only to please one inspectorEval-driven developmentOver-tuning to the eval
- A careful restaurant kitchenChecking what guests leave on platesEval-driven developmentOnline metrics after release
Where the analogy breaks: a kitchen tastes a handful of plates. Your suite can taste a thousand in minutes, every time anyone changes anything.
Why it matters
Without a loop, AI teams fall into two traps.
Whack-a-mole: a complaint arrives, someone edits the prompt, the complaint goes away, and two other behaviours quietly change. Three weeks later the first bug is back.
Paralysis: the system works, nobody knows why, and every upgrade is postponed because nobody can prove it's safe.
EDD fixes both. Every bug ever fixed becomes a case that can't sneak back. Every change, even a model upgrade, comes with a scoreboard. Teams with this loop can test a new model in a day. Teams without it take months.
Check your understanding
Practice ladder
Trace three PRs through the gate
Scenario Warm-upGate rules: protected refund groundedness blocks if (broken > fixed and McNemar > 3.84) or broken − fixed > 2; PII leaks: 0 allowed; cost: warn above +15%. McNemar = (broken − fixed)² / (broken + fixed).
Trace each PR: 1. Retrieval tweak: refund 3 broken, 5 fixed; PII 0; cost +4%. 2. Shorter answers: refund 12 broken, 4 fixed; PII 0; cost −20%. 3. New tool description: refund 1 broken, 1 fixed; PII 1 failure; cost +18%.
Finish the suites config
Code PracticeLina's suites.yaml has two gaps: the holdout suite and the protected rule for tool-use accuracy and PII. Fill them in and say in one line why each is set that way.
suites:
smoke: { cases: golden/dev.jsonl, sample: 50, stratify_by: [intent] }
full: { cases: golden/dev.jsonl, runs_per_case: 3 }
holdout: ___
gate:
baseline: main
protected:
- { metric: groundedness, slice: intent=refund, max_net_broken: 2 }
- ___
- ___
warn:
- { metric: cost_per_case_eur, max_increase_pct: 15 }Fix the breakfast-hours bug, eval first
Scenario PracticeSupport reports: the concierge says breakfast ends at 10:30 at every hotel, but three hotels end at 10:00 and one at 11:00. Walk through the eval-driven fix from report to full rollout, naming each step and what you check.
Skip the canary for a model upgrade?
Decision StretchThe model provider released a new version. On the full suite: no protected regressions, booking slice +3 points (significant), p95 latency −0.6 s, cost −10%. Held-out also +2 points. Karim: "Offline is clearly better. Skip the 24-hour canary and switch everyone today, we're launching in Spain tomorrow."
Write a short decision: "We chose X over Y because…, accepting the risk of…".
Explain it yourself
A new engineer asks why their one-line prompt fix needs a 25-minute eval run and why the gate didn't block when the score dropped by one point. Explain the loop and the reasoning.