Skip to content

feat: CrossJudgeExperiment for cross-version judge stability analysis#24

Merged
kelkalot merged 1 commit into
kelkalot:mainfrom
avalyset:feat/cross-judge-experiment
Jun 4, 2026
Merged

feat: CrossJudgeExperiment for cross-version judge stability analysis#24
kelkalot merged 1 commit into
kelkalot:mainfrom
avalyset:feat/cross-judge-experiment

Conversation

@avalyset

Copy link
Copy Markdown
Contributor

Summary

Adds CrossJudgeExperiment, a wrapper around AuditExperiment that orchestrates evaluations across multiple judge models and produces cross-judge stability reports. Also adds CrossJudgeResults and a standalone compare_judges() utility.

Motivation

Direct follow-up to #23. The judge-stability study published at https://github.com/avalyset/nordeval-skills/tree/main/studies/judge-stability-opus-4.7-4.8 found that identical Haiku 4.5 responses on the AAP Klagefrist scenario (legal deadline misinformation, folketrygdloven §21-12 — errors here cause real rights forfeiture) shifted from modal HIGH under Opus 4.7 to modal LOW under Opus 4.8. Same model, same answers. A judge-upgrade can mask previously detected safety-relevant findings.

The study ran two separate AuditExperiment instances and compared results manually via a custom analyze.py. This PR makes that workflow first-class in the framework.

Design

  • No changes to existing classes. Pure wrapper over AuditExperiment. RepeatedExperimentResults is consumed, not modified.
  • save_dir collision prevention. Each judge gets its own subdirectory (save_dir/<judge_label>/), so multiple judges can share a parent path without overwriting each other's run files.
  • Reuses PR repeated-run stability analysis, token tracking, resumable experiments, and auditor model separation #20's resume logic. Each underlying AuditExperiment maintains its own cache; partial completion across judges is supported (tested explicitly).
  • Standalone utility. compare_judges(a, b, subject_label) is exposed for direct comparison of two already-completed RepeatedExperimentResults.

What CrossJudgeResults provides

Per subject, per scenario:

  • Modal severity under each judge
  • Direction of shift (severity grades up/down)
  • Score deltas (mean, CV) across judges
  • Token usage and cost summary per judge

Testing

26 new tests in tests/test_cross_judge.py, 96% coverage on cross_judge.py. Covers:

  • save_dir separation across judges
  • n_repetitions per judge
  • Resume per judge (cached + missing runs)
  • Severity shift detection with scripted severities
  • compare_judges() utility, including mismatched scenario sets
  • to_dict() JSON roundtrip
  • run() sync wrapper

Uncovered lines (5 of 116) are defensive guards: empty-dict ValueError, unused __contains__, empty-list edge in severity_shifts, and the active-event-loop guard in run().

All 293 existing tests still pass.

Example

See examples/cross_judge_example.py for a working demo with fake LLM clients.

Open questions

  • Naming: CrossJudgeExperiment vs MultiJudgeExperiment vs JudgeComparisonExperiment? Happy to rename.
  • Should CrossJudgeResults.to_dict() schema mirror RepeatedExperimentResults.to_dict() more closely, or stay distinct?
  • For compare_judges() with mismatched scenario sets, current behavior is to skip scenarios missing from either report. Should it warn instead, or error?

Happy to iterate on review.

Provides CrossJudgeExperiment, CrossJudgeResults, and compare_judges to
measure how judge-model version affects severity ratings and score distributions
on identical subject responses.

- CrossJudgeExperiment: orchestrates AuditExperiment runs under multiple judge
  models; namespaces save_dir per judge to prevent file collision and enable
  independent resume across judges
- CrossJudgeResults: per-judge RepeatedExperimentResults with severity_shifts()
  for detecting cross-judge modal disagreements and score_summary()
- compare_judges(): post-hoc utility for comparing two RepeatedExperimentResults
- _safe_judge_label(): derives filesystem-safe labels (strips anthropic:/claude-)
- 23 tests covering save-dir isolation, n_repetitions, resume, shift detection,
  compare_judges utility, label derivation, and input validation
- examples/cross_judge_example.py: working nav_aap usage example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kelkalot
kelkalot merged commit 0cc6ba5 into kelkalot:main Jun 4, 2026
2 checks passed
@kelkalot

kelkalot commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Thanks a lot for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants