Skip to main content
Two runs of the same dataset score 7.4 and 7.6. Did the change help? Honestly: you cannot tell. LLM judge scores drift when the rubric wording moves, and they bunch up in the 7-8 band, so a 0.2 gap between two averages is inside the noise. This is the reason teams stare at a green delta and still ship a regression. Head-to-head judging asks a different question, the one a person would actually ask: for this question, which of these two answers is better? A preference between two concrete answers is far more stable than either answer’s absolute score, and it is directly the claim you want to make when shipping a change.

Run one

Both runs must be of the same dataset - a per-question comparison needs the same questions. Run A is the candidate by convention, so “A wins” reads as “the new version won”. In the dashboard, the same thing lives under Head to head in the version comparison dialog, directly beneath the two averages it exists to second-guess. Nothing judges on open: the panel shows the last comparison for that pair, and judging again is a click, because every case costs a judge call.

Position bias, and why both_orders matters

LLM judges favor whichever answer they read first. Left alone, that bias decides comparisons, and you would never see it - the result looks like a clean verdict. AgentX designs against it rather than mentioning it:
  • The judge is shown “Answer 1” and “Answer 2”. It never learns which run is the candidate.
  • The presentation order alternates case by case, so a biased judge cannot favor one run across the whole batch.
  • both_orders=True judges every pair twice with the sides swapped. Any disagreement between the two passes is recorded as a tie, but only opposite verdicts (A in one order, B in the other) count as a position flip and raise the batch’s flip rate - the order decided it, not the answers. A tie-vs-win disagreement still becomes a tie, but it is ordinary judge wobble, not evidence of position bias, so it does not raise the flip rate.
A high flip rate does not mean the comparison narrowly passed. It means the comparison is inconclusive, and both the dashboard and assert_pairwise say so rather than reporting the win they would otherwise show. both_orders doubles the judge cost. That is the price of knowing whether the result is real.

Fail a build on it

Each check catches something a single average cannot: A comparison run without both_orders has no flip rate to check, so max_flip_rate is skipped rather than passing on a fabricated zero.

What is graded

The head-to-head uses the same material a normal run of that dataset uses: its evaluation criteria and each case’s expected result as the reference answer. This matters more than it sounds. Without the reference, a confidently vague answer (“Maybe, it depends”) can beat a correct specific one, because the judge has no ground truth to check against. Override the criteria per comparison when you want to compare on one axis:

Reading the result

Each case records which side the judge read first, so any verdict can be audited after the fact. comparison.summary.errors counts cases where the judge call itself failed (provider outage, unusable output) - those are excluded from the win/tie totals, never counted as real ties. Cases the two runs do not share - one side produced no answer, or the batch hit the 100-case per-comparison cap - are listed in comparison.skipped with a reason, rather than quietly dropped in a way that would make a partial comparison look like a sweep. Past comparisons are stored and included in backups:

When to use which

Absolute scores

“Is this good enough to ship?” Use a rating floor - CI gates and min_rating. A threshold needs a number.

Head to head

“Is this better than what we have?” Use pairwise. A comparison needs a preference, and preferences survive judge drift that absolute numbers do not.
Most teams want both: a floor that stops bad releases, and a head-to-head that proves the change was worth shipping.