Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Validate LLM judges against human labels using TPR/TNR metrics and train/dev/test splits. Use after writing a judge prompt to verify it agrees with human judgment.
.claude/skills/growthxai-output-eval-validate-judge/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 3% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 24% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 20% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 233% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 42% | 0% |
An LLM judge is only useful if it agrees with human judgment. This skill walks you through calibrating a judge against human-labeled data using True Positive Rate (TPR) and True Negative Rate (TNR) metrics. Do this before trusting any judgeVerdict(), judgeScore(), or judgeLabel() evaluator in your eval suite.
.prompt file — Written following output-eval-judge-promptground_truth.evals.<evaluator_name>.verdict: pass or failThis process applies only to LLM-based judges. For code-based Verdict.* evaluators, write unit tests instead.
Split your labeled datasets into three groups:
| Split | % of Data | Purpose | Example (100 datasets) | |-------|-----------|---------|----------------------| | Train | 10-20% | Source of few-shot examples in the judge prompt | 15 datasets | | Dev | 40-45% | Iterate on judge prompt, measure TPR/TNR | 42 datasets | | Test | 40-45% | Final held-out measurement, run once | 43 datasets |
Use a naming convention or subdirectories to separate splits:
Option A: Name prefixes
tests/datasets/
├── train_formal_pass_01.yml
├── train_casual_fail_01.yml
├── dev_technical_pass_01.yml
├── dev_ambiguous_fail_01.yml
├── test_simple_pass_01.yml
├── test_contradictory_fail_01.yml
└── ...Option B: Subdirectories
tests/datasets/
├── train/
│ ├── formal_pass_01.yml
│ └── casual_fail_01.yml
├── dev/
│ ├── technical_pass_01.yml
│ └── ambiguous_fail_01.yml
└── test/
├── simple_pass_01.yml
└── contradictory_fail_01.yml.prompt file. Never use dev or test examples — that's data leakageExecute the eval workflow against only the dev-split datasets:
bash# Run with cached output on dev datasets npx output workflow test <workflowName> --cached \ --dataset dev_technical_pass_01,dev_ambiguous_fail_01,dev_formal_pass_02,...
Or if using subdirectories, list the dev dataset names:
bashnpx output workflow test <workflowName> --cached \ --dataset $(ls tests/datasets/dev/ | sed 's/.yml//' | tr '\n' ',')
Save the output. You need the judge's verdict for each dataset to compare against ground truth.
Use --json to get machine-readable results:
bashnpx output workflow test <workflowName> --cached --dataset <dev_datasets> --json
The output includes per-dataset, per-evaluator verdicts that you can compare against ground_truth.evals.<evaluator_name>.verdict.
For the evaluator you're validating, build a confusion matrix from the dev results.
Using "fail" as the positive class (what you're trying to detect):
| | Judge says Fail | Judge says Pass | |---|---|---| | Human says Fail | True Positive (TP) | False Negative (FN) | | Human says Pass | False Positive (FP) | True Negative (TN) |
TPR (True Positive Rate) = TP / (TP + FN)
TNR (True Negative Rate) = TN / (TN + FP)
Dev set results for check_tone evaluator (42 datasets):
| | Judge: Fail | Judge: Pass | |---|---|---| | Human: Fail | 18 (TP) | 3 (FN) | | Human: Pass | 2 (FP) | 19 (TN) |
Raw accuracy = (TP + TN) / total = (18 + 19) / 42 = 88.1%
This looks fine, but masks problems. If your dataset were 90% pass (class imbalance), a judge that always says "pass" would get 90% accuracy while catching zero failures (TPR = 0%). TPR and TNR measure what actually matters: catching failures and not crying wolf.
For every case where the judge disagrees with the human label, determine the root cause.
The judge said "pass" but the human said "fail." For each:
The judge said "fail" but the human said "pass." For each:
Track each disagreement to guide prompt iteration:
| Dataset | Human | Judge | Root Cause | Fix | |---------|-------|-------|------------|-----| | dev_technical_pass_03 | pass | fail | Judge flagged "it's" as casual but context was a direct quote | Add exception: "Contractions within direct quotes are acceptable" | | dev_ambiguous_fail_02 | fail | pass | Judge missed subtle tone shift in paragraph 3 | Add borderline few-shot example showing mid-text tone drift |
Apply the fixes from Step 4 to the judge .prompt file. Then re-run on the dev set:
bashnpx output workflow test <workflowName> --cached --dataset <dev_datasets>
Recompute TPR and TNR. Repeat until both metrics meet the target.
| Metric | Target | Minimum Acceptable | |--------|--------|--------------------| | TPR | > 90% | > 80% | | TNR | > 90% | > 80% |
If you can't reach 80%/80% after 3-4 iterations:
.prompt frontmatterEach iteration:
.prompt file (not random changes)Once dev metrics meet the target, run the judge on the held-out test set exactly once:
bashnpx output workflow test <workflowName> --cached --dataset <test_datasets> --json
Compute TPR and TNR on the test results. Record these as the final metrics.
Document the final validation results alongside the judge prompt:
markdown# Validation: check_tone (judge_tone@v1.prompt) # Date: 2026-03-25 # Model: claude-haiku-4-5-20251001 ## Dev Set (42 datasets) - TPR: 90.5% (19/21) - TNR: 95.2% (20/21) ## Test Set (43 datasets) - TPR: 88.0% (22/25) - TNR: 94.4% (17/18) ## Conclusion: APPROVED — both metrics above 80% minimum
Store this in a VALIDATION.md file next to the judge prompt or in the evaluator's documentation.
output-eval-judge-prompt — Design the judge prompt being validatedoutput-eval-error-analysis — Source of human-labeled data for validationoutput-eval-dataset-design — Generate additional labeled datasets if you need more dataoutput-dev-eval-testing — output workflow test CLI, --cached and --dataset flagsoutput-eval-audit — Audit whether existing judges have been validated| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 28,990 | 14,814 | -49% | 1 | 1 | 0% | 5,209 | 5,353 | +3% | 0 | 0 | — |
case-02 | fail→pass | 29,637 | 15,433 | -48% | 1 | 1 | 0% | 4,389 | 5,450 | +24% | 0 | 0 | — |
case-03 | fail→pass | 25,349 | 12,036 | -53% | 1 | 1 | 0% | 4,112 | 4,935 | +20% | 0 | 0 | — |
case-04 | pass→pass | 24,444 | 3,202 | -87% | 1 | 1 | 0% | 1,851 | 3,126 | +69% | 0 | 0 | — |
case-05 | fail→pass | 6,021 | 2,252 | -63% | 1 | 1 | 0% | 892 | 2,967 | +233% | 0 | 0 | — |
case-06 | pass→pass | 9,859 | 6,203 | -37% | 1 | 1 | 0% | 1,605 | 3,739 | +133% | 0 | 0 | — |
case-07 | fail→pass | 14,483 | 3,865 | -73% | 1 | 1 | 0% | 2,281 | 3,243 | +42% | 0 | 0 | — |
case-08 | fail→pass | 14,650 | 2,241 | -85% | 1 | 1 | 0% | 2,270 | 3,022 | +33% | 0 | 0 | — |
case-09 | fail→pass | 16,199 | 2,853 | -82% | 1 | 1 | 0% | 2,626 | 3,127 | +19% | 0 | 0 | — |
case-10 | fail→pass | 12,550 | 5,949 | -53% | 1 | 1 | 0% | 1,809 | 3,524 | +95% | 0 | 0 | — |
case-11 | pass→pass | 16,648 | 7,589 | -54% | 1 | 1 | 0% | 2,435 | 3,741 | +54% | 0 | 0 | — |
case-12 | pass→pass | 10,294 | 4,527 | -56% | 1 | 1 | 0% | 1,506 | 3,300 | +119% | 0 | 0 | — |
case-13 | fail→pass | 14,763 | 3,617 | -75% | 1 | 1 | 0% | 2,357 | 3,331 | +41% | 0 | 0 | — |
case-14 | fail→pass | 12,224 | 2,204 | -82% | 1 | 1 | 0% | 1,913 | 2,952 | +54% | 0 | 0 | — |
case-15 | pass→pass | 12,186 | 6,655 | -45% | 1 | 1 | 0% | 1,955 | 3,772 | +93% | 0 | 0 | — |
case-16 | pass→pass | 18,744 | 6,708 | -64% | 1 | 1 | 0% | 2,773 | 3,665 | +32% | 0 | 0 | — |
case-17 | pass→pass | 15,251 | 5,678 | -63% | 1 | 1 | 0% | 2,287 | 3,561 | +56% | 0 | 0 | — |
case-18 | fail→pass | 14,060 | 5,817 | -59% | 1 | 1 | 0% | 2,019 | 3,614 | +79% | 0 | 0 | — |
case-19 | pass→pass | 9,028 | 1,892 | -79% | 1 | 1 | 0% | 1,387 | 2,880 | +108% | 0 | 0 | — |
case-20 | pass→pass | 16,144 | 6,108 | -62% | 1 | 1 | 0% | 2,680 | 3,624 | +35% | 0 | 0 | — |
case-21 | pass→pass | 16,070 | 6,207 | -61% | 1 | 1 | 0% | 2,554 | 3,698 | +45% | 0 | 0 | — |
case-22 | pass→pass | 14,925 | 10,081 | -32% | 1 | 1 | 0% | 2,547 | 4,253 | +67% | 0 | 0 | — |
case-23 | pass→fail | 18,526 | 12,970 | -30% | 1 | 1 | 0% | 2,986 | 4,802 | +61% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +43 percentage points is the difference between those two pass rates over the 23 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.