Causal Judge Evaluation
Calibrate judge scores to observed outcomes.
Reuse an informative judge and available outcome labels to reduce evaluation costs.
CJE estimates policy means and paired differences, including sampling and calibration uncertainty. Reusing a calibration map across policies or time requires evidence that it still applies; diagnostics make unresolved assumptions visible.
Cut evaluation costs
14× cheaper than full oracle labeling at 5% oracle coverage on the Arena benchmark (arXiv v3), where a stronger model's ratings stood in for human labels; intervals are wider than with full labels. At equal precision, a judge cuts the labels you need by about the share of your outcome's variance it explains within a policy, so measure that on a pilot slice first.
Produce auditable results
Save estimates, intervals, sampling assumptions, and audit evidence so others can inspect the comparison.
Where judge scores can mislead

Real example: Claude Code sycophancy. absolutelyright.lol
A hypothetical, but a familiar one: January, users love the affirmation. Your LLM judge scores it 9/10. March: users find it cloying. The judge still gives 9/10. You ship it, and satisfaction drops — the judge never notices.
Raw judge scores are surrogates (S), not outcomes (Y). They fail in three ways:
1. Preference Inversion
Higher judge scores can accompany lower real-world quality due to verbosity bias or sycophancy.
2. Invalid Confidence Intervals
Intervals around raw judge means quantify sampling noise on the judge scale, not bias against the oracle. In the Arena benchmark, those nominal 95% intervals had 0% oracle coverage.
3. Scale Arbitrariness
Is 4.2 actually 5% better than 4.0? Or just noise? Without calibration, you can't know.
The Solution: Calibration
Your judge gives you scores. Those scores don't directly equal quality. CJE figures out the mapping (like calibrating a scale against known weights).
- 1. Supply outcome labels: Use a probability-sampled slice with an explicit outcome definition. Existing expert ratings or production outcomes can help when identity, selection, and population assumptions are justified.
- 2. Learn the mapping: CJE learns
f(S, X) → Yusing isotonic regression or two-stage calibration. - 3. Estimate outcomes: Estimate policy outcomes with intervals that include evaluation sampling and finite-label calibration uncertainty on supported paths.
What should my Y labels (oracle) be?
- Expert audits (domain specialists rate quality 1-5)
- Observed conversion or task success with a defined measurement window
- User satisfaction surveys (post-task ratings)
- Long-term metrics (7-day retention, support tickets)
Quick Start
pip install -U "cje-eval>=0.9.2"- Python 3.10–3.13 (on an older Python, a plain
pip install cje-evalinstalls the legacy 0.5 line) - Observed policy responses with judge scores and preserved prompt identities
- Probability-sampled oracle labels: calibration requires at least 4 independent labeled prompt clusters; 10 or more is a starting recommendation, not a precision or power guarantee
Using a coding agent? Paste the handoff from the README; it has the agent run cje skill, which prints the instructions bundled with the installed version.
Plotting requires pip install "cje-eval[viz]". Core summaries and comparisons do not require the extra.
Upgrading? Read the changelog first. Two-stage calibrators saved before 0.8.0 need refitting from retained inputs.
For development, clone the repository and run poetry install or pip install -e .; a working example lives in examples/arena_sample/. The command-line interface runs the same analysis:
# Analyze a directory of per-policy response files
python -m cje analyze responses/
# Save results to JSON
python -m cje analyze data.jsonl -o results.json
# Validate data format before running
python -m cje validate data.jsonl --verbose
# Print the agent skill bundled with the installed version
cje skillfrom cje import analyze_dataset
# Synthetic data: two policies, production vs candidate, each answered the same
# 20 prompts. A separate fixed judge model scored all 40 responses; human
# raters labeled 10 of production's (None = not labeled).
judge_scores = {
"production": [0.62, 0.68, 0.72, 0.76, 0.79, 0.83, 0.85, 0.88, 0.91, 0.95,
0.64, 0.69, 0.73, 0.77, 0.80, 0.84, 0.87, 0.89, 0.92, 0.94],
"candidate": [0.70, 0.74, 0.75, 0.78, 0.81, 0.83, 0.86, 0.90, 0.93, 0.94,
0.72, 0.76, 0.79, 0.80, 0.84, 0.85, 0.88, 0.89, 0.91, 0.95],
}
human_labels = [0.55, 0.60, 0.70, 0.74, 0.75, 0.80, 0.90, 0.92, 0.88, 0.97,
None, None, None, None, None, None, None, None, None, None]
draws = {
"production": [
{"prompt_id": f"q{i:02d}", "judge_score": s, "oracle_label": y}
for i, (s, y) in enumerate(zip(judge_scores["production"], human_labels))
],
"candidate": [
{"prompt_id": f"q{i:02d}", "judge_score": s}
for i, s in enumerate(judge_scores["candidate"])
],
}
results = analyze_dataset(fresh_draws_data=draws)
# Estimates, then the paired test of candidate vs production on the shared
# prompts (decide from that, not by comparing the two intervals by eye).
print(results.summary())CJE Estimation Results (method: calibrated_direct) candidate 0.824 95% CI [0.766, 0.882] [borrowed calibration] production 0.786 95% CI [0.696, 0.876] candidate: no labels of its own; its estimate and every difference involving it assume production's calibration transfers, which the CI and p-value do not cover (the true difference can have either sign). Label >=20 random responses of candidate, or audit transport on a held-out random sample of their responses (size it with plan_transport_audits). Best by point estimate: candidate (point estimate, not a test) Limitations: borrowed calibration (residual transport NOT_CHECKED) Paired differences (p unadjusted): candidate - production: +0.038 95% CI [-0.027, +0.102] p=0.22 [borrowed calibration: candidate] No reliable winner: every paired CI includes 0 (not evidence that they are equal) Status: warning
This synthetic example has 20 shared prompts and 10 labeled prompt clusters. It demonstrates the API, not adequate power for a real evaluation. The paired CI includes 0, so there is no reliable winner. Candidate has no labels of its own, so it is marked [borrowed calibration]: its estimate and the paired difference assume production's calibration transfers.

Arena benchmark illustration, separate from the quickstart data above. Use paired comparisons and diagnostics to assess a policy difference.
Levels, rankings, and existing outcomes
Historical judge/outcome pairs can train calibration. Logged responses can also describe an observed evaluation population. Neither estimates the unseen outputs of another policy. Match labels to exact responses, justify feedback selection, and preserve dependence clusters. Existing outcomes can reduce new annotation cost; count their acquisition cost separately in a total-cost comparison.
A shared residual offset can cancel in a policy difference, so failed absolute calibration does not by itself disprove the ordering. Monotone scores or a scalar support badge do not establish ordering under a shift either. CJE has no validated automatic label-free reuse gate.
Representative target labels can correct a current estimate even when an audit of the old map fails. Audit-only probes do not change the estimate. See the correction guide and audit-budget guide.
How It Works
Supply responses observed under each policy on the population you want to compare. These can be generated for the evaluation or retained in production logs with a defensible sampling design:
Run your judge on all responses
Cheap model scores every response (e.g., GPT-4.1 Nano)
Label a small slice with your oracle
Choose a probability-sampled label budget for the precision and audit power your decision requires.
CJE learns the calibration mapping
Isotonic regression fits judge→oracle relationship
Get calibrated estimates and uncertainty
Account for sampling and calibration uncertainty; audit reuse across policies or time.
Note: Earlier versions of CJE included off-policy estimators (Importance Sampling and Doubly Robust). We benchmarked them and removed them in v0.4.0: on the Arena benchmark, calibrated IPS ranked policies at 47% pairwise accuracy — below a coin flip — despite healthy-looking effective sample sizes, because LLM policies share almost no overlap in output space. Direct-mode calibration is what the evidence supports. See benchmark paper →
What You Get
Calibrated Estimates
Estimates target the declared oracle outcome and scale. A value of 0.75 is a 75% rate only for an appropriate binary or proportion outcome; an audit pass does not redefine that target.
results.estimatesHonest Confidence Intervals
Intervals include sampling and finite-label calibration uncertainty. Their coverage depends on the sampling design, dependence model, and calibration assumptions; it is not guaranteed by the nominal 95% label.
results.standard_errorsTransportability Auditing
Detect when calibration drifts. Declare the drift you can tolerate (a margin in outcome units), and a weekly probe grades PASS / FAIL / INCONCLUSIVE against it before you ship stale estimates.
audit_transportability(..., delta_max=0.05)
Planned probes can detect drift relevant to a decision. Week 3's residual CI lands outside the declared margin → FAIL → time to recalibrate.
Bonus: Debugging failures. When transportability fails, inspect residuals to find what's fooling your judge: sycophancy, verbosity bias, adversarial patterns. compute_residuals()
Tested on 5k Arena Prompts
Benchmarked 13 estimators on LMSYS Arena data with GPT-5 as oracle, its ratings standing in for human labels.
How Much Data Do I Need?
It depends on your domain: outcome variance, judge quality, and effect sizes you care about.
Plan around the decision. Choose a practical difference, label budget, and analysis sample size before collecting labels. Use a pilot to plan precision; use the separate audit-budget planner to assess whether transport checks can resolve.
- • Comparing policies? Use
results.compare_policiesfor the paired difference and its interval. Marginal interval overlap can hide a clear paired difference. - • Claiming equivalence? Predeclare a practical difference margin and require suitable interval evidence inside it. A nonsignificant difference is inconclusive.
- • Adding labels after a look? Use a planned follow-up analysis or a valid sequential design; repeatedly checking until a result appears changes the inference.
In our Arena benchmark, 250 oracle labels (5% of 5k samples) achieved 99% ranking accuracy in that configuration; the average across all configurations is 94%. Your mileage will vary: at equal precision, a judge cuts the labels you need by about the share of your outcome's variance it explains within a policy, so label a pilot slice of a few hundred responses and measure that before sizing the budget.
When to Use CJE
✅ Use CJE if:
- • You need policy means or paired comparisons on an oracle outcome
- • You suspect judge favors verbose/sycophantic answers
- • You can supply a probability-sampled oracle slice or justify calibration reuse
- • You want valid confidence intervals, not just point estimates
❌ Don't use CJE if:
- • You have zero gold labels (can't calibrate without ground truth)
- • Your outcome can't be defined or measured
- • You need counterfactual outputs for policies absent from your data
Defining Your Target (Y*)
CJE calibrates your judge scores to outcomes, but which outcomes? If your oracle labels are noisy or misaligned with what you actually care about, you're calibrating to the wrong target.
Y*-alignment ensures your generation prompt and evaluation rubric target the same welfare outcome. Use a Standard Deliberation Protocol (SDP) to operationalize what "good" means in your domain.
Read: Aligning Generation & Evaluation →Next Steps
Ready to measure?
Start with the tutorial notebook or read the paper.
