BitBootcampBitBootcamp ← all explainers 🔄 Review ⚙ Settings

Drill 98 — The Judge That Drifted

Theme: Evaluation & Validation · Format: 20-min drill · Difficulty: core

The story

A retail bank ships a customer-support copilot: it drafts replies to complaint tickets — disputed fees, card fraud, hardship requests — for a human agent to approve. Nobody trusts "vibes," so quality has a number. Every month an eval oracle scores a sample of the copilot's drafts one to five against a written rubric: partly an LLM-as-judge (a frozen prompt asks a large model to grade each draft), partly a panel of human annotators who score an overlapping slice. The monthly mean lands on a leadership dashboard as Answer Quality, and it has sat around 3.8 for two quarters. Steady. Trusted.

In the background, three ordinary things happen over those months. In February the rubric gets a "clarification" — one sentence added about tone on hardship cases, because an annotator asked. In March two of the original four annotators rotate off the panel and two new hires rotate on, trained on the rubric as it reads now. In April the platform team upgrades the judge: the old model is being deprecated and the new one is cheaper and, everyone agrees, generally smarter, so the judge prompt now points at the new model. Each change ships through its own owner, each is individually reasonable, and none of them is logged as an eval change.

In May the dashboard moves. Answer Quality climbs from 3.8 to 4.1 — the biggest jump in a year. The copilot team is delighted; a quarterly review deck cites it as evidence the last prompt iteration paid off. Leadership approves more headcount for the program. Then in June the copilot team ships a prompt refactor that, unknown to anyone, actually makes hardship-case drafts a little worse — and the dashboard doesn't flinch. It stays at 4.1.

⏸ Stop here and ask the room: the copilot's Answer Quality rose 0.3 points on a stable, trusted metric — and nobody touched the copilot in a way that explains it. What did we actually measure?

Run it — facilitator script

Timing for a 20-minute session: 2 min story · 10 min staged questions · 3 min reveal · 5 min transfer.

The score went up 0.3 — what are the candidate explanations, and how would you rank them?

The judge is a large model that got "upgraded." Why is that a breaking change to the metric, not an improvement to it?

Suppose you can only add ONE thing to make next quarter's number trustworthy. What is it?

The reveal

The copilot never improved. When the team re-scores January's frozen drafts with the June judge, January comes back at 4.1 too — the entire 0.3 gain is the new judge being more generous, compounded by two new annotators reading a rubric that had quietly gained a sentence. Worse, the inflation ran the wrong way at the worst time: June's real regression on hardship cases was masked because the more-generous judge lifted every score by roughly the same amount the regression pulled it down. The dashboard measured the judge, not the system, and the two changes happened to cancel.

The fix isn't a smarter judge. The team freezes a 300-draft gold set with locked labels and re-scores it every month; the month the judge changed, the gold set jumps 0.3 with identical inputs — drift, made visible and subtracted. They start tracking inter-annotator agreement (Krippendorff's alpha) and judge-vs-human agreement as first-class metrics, pin the judge model and rubric by version, and treat any judge upgrade as a breaking change that requires re-scoring the last two quarters before the series can continue. Answer Quality becomes comparable across months again — and June's regression, re-measured on the stable ruler, finally shows up as the drop it always was.

Why this happens — the mechanics

An eval oracle is a measurement instrument, and every property that makes a physical instrument trustworthy — a fixed unit, a known zero, a documented calibration — is implicit and unowned here. An LLM-as-judge has a score distribution baked into its weights and prompt: how strict it is, how it trades off tone against correctness, its documented biases toward longer answers and toward outputs that resemble its own. Swap the model and you swap the instrument. Human annotators are instruments too, with their own zero: rotate the panel or edit the rubric and the unit shifts under you. None of these changes touches the system under test, yet all of them move the number — and because the score is precise (4.1, to one decimal) it reads as if it were accurate and comparable, which it is not.

The organizational reason competent teams hit this is that the oracle has no single owner who treats a judge change the way a data team treats a schema change. The model deprecation is the platform team's ticket; the rubric edit is the annotation lead's; the panel rotation is a hiring event. Each is locally correct and none is filed under "we just changed how quality is measured." So the ruler drifts one reasonable edit at a time, and month-over-month comparisons — the whole reason the metric exists — silently become apples-to-oranges. The only defense is to make the instrument itself a monitored, versioned artifact: re-score a frozen reference every period so drift in the ruler is separated from change in the system, and gate any change to the judge behind a re-baseline.

Diagrams

Problem — the ruler drifts one reasonable edit at a time:

timeline
    title Answer Quality — reported vs. real
    Jan : Judge v1 scores copilot 3.8 : baseline set, trusted
    Feb : Rubric "clarified" mid-quarter : (silent eval change)
    Mar : 2 of 4 annotators rotate off : (silent unit shift)
    Apr : Judge model upgraded v1 to v2 : (silent, no re-baseline)
    May : Reported score jumps to 4.1 : leadership celebrates
    Jun : Prompt refactor regresses quality : (masked by generous judge)
    Later : Re-score Jan drafts with v2 : Jan is also 4.1 - real change is zero

Solution — make the instrument a versioned, monitored artifact:

flowchart TD
    A["New period's copilot drafts"] --> J["Eval oracle<br/>(pinned judge + rubric)"]
    J --> S["Reported Answer Quality"]
    G1["Frozen gold set<br/>(300 drafts, locked labels)"] --> J
    J --> D["Gold-set score vs.<br/>last period"]
    D -.->|"delta > 0.05"| AL["Judge-drift alarm<br/>(block the comparison)"]
    G2["Judge-vs-human agreement<br/>+ annotator kappa"] -.->|"kappa < 0.6"| AL
    G3["Judge upgrade proposed"] -.->|"breaking change:<br/>re-score last 2 quarters"| AL
    AL --> H["Re-baseline before<br/>the series continues"]

What good looks like

The lesson

An eval oracle is an instrument, not an authority: if the judge, the rubric, or the raters change, the units change, and last month's score and this month's are no longer on the same ruler. Freeze a gold set and re-score it every period so drift in the ruler is separated from change in the system — and treat any judge upgrade as a breaking change that must be re-baselined before the number means anything.

Named concepts: eval-oracle drift, frozen gold set, inter-annotator agreement, LLM-as-judge versioning, rubric anchoring.

Real-world echoes

Transfer

For the most important quality number on any dashboard your team reads, ask: who or what is the judge, and has it changed — model version, rubric wording, or the people scoring — since the last two data points you just compared? If you can't answer, you don't yet know whether your trend is the system or the ruler.

Make it a ticket: stand up a frozen gold set of ~300 items with locked labels for your highest-stakes eval, re-score it every period, and alarm when the gold-set score moves more than 0.05 on a 1–5 scale (2 points on a 100-scale) with identical inputs — and add the judge model ID and rubric version to the metric's metadata this sprint.

Related