CIs shown by default
Everysuite.run() report now includes confidence intervals without any extra code:
avg_score hides. A model that scores 0.95 or 0.40 (never in between) has the same avg_score as one that always scores 0.67 — but they behave very differently. A bimodal distribution usually means the evaluation criterion has a sharp decision boundary and your model is straddling it.
Why single-run scores lie
LLMs are non-deterministic. Even withtemperature=0, hosted APIs introduce variance through hardware parallelism and batching. A confidence interval across sampled cases does not predict the range of repeated runs on a fixed suite. Repeated trials measure a different source of variation.
The fix: run more cases, run each case multiple times, and use confidence intervals to understand what your score actually means.
Confidence intervals with wilson_interval
The Wilson score interval is an approximate interval for a binomial proportion. It behaves better than the simple normal approximation near zero or one, but assumes independent observations. Correlated cases from the same document or conversation require analysis at that cluster level.
experiment.compare() shows these automatically:
Know how many cases you need
Before running an eval, calculate whether your test suite is large enough to detect the improvement you care about.
A 2 percentage-point improvement requires ~8,000 cases to confirm statistically. Most teams shouldn’t chase differences that small.
Power hints in compare()
When compare() finds a difference that doesn’t reach p < 0.05, it tells you how many more cases you’d need:
What a 100% pass rate actually tells you
New in 0.16.0. A suite at 100% can no longer detect improvement — and, less obviously, it is bad at detecting regressions too. Two properties on every report quantify what a perfect score can still claim:saturated is built on evaluated, not total. An all-error run is not saturated, but a run with one pass and nine errors can be. Inspect errors and skips separately; saturation does not establish coverage. min_detectable_regression anchors its variance near the observed rate, capped at a 0.95 baseline, so a perfect score can’t flatter its own sensitivity. Concretely, a 40-task suite at 100%:
purpose kwarg — '' (unset), 'capability', or 'regression':
purpose='regression' suite inverts it: 100% is the expected steady state, and any task below ceiling prints a triage warning instead (“N previously-passing task(s) below ceiling — something broke; triage before shipping”). That warning assumes you intend these cases to pass; the purpose flag does not load or verify a historical baseline. The purpose is copied onto the report and serialized in the JSON summary alongside saturated and min_detectable_regression; view --dir shows a saturated badge.
The 0% end of the scale has its own detector — see zero-pass suspects.
Multi-run flakiness detection
Combineruns=N with statistical rigor for per-case stability analysis:
The intervals above do not include uncertainty from missing cases, grader bias, or changes to the case distribution. Do not treat repeated cases as independent new tasks, or repeatedly stop at the first significant result without a sequential-testing procedure.
Starting configurations (not acceptance guarantees)
Multiple comparison correction
Running N evaluators and reporting N raw p-values inflates the false positive rate. At α=0.05 with 10 evaluators, you’d expect ~0.5 spurious “significant” results per run just by chance.exp.compare() displays Benjamini-Hochberg-adjusted p-values derived from
per-evaluator pass rates (the nearby score columns are descriptive). Its summary
comparison uses an unpaired two-proportion approximation. Per-evaluator tests
use total case counts, which can overstate evidence when evaluators skipped
cases or runs had errors. Use complete, comparable runs; for matched cases,
prefer baseline_report.compare(proposal_report) and its McNemar test.
The following output is illustrative:
Judge calibration
suite.calibrate() measures agreement with labeled examples. Despite the
name, it does not fit or update thresholds. Choose thresholds on a development
split, then measure performance on a separate test split. Review missing
evaluator coverage as well as the reported agreement.
- Inspect false accepts and false rejects separately, with sample sizes and uncertainty.
- Check each consequential task slice; overall agreement can hide rare failures.
- Set acceptance criteria from the consequences of a mistake. No universal agreement percentage establishes fitness for CI gating.
Interpretation checklist
Before trusting an eval result, ask:- Is the improvement statistically significant? (
exp.compare()shows p-value) - Did I test the difference directly? Separate confidence intervals can overlap even when a paired difference is significant. Use a comparison appropriate to the sampling design.
- Do I have enough cases? The power warning tells you automatically; use
runs_needed()to plan ahead. - Are there flaky cases inflating the variance? Check
report.flaky_count. - Are multi-evaluator comparisons corrected?
exp.compare()applies BH correction automatically; watch the*markers. - Is my judge calibrated? Measure agreement on held-out labeled samples and repeat after changes to the judge, rubric, parser, or task distribution.
- Is the suite saturated? A 100% pass rate only proves a Wilson floor; check
report.min_detectable_regressionfor what the suite can still see, and graduate it topurpose='regression'.

