Course · Part 5, Evals · Lesson 5.1 · 10 min

Write the check that would have caught today's bug

A deterministic check is a plain function that fails on the exact bug you saw, runs on every prompt change, and never asks a model for its opinion.

Before you read: find the line

Read the run below once. Decide which line is the mistake. The lesson tells you.

Eval run for step 1 extraction, six cases, four checks per case
  1. 1systemeval: support-extraction-v3 cases: 6 checks: json_parses, order_id_ok, issue_not_empty, no_reply_text
  2. 2usercase 1 ticket "Order 51902 arrived in black, I ordered navy" -> {order_id: "51902", issue: "wrong colour"} PASS
  3. 3usercase 2 ticket "Where is my refund for 30441?" -> {order_id: "30441", issue: "refund status"} PASS
  4. 4llmcase 3 ticket "I was charged twice for order 88213, please fix this today" -> {order_id: "82813", issue: "double charge"} order_id_ok: matches /^\d{5}$/ PASS
  5. 5usercase 4 ticket "No order number, just want to cancel my subscription" -> {order_id: null, issue: "cancel subscription"} PASS
  6. 6toolcase 5, case 6 PASS
  7. 7done6 passed, 0 failed suite green prompt v3 approved

What a check is

A check is a function. It takes the input and the output of one step and returns pass or fail. No model inside it. For the double charge run the checks write themselves once you say what broke: the output must parse as JSON; the order_id must appear, as a string, in the ticket text; the issue field must not be empty; a ticket that contains "charged twice" cannot come back with urgency low; step one must not contain a customer reply.

Hamel Husain calls these level one evals, the unit tests of an AI product, and his advice is to write them from real failures, not from imagination. Anthropic's eval guide says the same from the other side: prefer many cases with cheap automated grading over a few cases graded by hand. Checks are the cheap grading. Braintrust's autoevals library ships the common ones as scorers that return 0 to 1: exact match, JSON validity, Levenshtein distance, numeric difference. Most of your checks will be ten lines you wrote yourself.

Where checks come from

Every check in a good suite has a date next to it. Tuesday's bug was a swapped digit, so Tuesday's check is "order_id is a substring of the ticket". The rule is simple: when a run fails in production, before you touch the prompt, write the check that fails on that trace. Then fix the prompt and watch the check go green. Then keep the check forever.

The reason to write the check first is not ceremony. It is that the check is the only proof that the fix fixed anything. A prompt edit that feels right and a check that goes from red to green are two different levels of evidence, and only the second one survives the next prompt edit.

The check that looks right and catches nothing

The eval run below is the trap. The suite has six cases. All six pass. The bug is at line 4: the check for the order number is "matches five digits". The model returned 82813. That is five digits. The check tested the shape of the answer, not its content, and the exact bug from Tuesday sails through a green suite.

This happens constantly, because shape checks are easy to write and feel thorough. Is it JSON, does it have the fields, are they the right type. All useful, none of them about the thing that broke. The test of a check is one question: if I replay today's broken trace, does it fail? If the answer is no, you have a schema validator, not an eval.

The fix is one line. Replace "matches five digits" with "ticket text contains order_id". Now 82813 fails against a ticket that says 88213, and so does any other digit swap the model will ever make.

What a senior engineer does

Keep the checks in the repository next to the prompt, as code, with the case files beside them. Run them on every change to the prompt, the model name or the tools, the way you run unit tests on every commit. Keep each check binary. A score of 0.7 on "order id correctness" means nothing; the number is either in the ticket or it is not. Save averaging for the summary line, never for the decision.

Add the production trace that caused the bug as a case, with the real ticket text and the expected fields. That is where the held out set in lesson 5.5 begins. And when a check cannot be written because the question is a judgment, such as "is this reply apologetic enough", stop. That is lesson 5.2, and it needs a different tool.

What to say in the interview

"The first thing I write after a production failure is the check that fails on that trace. For the double charge it was a substring check: order_id in ticket text. It runs on every prompt change. I know it works because it was red before the fix and green after." If they ask about the green suite that missed the bug, say the words "shape, not content" and they will nod.

The line

Eval run for step 1 extraction, six cases, four checks per case
  1. 1systemeval: support-extraction-v3 cases: 6 checks: json_parses, order_id_ok, issue_not_empty, no_reply_text
  2. 2usercase 1 ticket "Order 51902 arrived in black, I ordered navy" -> {order_id: "51902", issue: "wrong colour"} PASS
  3. 3usercase 2 ticket "Where is my refund for 30441?" -> {order_id: "30441", issue: "refund status"} PASS
  4. 4llmcase 3 ticket "I was charged twice for order 88213, please fix this today" -> {order_id: "82813", issue: "double charge"} order_id_ok: matches /^\d{5}$/ PASS
  5. 5usercase 4 ticket "No order number, just want to cancel my subscription" -> {order_id: null, issue: "cancel subscription"} PASS
  6. 6toolcase 5, case 6 PASS
  7. 7done6 passed, 0 failed suite green prompt v3 approved

Line 4. The order_id check tests the shape of the answer, five digits, not its content. 82813 is five digits, so Tuesday's exact bug passes a green suite. The check must be "ticket text contains order_id", which fails here and on every future digit swap.

The pause

A suite of six cases is all green and the swapped digit bug is in case 3. What is wrong with the check, and what is the one question that tells you whether a check is worth keeping?

Do: fix the run

The same run, live. Tap the line, write the fix, a real model runs it on inputs you have not seen.

In the interview

“A bug reached production last week. Walk me through what you added to your evals.”

Say your answer out loud, in under a minute. Then compare.

Notes