Key takeaways
- ✓A test gate grades every change on an exam (here 21 questions: 16 with an answer key, 5 where the right reply is "I don't know"), one check at a time, and compares each score with a saved baseline. Search gets no allowance; answer scores get 0.10; two critical questions must pass every check.
- ✓Day one, on unchanged code, it was blocked: 8 of 21 questions failed a check, and one was the critical refund question, whose paragraph ranked 4th and was never sent. Read every red before touching the gate: 4 right answers without a source tag, 2 "I don't know" with the answer in hand, 2 search misses.
- ✓Break it on purpose. Deleting the "say I don't know" rule took that score from 5 of 5 to 0 and blocked the change, while answers naming a source went up from 9 to 15 of 16. One score can improve while the assistant gets worse, so the gate watches all of them.
- ✓Rerun unchanged code. Over five runs search was identical, but answers naming a source wobbled from 9 to 11 of 16 (0.125), more than the 0.10 allowance: with the lucky run saved as the baseline, the same code is blocked. Measure the wobble first, then set the allowance, and save a typical run as the baseline.
- ✓Sending 4 paragraphs instead of 3 fixed the refund answer and steadied the tags at 14 of 16 in all five runs. The gate stayed red, fairly: the refund paragraph still ranks 4th, one slip from gone. The next fix is search.
Every change to an AI assistant can quietly break answers that used to be right: new wording in the prompt, a new model, more paragraphs sent. Nobody rereads every answer after every change, so you write an exam of test questions and have it run automatically, before a change goes live. A test gate grades the answers and blocks the change if they got worse. This chapter covers what a gate checks, why it was red on day one, how to see that it catches a real break, and how to tell a real failure from the model's random wobble.
The code is Lab 05's eval suite, run unchanged: its RAG pipeline (gpt-5.4-nano), its checks, its gpt-5.4-mini judge and its baseline gate (evals/gate.py), all wrapped in run_evals.py, which CI runs on every pull request. The help center and the 21 questions are made up for the video and written before any run; the lab's own docs and 18 questions work the same way. Every answer and judge verdict is a real recorded run: the first run, five reruns of unchanged code, two changes that break it on purpose, and five runs of the fix.
How the assistant answers
- Search: find the 3 paragraphs in the help center that best match the question.
- Answer: hand them to the model with instructions: answer only from them, name the article in square brackets, and if the answer isn't there, reply exactly "I don't know based on the provided documents."
- The reply: "How long does an invite link last?" gets invitations expire after seven days, tagged
[teams].
An exam, graded one check at a time
21 questions, written before anything ran. 16 have an answer in the help center, and for each the answer key names the paragraph that holds it. 5 have none, like "Do you offer a student discount?", where the right reply is "I don't know", not a made-up discount.
Each answerable question is checked on search and on the answer separately, so a failure points at the part that broke:
| Check | Graded by | Allowed drop |
|---|---|---|
| Search found the right paragraph (recall) | Code, against the answer key | None |
| Search ranked it first (hit@1), and how high (MRR) | Code | None |
| The answer names a real source (citations) | Code | 0.10 |
| The answer sticks to the paragraphs (faithfulness) | gpt-5.4-mini as a judge |
0.10 |
| "I don't know" on the 5 with no answer (refusals) | Code: the exact sentence | 0.10 |
"Why is my account locked for 15 minutes?": found ✓, first ✓, sticks to it ✓, names its source ✗. A score is the share of questions passing one check: on day one, 9 of 16 answers named their source.
The gate
The gate compares each new score with the baseline, the scores saved from an earlier accepted run. Think of a bathroom scale: you compare with last week, not with a perfect number. A fixed target ("faithfulness ≥ 0.9") either blocks every change or lets real drops through.
- Allowance (tolerance): a scale wobbles, so the answer scores may dip by up to 0.10. With 16 answers, one miss (0.0625) is fine; two (0.125) is not.
- Search gets none. It returns the same results every run, so any drop is real.
- Critical questions must pass every check, every run, however good the scores are. Here: the student discount and the refund.
Updating the baseline is a deliberate step a person reviews (--update-baseline, committed in the pull request). CI never does it on its own.
Pick a saved baseline and a new run. The gate blocks if any score drops below the baseline by more than its allowance, or if a critical question fails any check. Search gets no allowance.
Baseline (saved earlier)
New run
Allowance on answer scores
One question of 16 = 0.0625| Score | Baseline | New | Allowed drop | Gate |
|---|---|---|---|---|
| search: ranked it first | 12 of 16 | 12 of 16 | none | ok |
| search: found the right paragraph | 14 of 16 | 14 of 16 | none | ok |
| search: how high it ranked (0 to 1) | 0.81 | 0.81 | none | ok |
| answer names its source | 11 of 16 | 9 of 16 | 0.10 | ✕ dropped too far |
| answer sticks to the paragraphs (judge) | 16 of 16 | 16 of 16 | 0.10 | ok |
| says “I don’t know” when it should | 5 of 5 | 5 of 5 | 0.10 | ok |
Refund (“I was charged by mistake, can I get my money back?”) fails in every recorded run: its paragraph ranks 4th, so with 3 paragraphs it isn't sent, and with 4 it's sent but not first. Critical questions must pass every check.
Same code, five runs: did the answer name its source?
Answers naming their source: 10, 10, 9, 11, 10 of 16 across the five unchanged runs. A spread of 2 questions (0.125), more than the 0.10 allowance.
Read the red
On day one there was no earlier run, so the run was compared with itself and nothing could drop. It was blocked because a critical question failed. In all, 8 of 21 questions failed a check, in three kinds:
| Kind | Questions | What happened |
|---|---|---|
| Right answer, no source tag | 4 | Like the locked account |
| "I don't know" with the answer in hand | 2 | "Can I pay without a credit card?" had the payment paragraph, which lists PayPal, ranked first |
| Search missed it | 2 | One is critical: "I was charged by mistake, can I get my money back?" Its paragraph ranked 4th, so with 3 sent it never reached the model |
The gate was right to block. Read every failure before touching the gate, the prompt or the allowance.
Break it on purpose
A fire drill: does the gate catch a real break? Deleting the "say I don't know" instruction (one of the lab's Exercise 2 changes):
| Score | Day one | Rule deleted | Gate |
|---|---|---|---|
| "I don't know" when it should | 5 of 5 | 0 of 5 | Dropped too far |
| Sticks to the paragraphs (judge) | 16 of 16 | 12 of 16 | Dropped too far |
| Names its source | 9 of 16 | 15 of 16 | Improved |
Without the rule, most unanswerable questions got polite replies in the model's own words, and "Can I pay with Apple Pay?" got a flat "No", though the help center never mentions Apple Pay either way. The check counts only the exact sentence, so a program can tell "no answer" from an answer. The gate blocked it, and the student-discount question, which is critical, failed too.
Meanwhile the source-tag score went up: without that rule, the model tagged almost every answer. One score looked better while the assistant got worse at saying "I don't know". That's why the gate watches every score at once. (One of the judge's four new flags was itself wrong: it called PayPal unsupported, but the billing paragraph lists it. The video leaves that drop out for that reason.)
The lab's other break, sending 1 paragraph instead of 3, dropped search's "found it" from 14 to 12 of 16 and was blocked too (recorded, not shown in the video).
Measure the wobble
The opposite test: will it block a change that did nothing? The same code, five times, without the lab's saved responses (EVAL_NO_CACHE=1, the lab's Exercise 4):
| Score | Runs 1–5 |
|---|---|
| Search: found / first / MRR | Identical every run |
| Sticks to the paragraphs | 16 of 16, every run |
| "I don't know" when it should | 5 of 5, every run |
| Names its source | 10, 10, 9, 11, 10 of 16 |
The model doesn't write the same answer twice. "Why is my account locked?" got no tag in run 1, and in run 2 nearly the same answer, one sentence longer, with the tag. Three questions flipped like that, so the score wobbled by two questions (0.125), more than the 0.10 allowance. Save the lucky run (11) as the baseline, and the next run of the same code (9) is a drop of two: blocked, for nothing. The lab's own gate.compare marks it "regressed".
So first measure the wobble by rerunning code you haven't changed, then set the allowance from it, and save a typical run as the baseline, not the lucky one. (In normal CI runs the lab replays saved responses for unchanged inputs, so only the parts a change touched can wobble.)
The fix, and a red that stays
The refund paragraph ranked 4th, so the fix was to send 4 paragraphs. Every answer now sees an extra paragraph, so the whole exam was rerun, five times:
| Score | 3 paragraphs (5 runs) | 4 paragraphs (5 runs) |
|---|---|---|
| Names its source | 9–11 of 16 | 14 of 16, every run |
| Search found the right paragraph | 14 of 16 | 15 of 16 |
| Sticks to the paragraphs | 16 of 16 | 16 of 16, once 15 |
The three questions that flipped stopped flipping in these runs, though the judge wobbled once. The refund question now gets the right answer: a full refund within 14 days of your first purchase.
The gate still says blocked. The refund paragraph is found but ranked 4th, and a critical question must pass every check, including "ranked first". That red is fair: the answer only arrives because a 4th paragraph is sent, and one slip in search would lose it again. The next fix is search itself.
The checklist
- Read every red before touching the gate, the prompt or the allowance.
- Break it on purpose and check the gate blocks; watch every score, since one can rise while the assistant gets worse.
- Rerun unchanged code and check the gate passes.
- Set each allowance from the wobble you measured, and none for scores that don't wobble, like search.
- Save a typical run as the baseline, update it only on purpose, and keep critical questions for what must never break.
Practice: the gate
Write the gate's comparison, its critical-question rule and its decision, then measure the wobble. The tests use the scores and checks recorded in the video: day one blocked by refund alone, the fire drill's "improved" source tags, the lucky baseline that blocks unchanged code at 0.10 but not at 0.15, and the 4-paragraph fix that improves two scores and is still blocked.
Quiz
1.What does the gate compare each new score with?
2.Why does search get no allowance, while answer scores get 0.10?
3.Day one ran on unchanged code, compared with itself, so no score could drop. Why was it blocked?
4.With the "say I don't know" rule deleted, answers naming a source rose from 9 to 15 of 16. Did the assistant get better?
5.Five runs of unchanged code tagged 10, 10, 9, 11 and 10 of 16 answers. The allowance is 0.10 (one question is 0.0625). What can happen?
6.After sending 4 paragraphs, the refund question gets the right answer, but the gate still says blocked. Is that a false alarm?
Check your understanding
Try each question before opening its answer.
What does a test gate do?
On every change it runs an exam of test questions, scores the answers one check at a time, and compares each score with a saved baseline. It blocks the change if a score drops past its allowance or a critical question fails any check.
Why compare with a baseline instead of a fixed target?
A fixed target either blocks every change (if you're below it today) or lets real drops through (if you're far above it). The question a change check asks is "did this make things worse than before?"
Why was the gate red on day one?
Nothing could drop, since the run was its own baseline. But the critical refund question failed: its paragraph ranked 4th, was never sent, and the assistant said "I don't know". The gate was right.
How can a score improve while the assistant gets worse?
With the "say I don't know" rule deleted, refusals fell from 5 of 5 to 0, and source tags rose from 9 to 15 of 16 because the model tagged almost everything. The gate watches all the scores, so it still blocked.
How do you set the allowance?
Rerun unchanged code several times and measure how far each score moves. Here source tags moved between 9 and 11 of 16, a wobble of 0.125, bigger than the 0.10 allowance, so a lucky baseline would block unchanged code. Set the allowance from the wobble, give none to scores that don't wobble, and save a typical run as the baseline.
Why is a red that stays after a fix sometimes right?
Sending 4 paragraphs made the refund answer right, but its paragraph still ranks 4th, so the critical "ranked first" check fails. The answer depends on a 4th paragraph being sent; one slip in search and it's gone. The real fix is in search.