Eval Harness First
Seth HobsonCommunityCreated by Seth Hobson
- Skill
- Workflow
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured workflow for building an evaluation harness that acts as the primary gate for fine-tuning efforts. It emphasizes data curation, error analysis, and the creation of a regression suite that serves as both the training data source and the final checkpoint validator. By establishing deterministic graders and calibrated LLM-judges, the harness ensures that every fine-tuning run is measured against a consistent, versioned baseline, preventing regressions and providing a clear metric for model improvement.
What it does
- Guides the collection and axial coding of traces into failure buckets.
- Defines a directory structure for versioned goldens, graders, and drift suites.
- Provides templates for deterministic and LLM-based grading.
- Outlines a calibration protocol for LLM-judges to ensure reliable verdicts.
- Establishes a baseline against the unmodified base model for future comparisons.
- Ensures evaluation data is strictly separated from training data.
When to use it
- Before starting any fine-tuning effort.
- When converting production traces into a structured evaluation set.
- When calibrating an LLM-judge against human-labeled data.
- When establishing a CI regression suite for model checkpoints.
Advantages
- Prevents regressions by gating checkpoints against a fixed baseline.
- Provides a clear, data-driven basis for model promotion.
- Reduces noise in evaluation by using binary pass/fail grading.
- Ensures evaluation metrics are reproducible and versioned like code.
Usage examples
- “How do I start the Phase 0 evaluation setup for my fine-tuning project?”
- The agent will guide you through collecting 100+ traces, performing axial coding to define 4-8 failure buckets, and creating the initial eval/ directory structure.
- “I need to set up a grader for my JSON schema compliance failure bucket.”
- The agent will provide the schema-compliance Python template and explain how to integrate it into the eval/graders/ directory.
- “How do I calibrate my LLM-judge for the tone-consistency bucket?”
- The agent will walk you through the labeling, splitting, and TPR/TNR calculation protocol defined in the calibration reference.
- “What should I do before running my first fine-tuning training config?”
- The agent will verify the Phase 0 Exit Checklist, ensuring goldens, graders, judge calibration, and the baseline file are all present and valid.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/wshobson/eval-harness-first/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip eval-harness-first.zip -d .agents/skills
Documentation by Seth Hobson
Eval Harness First
The Phase 0 gate for the whole plugin:
finetuning-method-selection and every downstream
skill assume this harness exists before a training
config gets written. The harness is not a run-end
side artifact — it is the data-curation engine. The
same labeled traces that build the goldens feed
training data, minus an explicit holdout.
Input: production/agent traces if they exist, or
a task spec if they don't, plus labelers willing to
grade ≥100 examples.
Output format: the eval/ directory below —
goldens, graders, drift suite, and the base-model
baseline that later phases gate on.
The Gate
No eval harness, no fine-tune. Skip to a training config and there is nothing to measure against, nothing to catch regressions, and no labeled data to train on. The flywheel:
Collect traces — production/agent spans, or synthetic tasks if none exist yet.
Error analysis — open coding on ≥100 traces, axial coding into 4–8 failure buckets.
One grader per bucket — deterministic first; calibrated LLM-judge only for genuinely subjective criteria.
Prioritize by frequency × severity × value.
The labeled traces feed dataset curation, minus an explicit holdout. Every
eval/goldens.jsonlID stays excluded from training data by ID.Train.
Re-run the same harness on the checkpoint — not a different, looser one.
Drift detection feeds back to step 2 — new production failure modes re-open error analysis.
Steps 2–4 build the harness; steps 5–8 are why it must exist first — it is both the training data source and the checkpoint's exit gate.
Building Goldens
From traces, when they exist: run error analysis — open coding on ≥100 real traces (read them, tag failures in your own words, no fixed taxonomy yet), then axial coding to collapse those tags into 4–8 named failure buckets. Fewer than 4 means the coding pass was too shallow; more than 8 means buckets need merging. Exception: single-failure-surface tasks (e.g. strict-schema extraction) may land at 1–2 buckets with per-field sub-metrics inside one grader — don't invent artificial splits with no evidence behind them.
Synthetic, when traces don't exist yet: dimension-based generation — enumerate the axes that matter (task type, difficulty, edge case, persona) and sample the cross-product; free- generated prompts cluster around whatever's easiest to write.
Goldens are versioned like code — commit
eval/goldens.jsonl, diff it in review, tag it per release. It doubles as the CI regression suite.
Graders
One grader per failure bucket from error analysis — not one for the whole eval set. A single blended score hides which bucket regressed.
Deterministic first. Regex, schema validation, or execution checks are cheaper, reproducible, and need no calibration.
LLM-judge only for genuinely subjective criteria — tone, faithfulness, "which response is better" — where no deterministic check can express it.
Binary pass/fail over Likert. A 1–5 or 1–10 scale is noisier to calibrate and harder to apply consistently; collapse to pass/fail.
Drift-suite MMLU-style scoring: prefer logprob over generate-and-extract — a tight token budget makes generate-and-extract parse-brittle for models that preamble, conflating format compliance with the knowledge being measured. Templates for all four grader shapes and this scoring note:
references/grader-templates.md.
Judge Calibration Is a Prerequisite
Any bucket routed to an LLM-judge needs calibration before its verdicts count for anything beyond exploration — a hard prerequisite, not a nice-to-have. N/A when no bucket routes to a judge — an all-deterministic harness has nothing to calibrate; state that rather than leaving this section unaddressed.
Label ≥100 items, split train/dev/sealed test (report once, no re-touching after).
Report TPR and TNR, not one blended accuracy number — a judge can hit 90% by always saying "pass" on a skewed set.
Pin the judge to a fixed model snapshot and recalibrate on judge-model change, quarterly regardless.
The judge must come from a different model family than the model under test.
A judge that misses the agreed TPR/TNR bar ships advisory-only — flags for human review, never gates a promotion. Full protocol, bias correction, and recalibration checklist:
references/judge-calibration.md.
The Baseline
Before Phase 1 (method selection) starts, run the full harness — goldens plus the capability-drift suite — against the unmodified base model. This is the number every later checkpoint gets compared against.
eval/baseline-<model>.json is the gate token. No
baseline file, no comparison basis for
checkpoint-promotion — a checkpoint that "looks
better" against nothing measured isn't a finding.
Directory Contract
eval/
├── goldens.jsonl # labeled traces + synthetic goldens, versioned
├── graders/ # one module per failure bucket
│ ├── schema_compliance.py
│ ├── exact_match.py
│ └── rubric_judge.py
├── drift-suite.yaml # frozen benchmarks + 200-500 domain-adjacent items
└── baseline-<model>.json # gate token: harness + drift suite vs the base model
runs/
└── <run-id>/
└── results.json # per-run harness output, one per checkpointeval/ persists across runs and lives outside
runs/ — the fixed measuring stick, not a run
artifact. runs/ is disposable; eval/ is not.
Never let a run script write into eval/. Canonical
location: every per-trace results.json — the
Phase 0 baseline included — lives at
runs/<run-id>/results.json, never under
eval/runs/...; an instruction requesting the
latter is wrong, not this contract.
Phase 0 Exit Checklist
Before finetuning-method-selection, confirm:
≥100 traces open-coded; 4–8 failure buckets (N/A floor for synthetic goldens on a single-failure- surface task — see the Building Goldens exception; bucket count then comes from post-baseline error analysis instead).
eval/goldens.jsonlcommitted and versioned.One grader per bucket, deterministic first.
Judges calibrated — TPR/TNR, snapshot pinned, different family (N/A when no bucket routes to an LLM-judge; state that explicitly).
eval/drift-suite.yamlfrozen.eval/baseline-<model>.jsonwritten.
Missing any of the six (or its stated N/A)? Not
Phase 0 complete — /finetune checks the baseline
file before a run.
Related Skills
General-purpose evaluation guidance (dashboards, A/B
testing, non-fine-tuning harnesses) lives in the
llm-application-dev plugin's llm-evaluation
skill — this skill covers only the fine-tuning
coupling: goldens that double as training data, and
the baseline that gates a checkpoint.
finetuning-method-selection— routes here first.dataset-curation— formats these traces into training rows.trace-to-training-data— turns graded traces into training examples.checkpoint-promotion— consumesbaseline-<model>.json, re-runs this harness on each candidate checkpoint.
References
references/grader-templates.md— runnable grader examples per shape, plus adrift-suite.yamlexample and MMLU logprob-scoring note.references/judge-calibration.md— the calibration protocol, including the all- deterministic N/A path.
Files
- ATTRIBUTION.mdAdded by AutoGineer982 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md7.8 KiB
- references/grader-templates.md9.9 KiB
- references/judge-calibration.md6.5 KiB
references/judge-calibration.md
Last verified: 2026-07-14
Judge Calibration Protocol
The full procedure behind SKILL.md's "Judge
Calibration Is a Prerequisite" section. Any grader
routed to an LLM-judge follows this before its
verdicts count toward a pass rate or a checkpoint
promotion decision.
N/A Path: All-Deterministic Harness
If error analysis produced zero buckets that route to
an LLM-judge — every grader is regex, schema, or
execution-based — this entire protocol is N/A for the
run, not an unsatisfiable checklist item. Phase 0
Exit Checklist item 4 in SKILL.md is satisfied by
stating this explicitly (e.g. "Judge calibration:
N/A — all N graded criteria are deterministic") rather
than leaving it blank or blocking Phase 0 completion
on a judge that was never going to exist. This is
common on a greenfield strict-schema or exact-match
task with synthetic goldens — don't invent a
subjective criterion just to have something to
calibrate.
1. Label
Collect human labels for ≥100 items covering the
failure bucket the judge will grade. Use the same
labelers (or a labeling rubric tight enough to be
interchangeable) that produced the axial-coding
buckets in SKILL.md's Building Goldens section —
a judge calibrated against a different notion of
"pass" than the one used to build goldens will
silently diverge from what the harness is supposed
to measure.
2. Split
Divide the labeled set three ways and keep the splits separate for the whole calibration cycle:
| Split | Purpose | Size |
|---|---|---|
| train | Few-shot examples embedded in the judge prompt | ~20-30% |
| dev | Iterate the prompt, catch obvious misses | ~30-40% |
| sealed test | Report TPR/TNR once; never re-touch after | ~30-40% |
The sealed-test split is sealed: if a dev-split iteration cycle causes the reported test-split number to move, that number is no longer a valid generalization estimate — re-seal a fresh test split instead of re-running against the same one.
3. Compute TPR/TNR
Run the judge (with train-split few-shot examples in the prompt) against the sealed test split and compute:
def tpr_tnr(judge_verdicts: list[bool], human_labels: list[bool]) -> tuple[float, float]:
"""True Positive Rate (sensitivity) and True
Negative Rate (specificity) against human labels.
A single blended accuracy number hides which
direction the judge is biased toward — always
report both, never accuracy alone.
Raises ValueError on empty or mismatched-length
input, or if the sealed test split lacks either
class — a `zip()` over unequal lists silently
drops the extra items, which can produce
apparently valid metrics from a partial or
miscollected split.
"""
if not judge_verdicts or not human_labels:
raise ValueError("tpr_tnr: judge_verdicts and human_labels must be non-empty")
if len(judge_verdicts) != len(human_labels):
raise ValueError(
f"tpr_tnr: length mismatch ({len(judge_verdicts)} verdicts vs "
f"{len(human_labels)} labels) — check the split for a collection bug"
)
tp = sum(j and h for j, h in zip(judge_verdicts, human_labels))
fn = sum((not j) and h for j, h in zip(judge_verdicts, human_labels))
tn = sum((not j) and (not h) for j, h in zip(judge_verdicts, human_labels))
fp = sum(j and (not h) for j, h in zip(judge_verdicts, human_labels))
if (tp + fn) == 0:
raise ValueError("tpr_tnr: no positive-labeled items in this split — TPR undefined")
if (tn + fp) == 0:
raise ValueError("tpr_tnr: no negative-labeled items in this split — TNR undefined")
return tp / (tp + fn), tn / (tn + fp)Agree on a TPR/TNR bar with whoever owns the eval harness before running this step — a common starting bar is ≥0.85 on both, tightened per bucket based on how costly a false pass or false fail is downstream.
4. Bias-Correct Reported Rates
A judge with unequal TPR/TNR does not report the true pass rate of the model under test — it reports a rate skewed by its own asymmetric error pattern. Correct the observed pass rate before publishing it:
def bias_corrected_pass_rate(observed_pass_rate: float, tpr: float, tnr: float) -> float:
"""Rogan-Gladen style correction: recovers the
true pass rate from the judge's observed rate and
its TPR/TNR, rather than reporting the judge's raw
output as if it were ground truth.
"""
denom = tpr + tnr - 1
if denom <= 0:
raise ValueError("Judge is at or below chance — do not correct, recalibrate")
return (observed_pass_rate + tnr - 1) / denomIf tpr + tnr - 1 is small (judge close to chance),
the correction blows up and becomes unreliable — that
is itself a signal the judge needs a prompt rewrite,
not a correction formula.
5. Pin the Snapshot
Record the exact judge model snapshot/version used for calibration alongside the TPR/TNR numbers. An unpinned judge (a "latest" alias that moves under you) invalidates the calibration the moment the underlying model changes — the TPR/TNR numbers stop describing the judge actually in use.
6. Recalibrate
Two triggers, either one is sufficient:
Judge-model change — any change to the pinned snapshot, intentional or forced (deprecation).
Quarterly, regardless — schedule recalibration even with no judge change, since the underlying task distribution (what "hard" cases look like) drifts as the model under test improves and error analysis surfaces new failure modes.
Recalibration reruns steps 1-4 in full — it is not a partial refresh of just the test split.
7. Miscalibration Fallback
If the judge cannot hit the agreed TPR/TNR bar after prompt iteration on the dev split:
The judge ships advisory-only: its verdicts surface in review tooling for a human to consider, but do not gate a checkpoint promotion and are not counted into a reported pass rate.
Do not lower the TPR/TNR bar to make an uncalibrated judge "pass" — that defeats the point of calibrating in the first place.
Prefer routing the bucket to a deterministic grader if the criterion can be reframed as one (see
SKILL.md's Graders section) over shipping a permanently advisory-only judge.
Different Model Family, Always
The judge model must come from a different model family than the model under test, for every calibration cycle and every recalibration — a judge evaluating outputs from its own family is a biased grader, and this cannot be corrected away with the TPR/TNR formula above since the bias is systematic rather than random.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- Seth Hobson
- Publisher
- Seth Hobson (Community)
- Source
- wshobson/agents · plugins/llm-finetuning/skills/eval-harness-first · commit 4bde316
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Seth Hobson.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download eval-harness-first.zip