eval-harness-first
wshobson/agents
Build the evaluation harness that gates fine-tuning — goldens, graders, judge calibration, and baselines.
What is eval-harness-first?
Establishes Phase 0 of fine-tuning: a labeled golden set, per-failure-mode graders, judge calibration, and a base-model baseline that all downstream checkpoints are measured against. Use this before starting any fine-tuning effort, when converting production traces into an eval set, or when calibrating judges against human labels.
- Error analysis on ≥100 traces to identify 4–8 failure buckets via open and axial coding
- One deterministic or LLM-judge grader per failure bucket, with binary pass/fail scoring
- Judge calibration protocol with TPR/TNR reporting and model-family separation
- Frozen drift-suite benchmarks for capability regression detection
- Base-model baseline (eval/baseline-<model>.json) that gates all checkpoint comparisons
- Goldens that double as training data source, with explicit holdout by ID
How to install eval-harness-first
npx skills add https://github.com/wshobson/agents --skill eval-harness-first- ≥100 production traces or a task specification for synthetic generation
- Labelers willing to grade at least 100 examples
- Access to a base model to establish the baseline
How to use eval-harness-first
- 1.Run open coding on ≥100 traces, tagging failures in your own words without a fixed taxonomy
- 2.Perform axial coding to collapse tags into 4–8 named failure buckets
- 3.Create one grader per bucket: deterministic first (regex, schema, execution), LLM-judge only for subjective criteria
- 4.Calibrate any LLM-judges on ≥100 labeled items split into train/dev/sealed test, reporting TPR and TNR
- 5.Freeze the drift-suite.yaml with benchmarks and 200–500 domain-adjacent items
- 6.Run the full harness against the unmodified base model and save results to eval/baseline-<model>.json
- 7.Commit eval/goldens.jsonl, eval/graders/, eval/drift-suite.yaml, and the baseline file before proceeding to method selection
Use cases
- Starting a fine-tuning effort with no existing eval harness
- Converting production agent traces into a labeled golden set and training dataset
- Calibrating an LLM-judge against human labels before using it to grade checkpoints
- Detecting capability drift and new failure modes in production traces
- Gating checkpoint promotion decisions against a frozen baseline
- ML engineers building fine-tuning pipelines
- Teams converting production traces into training data
- Practitioners setting up evaluation gates before model training
- Anyone establishing baseline metrics for checkpoint comparison
eval-harness-first FAQ
Use dimension-based synthetic generation: enumerate axes that matter (task type, difficulty, edge case, persona) and sample the cross-product. Avoid free-generated prompts, which cluster around whatever is easiest to write.
No. Use deterministic checks (regex, schema validation, execution) first—they are cheaper and reproducible. Route only genuinely subjective criteria (tone, faithfulness, comparative quality) to an LLM-judge, and calibrate those judges before use.
Label ≥100 items split into train/dev/sealed test. Report TPR and TNR separately, not a blended accuracy. Pin the judge to a fixed model snapshot and use a different model family than the one under test. Recalibrate on judge-model change or quarterly.
No. The baseline (eval/baseline-<model>.json) is the gate token for checkpoint promotion. Without it, there is no comparison basis—a checkpoint that 'looks better' against nothing measured is not a finding.
The same labeled traces that build the goldens feed training data, minus an explicit holdout. Every ID in eval/goldens.jsonl stays excluded from training by ID, ensuring clean separation between eval and train sets.
Full instructions (SKILL.md)
Source of truth, from wshobson/agents.
name: eval-harness-first description: Build the evaluation harness that gates every fine-tuning run — golden sets, per-failure-mode graders, judge calibration, and base-model baselines. Use when starting a fine-tuning effort, when converting traces into an eval set, or when calibrating a judge against human labels.
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 checkpoint
eval/ 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.
Related skills
More from wshobson/agents and the wider catalog.

evaluation-methodology
Understand PluginEval's quality scoring methodology, dimensions, rubrics, and anti-patterns.

event-store-design
Design and implement event stores for event-sourced systems with architecture patterns and technology guidance.

fastapi-templates
Production-ready FastAPI project templates with async patterns, dependency injection, and error handling.

file-conversion
Convert files between 999 formats (PDF, images, video, audio, documents, data) via free ChangeThisFile service.

finetuning-method-selection
Route fine-tuning decisions: whether to fine-tune at all, and which method (SFT, DPO/ORPO/KTO, GRPO/RLVR, CPT) fits your data.

gdpr-data-handling
Implement GDPR-compliant data handling with consent management and data subject rights.