Manual spot-checking - trying a prompt on a few examples and eyeballing the output - doesn't scale and doesn't catch regressions. It confirms the cases you happened to try worked; it says nothing about the cases you didn't try. A minimal evaluation harness replaces that gut check with three fixed components: a representative golden dataset (real or realistic inputs, including known-hard edge cases), a consistent grading method (exact-match where the task allows it, a rubric-based grade where it doesn't), and a repeatable runner that executes the same dataset through the same grading logic every time, so a score from one run means the same thing as a score from another. In effect, it's a regression test suite for a probabilistic system.
Building the Golden Dataset
Each example needs an input and either an expected answer (for exact-match tasks) or a grading rubric (for open-ended ones). The dataset should be weighted toward the inputs that actually matter in production - including the ones known to be hard. When a real failure shows up in production, the fix isn't just patching the prompt; it's also adding that exact failure as a new permanent case in the dataset, so a future change can never silently reintroduce it. Treat the dataset like code: version it, review changes to it, and never edit it in the same commit as the thing it's testing.
Choosing a Grading Method
Prefer exact-match or programmatic checks whenever the task allows it - a classification label, a specific extracted JSON field, a regex the output must satisfy, a numeric value within tolerance. These are cheap, deterministic, and unambiguous; there's no reason to reach for anything fuzzier when the task has one correct answer. For open-ended output - summary quality, tone, whether instructions were actually followed - exact match doesn't apply, so grading falls to either human raters or a model-graded rubric (LLM-as-judge).
LLM-as-Judge: Power and Pitfalls
The common pattern: a separate Claude call receives the original input, the output being graded, and a fixed rubric, then returns a score (pass/fail, or 1–5) plus a short justification. This scales far better than human grading and catches nuance that exact-match can't express. But it carries a real risk: a model grading outputs from its own model family - especially its own kind of output - can rate them more favorably than an independent judge would, a self-preference bias. The mitigations are structural, not aspirational: hold the rubric prompt fixed across comparisons, prefer a different or stronger model as the judge than the one under test, and periodically sample a batch of automated grades for a human to re-check against.
Common exam distractor
An answer that uses a model-graded rubric with no fixed rubric prompt and no periodic human check is a trap - 'model-graded' is not automatically trustworthy. The exam expects you to know that unchecked self-grading (particularly a model grading its own family's output) can inflate scores, and that a fixed rubric plus human spot-checks is the mitigation, not a reason to avoid LLM-as-judge altogether.
Keeping the Comparison Valid: One Variable at a Time
For a before/after comparison to mean anything, the test set and grading criteria must stay fixed while exactly one thing changes - the prompt, the model, or a single parameter. Changing the dataset at the same time as the prompt makes it impossible to attribute a score difference to either one specifically. Store every run's results with its full metadata - model ID, prompt version or hash, dataset version, timestamp - so any two runs can be diffed unambiguously later, and a dataset update is itself logged as a distinct, deliberate event rather than folded silently into an unrelated change.
Key concept
An eval set is only useful if it can catch a regression, not just confirm a win - deliberately include cases you're not confident will pass. A dataset that always scores 100% has stopped telling you anything.
Sample Size and Run-to-Run Variance
Claude's output is not perfectly deterministic even with identical inputs and default settings - a score can shift by a few points between two runs of the exact same prompt against the exact same dataset. A jump from 80% to 88% on a ten-example set could be a real improvement, or it could be noise from one or two examples flipping. Larger datasets average out individual-example volatility; running each version multiple times and comparing distributions (not single scores) is more reliable still. A single run on a small sample is fine for quick iteration during development, but it is not sufficient evidence to declare a regression fixed or a change a genuine improvement.
Running the Harness Repeatably
The runner itself is simple: iterate the fixed dataset, call the model/prompt version under test for each input, apply the grading method, aggregate the results into an overall score (and, ideally, a per-example pass/fail so you can see exactly which cases broke), and write the run to a versioned log. Every subsequent run diffs against a stored baseline instead of relying on someone's memory of how the last version performed - that's what turns evaluation from a one-off exercise into an actual regression gate a team can trust before shipping a prompt or model change.