Pattern 01
No evaluation harness
What we see
A prompt in a template string, a call to the model, and the response rendered. Quality was assessed by the developer trying it. There is no set of representative inputs with expected properties, nothing that runs in CI, and consequently no way to change the prompt or the model with any confidence about what it did to the other cases.
Why an assistant produces it
"Build a feature that summarises the ticket" is answered completely by code that summarises the ticket. Evaluation is a testing practice, not a feature, and it is the part a human normally supplies.
The pattern
const summary = await claude.messages.create({
model: "claude-sonnet-5",
messages: [{ role: "user", content: `Summarise: ${ticket}` }],
});
// ship itThe fix
// evals/summarise.test.ts — runs in CI, fails the build on regression.
const CASES = loadGoldens("evals/summarise/*.json"); // 40+ real tickets
for (const c of CASES) {
test(c.name, async () => {
const out = await summarise(c.input);
// Assert properties, not exact strings — the output is non-deterministic.
expect(out).toContain(c.mustMention); // key fact retained
expect(out.length).toBeLessThan(c.maxChars);
expect(await noHallucinatedIds(out, c.input)).toBe(true);
});
}The correct implementation
Build a golden set of 30–50 real inputs with the properties a good answer must have, and assert those properties rather than exact strings. Run it in CI and gate merges on it. Where quality is genuinely subjective, an LLM-as-judge scorer with a fixed rubric is a reasonable proxy — but hold out a human-labelled subset to check the judge, because a drifting judge is worse than no judge.
How to test it
The test is whether the team can answer this: "we want to switch models to cut cost — how do we know quality held?" If the answer is "we'd try a few prompts", there is no harness. Building one is typically two days and it is the highest-leverage two days in the whole feature.