Here is how most AI features get "improved." Someone notices the summarizer occasionally rambles. They open the prompt, add "be concise," run it against the two examples they have open, and the outputs look tighter. Ship it.
Three days later, support tickets: the summarizer has started dropping order numbers from confirmation recaps. "Be concise" fixed the case they were looking at and silently broke four they weren't. Nobody connected the tickets to the prompt change, because nothing in the deploy looked like a deploy.
This is the defining failure mode of building on LLMs: every change is global, and your feedback is local. A prompt edit, a model upgrade, a temperature tweak — each one re-rolls behavior across every input your feature will ever see, and you evaluated it on the two you had open.
Traditional software solved this decades ago. The solution is a test suite. Yours is called a golden set.
The pattern
A golden set is 25–50 real input cases with per-case assertions, run automatically against every prompt change, model swap, or parameter tweak — exactly like unit tests, except the function under test is stochastic, so you grade rather than assert-equal.
- Collect real cases from production. Every input that ever produced a bad output goes in the set — that's your regression corpus. Add representative wins so you notice when a "fix" breaks what already worked. Synthetic-only sets test the inputs you imagined, not the ones you get.
- Attach assertions to each case. What must be present (the order number), what must never appear (another customer's data, an invented discount), what shape the output must have (valid JSON, under 120 words).
- Gate ships on the pass rate. The new prompt ships when its rate meets or beats the current baseline. "It reads better to me" plus a lower pass rate ships nowhere until the failures are triaged.
The implementation
The whole harness is a loop, a validator, and a cheap judge:
const GOLDEN = require('./golden-set.json');
// [{ id, input, checks: { schema, must[], never[], rubric } }]
async function runGoldenSet(promptVersion) {
const graded = [];
for (const c of GOLDEN) {
const out = await feature.run(c.input, { prompt: promptVersion });
graded.push({
id: c.id,
schema: validate(out, c.checks.schema), // tier 1: code
musts: c.checks.must.every(s => out.includes(s)),
nevers: c.checks.never.every(s => !out.includes(s)),
rubric: await judge(out, c.checks.rubric), // tier 2: cheap model
});
}
const pass = graded.filter(g => g.schema && g.musts && g.nevers && g.rubric);
return {
rate: pass.length / GOLDEN.length,
failures: graded.filter(g => !pass.includes(g)),
};
}
// Ship rule: newRate >= baselineRate. No exceptions without triage.
Run it in CI if your prompts live in git (they should). Total cost per run: 25–50 calls to your feature plus 25–50 calls to a small judge model — pennies, next to one silent regression reaching customers.
Three tiers of grading
Grade with the cheapest tier that can catch the failure:
- Tier 1 — code checks. JSON validity, required fields, must-include strings, must-never strings, length bounds. Deterministic, free, catches most regressions. Put every check you possibly can in this tier.
- Tier 2 — LLM-as-judge, small model. For the fuzzy assertions: "does the reply actually answer the question," "is the tone appropriate for a complaint." One yes/no rubric question per case, answered by a cheap fast model — the same economics as the cheap-planner pattern from #147: don't pay frontier rates to grade multiple-choice.
- Tier 3 — human spot-check. Only for cases where tiers 1–2 disagree with your gut, and for auditing the judge itself once a month. If you're hand-reviewing every run, your tier 1 and 2 checks are underbuilt.
When to run it
- Every prompt change — this is the whole point. No prompt edit merges without a run.
- Every model change. Model upgrades are the biggest uncontrolled variable in AI software: the vendor reroll you didn't ask for. When the new version drops, the golden set tells you in five minutes whether your feature survived it — before your customers do.
- Weekly, unchanged. Same prompt, same pinned model, scheduled run. This catches upstream drift and quiet vendor-side changes. A pass rate that moves when you changed nothing is a finding.
The audit signal
Store every run: { prompt_version, model, case_id, pass, timestamp }. Two queries earn their keep:
- A case that flips on a model upgrade = behavior your prompt was silently relying on. Pin the old model until the prompt is fixed, and keep the case forever — it just proved it works.
- A case that has never failed in six months = a candidate to retire. A golden set that only grows becomes a slow, expensive suite nobody runs. Keep it at 25–50 by retiring the cases that no longer discriminate.
Every AI feature we ship at AutomateScale carries its golden set from day one: real cases from the client's own leads and conversations, tiered grading, pass-rate gate in the deploy path. The cost is an afternoon. The cost of not having one is finding out about regressions from your customers, one ticket at a time. Want us to build the eval harness around your AI features? Apply for the audit.
The one-line summary
You wouldn't merge a code change with zero tests; a prompt is a code change. Collect 25 real cases, assert what must and must never appear, grade cheap-first, and gate every prompt and model change on the pass rate. Pairs with the router from #147 and the retry-safety layer from #149.