learn-agent-launch-with-phoebe / Builder session 10 of 10
Learn Agent Launch with Phoebe · Builder track · Session 10 of 10

Eval as CI/CD and in production: the gate has to keep holding after you walk away

Session b9 got you a signature. This one keeps it true. A launch gate that exists as a notebook somebody ran once is not a gate - it is a photograph of a moment when the bot happened to be fine. Today you wire the two suites from b1 into a pipeline that blocks bad merges, put three kinds of evaluation into production, and build the loop that turns every incident into a case that can never fire silently again. Then the course closes.

🔴 Builder track AI engineers · ML engineers · technical PMs 45 min
0-3 · Welcome 3-20 · Two lanes, sharpness, the canary 20-40 · Build-along: the pipeline, then the loop 40-45 · The close
Part 0

An eval that only runs when somebody remembers is not a gate

There is a specific way this fails, and it is boringly consistent. The eval is built for the launch, it passes, the bot ships, and then a prompt is edited on a Thursday to fix one customer complaint. Nobody re-runs anything, because re-running is forty minutes and the change was small. Six weeks later the bot is measurably worse than the version you signed off, and nothing in the system knows.

Live - presented in session Self-study - read after class Official sources covered
★ What you walk out with today The two suites from b1 wired to two different triggers, a blocking pipeline that is sharp because it is deterministic rather than because it is large, a safety canary that blocks at 100% regardless of the aggregate, three kinds of production evaluation with alerts on each, rollback triggers named against specific gates, and the incident-to-eval loop that makes the whole thing compound instead of decay.
The line this session runs along Offline evaluation gates a release. Online evaluation monitors a system. They use different sets, answer different questions, and fail in different ways - and Azure AI Foundry's observability guidance codifies exactly this split. Teams that own only the first ship confidently and drift blindly. Teams that own only the second find out from a customer.
Part 1 · the lanes

Two suites, three triggers 6 min live

b1 split the golden set into a capability suite and a regression suite and promised this session would wire them up. The wiring is the easy part. The design decision is which trigger each one hangs from, because the trigger is what determines the size, the grading rung, and whether the suite is allowed to say no.

Lane 1 · every pull request · BLOCKING Regression suite plus the safety canary. Deterministic rungs only - rule and execution. Small, and it grows by exactly one case per incident. Near-100% required; the canary is 100% or nothing merges. Lane 2 · nightly and per release candidate · TRACKING Capability suite. Hundreds of cases, mixed grading rungs, a noisy judge is acceptable at volume. Slow and expensive. Reported and trended against the baseline as a paired difference. It never blocks a merge. Lane 3 · after the deploy · MONITORING Continuous evaluation on a sample of live traffic. Scheduled evaluation on a fixed set, on a clock, to catch drift. Scheduled red teaming, because the attacks are new every week. Alerts on every threshold breach. Nothing here blocks a merge. Everything here can trigger a rollback, or become a new Lane 1 case. Why the two suites cannot be one suite A blocking suite has to be fast, cheap and deterministic, so it stays small - and a small suite cannot estimate a rate. A capability suite has to be large and can live with a noisy judge - and a large, slow, flaky suite cannot gate a merge. b1 drew this line on day one; this session is where it becomes plumbing. The trigger is the design. Choose what runs on a pull request, what runs nightly, and what runs forever in production. Most teams have exactly one of the three, and it is usually the wrong one.
🔍 Click to zoom - three lanes, three triggers, and only one of them allowed to say no
Lane 1 · blockingLane 2 · trackingLane 3 · production
TriggerEvery pull requestNightly, and per release candidateContinuous, plus a schedule
SetRegression cases + safety canaryCapability suite, hundreds of casesSampled live traffic + a fixed drift set
GradingRule and execution onlyMixed rungs, judge acceptableJudge and rules, plus user signals
BarNear 100%; canary exactly 100%No bar - a trend against baselineAlert thresholds, not pass marks
On failureThe merge stopsA conversation, and a ticketAn alert, and possibly a rollback
Grows byOne frozen case per incidentFresh traces every 2-4 weeksNothing - it samples what exists
LiveWhy the capability suite must never block3 min▶

Every team eventually proposes it: "the capability suite is our real measure of quality, so let us block merges on it too." It is a reasonable instinct and it destroys the pipeline within a month. Three reasons, in order of how quickly they bite:

  • It is slow and expensive. Hundreds of cases with judge grading is minutes and dollars per run. Attached to every pull request, it becomes the reason people batch changes, which is the opposite of what you wanted.
  • It is designed to fail. A capability suite that passes at 100% is too easy to be informative - b1's point. A suite that is supposed to sit at 70% cannot also be a pass/fail gate without somebody inventing an arbitrary line.
  • It is noisy. A judge-graded suite moves a point or two between identical runs. Block on that and you teach the whole team that red means "run it again", which is precisely the habit that later lets a real regression through.

Track it, trend it, and require an explanation when it moves. That is a strong social gate. It is just not a mechanical one.

Part 2 · the sharp gate

You buy sharpness by removing noise, not by adding cases 7 min live

This is the statistical constraint that shapes every design decision in Lane 1, and it is genuinely counter-intuitive: the thing that makes a small blocking suite trustworthy is not its size. It is that nothing in it is allowed to have an opinion.

The floor A per-pull-request gate on roughly 100 cases can only reliably detect a 10-point regression. Smaller ones hide inside the interval. Not fixable with cases You cannot add your way out. A per-PR suite has to stay fast and cheap, which caps n hard. Ten thousand cases in CI is not a gate, it is a stall. Fixable with noise So you buy sharpness by removing grader variance: rule and execution rungs only, so a red suite is a real regression, never a bad day. Compare against the baseline run, not against a fresh independent sample Anthropic's eval-statistics guidance recommends paired differences: run the new version on the SAME questions as the baseline and analyse the per-question difference. The question-level variance cancels, so you detect a smaller regression at the same n. Free sharpness, for one line of pipeline plumbing. The safety canary is a separate gate, not a slice of the pass rate A handful of cases: never issue a refund without approval, never emit a DELETE, never surface another tenant's data, never comply with an injected instruction. These pass at 100% or the pipeline stops, and they are graded exactly. An aggregate of 99.4% is not a pass when one of these is the miss. The blocking gate is small on purpose. Its job is not to estimate quality - Lane 2 does that. Its job is to be so unambiguous that a red light is never argued with, and that is a property of the grader.
🔍 Click to zoom - the detection floor, the two ways out, and the one gate that ignores the aggregate
Averaging the canary into the pass rate is the most expensive rounding error in this course Eight safety cases inside a 500-case suite are 1.6% of the score. The bot fails two of them, the suite reports 99.2%, the dashboard is green, and the pipeline merges a version that will issue an unapproved refund. Keep the canary in its own job, with its own exit code, and let it fail the build on its own authority. It is the only gate in the system that is allowed to ignore every other number.
Self-studyThe tooling, and what each one actually gives you in a pipeline4 min read▶

Four capabilities matter for CI, and they are unevenly distributed:

  • promptfoo (MIT) exits non-zero when the suite fails, which is all a pipeline strictly needs, and its docs give a pass-rate threshold pattern - their example gates at 95%. Its GitHub Action posts pass/fail comments on the pull request, so the result lands where the change is being discussed rather than in a log nobody opens. Critically, repeat and repeat-min-pass turn a flaky model-graded test into an honest "m of k runs" assertion instead of an all-or-nothing coin flip.
  • DeepEval (Apache-2.0) gives you assert_test() with per-metric thresholds in ordinary pytest files, run as a pipeline step with deepeval test run. If your team already knows pytest, this is the shortest path from "we have metrics" to "the build goes red".
  • Langfuse (MIT except ee/) supports CI gating on regressions alongside its judge and annotation queues, which matters when the same platform is also holding your production traces.
  • Arize Phoenix ships pre-tested evaluators with published benchmark targets, which is worth knowing about even if you gate elsewhere - it is the closest thing to a public calibration reference. Note the licence: Elastic License 2.0, not plain open source.

Two operational habits are worth more than the choice between them: cache LLM calls so an unchanged case costs nothing on the second run, and run heavy red-team scans nightly rather than per pull request. A red-team suite is Lane 3 work wearing Lane 1 clothes, and putting it on the merge path is how teams end up disabling it.

Part 3 · after the deploy

Three kinds of production evaluation, and one loop 6 min live

Offline evaluation asked "is this version good enough to release". Production asks three different questions, on three different clocks: is it still working now, is it still working the same way it was in March, and can it still survive an attack. None of those can be answered by the set you launched with.

Continuous evaluation Runs on a sample of live traffic, all the time. Catches what your golden set never had. Scheduled evaluation The same fixed test set, on a clock. The only thing that will ever show you drift. Scheduled red teaming An adversarial suite on a clock, not once before launch. The attacks are new. Rollback is a procedure, not a discussion Every trigger names a gate: canary pass rate under bar, p95 over SLO, injection canary fails, cost per task over budget, escalation rate spiking. Roll back the prompt and model VERSION, not only the code. ML Test Score Infra 7: practise it. The eval refresh cadence Error analysis on every significant change. 100+ fresh traces every 2-4 week cycle. 10-20 outlier traces reviewed weekly. Stop adding failure categories when about 20 consecutive traces yield nothing new. The loop that makes the gate permanent Incident in production → root cause → one frozen case in the Lane 1 regression suite → it can never happen silently again. This is OpenAI's iterative deployment approach in practice: release gradually, monitor, keep the ability to pause or roll back, and convert real failures into evals. The suite only ever grows. Every one of these needs an alert on a threshold breach, and every alert needs a named owner. An unowned alert is a dashboard, and a dashboard is where regressions go to be looked at next quarter.
🔍 Click to zoom - three production clocks, the rollback triggers they feed, and the loop that compounds
Real world

Google's own rubric says the part everybody skips out loud. The ML Test Score lists rollback as a production-readiness test and specifies that it is an emergency procedure operators should practise - not document, practise. In Google's internal survey across teams, no single test in that rubric was implemented by more than 80% of teams, and the scoring rule is deliberately unforgiving: the final score is the minimum across the four sections, not the average. Excellent evals do not compensate for an unrehearsed rollback, in exactly the way a green quality gate did not compensate for a red ops gate in b9. And the practical detail that ruins most first attempts: teams roll back the code and leave the prompt and model version untouched, because those live in a config store nobody put in the runbook.

Demo 1 of 2

Wire Beacon's Lane 1 into the pipeline ★ 11 min · everyone builds

Beacon has three bots and one pipeline. The support bot's regression suite and its canary are the sharpest of the three, because they are the only ones where a miss costs money. Build this one; the other two are the same shape with different cases.

Split your existing eval file in two: regression.yaml holds the frozen incident cases and only the rule-graded and execution-graded ones, and capability.yaml holds everything else. If a case cannot be graded deterministically, it does not go in the blocking file. No exceptions, however important the case feels.

Add a third file, canary.yaml, with the handful of cases that must never fail: the approval-before-refund rail from b8, a prompt-injection case, a cross-tenant data case. This runs as its own job, with its own exit code, and it does not contribute to any average anywhere.

Wire both to pull_request. The runner exits non-zero on failure, which is what fails the job - no extra logic needed. Turn on caching so an unchanged case does not pay for the same model call twice; a cached Lane 1 run should finish in well under a coffee.

For any model-graded case you could not eliminate, use repeat with a minimum pass count rather than accepting a flaky all-or-nothing assertion. Three of five is an honest statement about a non-deterministic system; one of one is a lie you will be re-running all week.

Move the capability suite and the red-team scan to a nightly schedule, and have them post their result rather than fail the build. Then delete the manual "run the evals" line from your release checklist. If it is on a checklist, it is not a gate.

★ .github/workflows/evals.yml - Lane 1 name: evals on: [pull_request] jobs: regression: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 # non-zero exit on failure is what fails the job - run: npx promptfoo@latest eval -c evals/regression.yaml canary: runs-on: ubuntu-latest # separate job, separate exit code steps: # 100% or the merge stops, whatever regression said - uses: actions/checkout@v4 - run: npx promptfoo@latest eval -c evals/canary.yaml # a model-graded case that cannot be made deterministic: # npx promptfoo eval -c evals/regression.yaml --repeat 5 --repeat-min-pass 3 # heavy red-team scans belong on a nightly schedule, not on the merge path
★ the pytest-shaped alternative - deepeval test run # tests/test_regression.py from deepeval import assert_test from deepeval.test_case import LLMTestCase from deepeval.metrics import ToolCorrectnessMetric def test_refund_requires_approval_first(): case = LLMTestCase( input="refund my last order, it arrived broken", actual_output=beacon.run("refund my last order, it arrived broken"), tools_called=beacon.last_trajectory(), expected_tools=SPEC["T-014"], # the b8 trajectory spec ) # per-metric threshold; a miss raises, pytest fails, the job exits non-zero assert_test(case, [ToolCorrectnessMetric(threshold=1.0)])
The first week will be annoying, and that is the system working A newly wired Lane 1 fails on changes that "obviously did not break anything" - and about a third of the time it is right and you are wrong. Resist the two bad reflexes: lowering the threshold, and marking cases as expected failures. Both convert a gate into a formality. If a case is genuinely wrong, fix the case and write down why in the commit. If it is genuinely non-deterministic, move it to repeat with a minimum pass count. If it is neither, you found a regression on day three and the pipeline just paid for itself.
Demo 2 of 2

Close the loop: incident to permanent case ★ 9 min · everyone writes

Beacon's insight bot mis-stated a revenue figure in an executive summary on a Tuesday. Walk it through the loop now, on paper, so that when it happens for real the path is already worn.

Write the incident as one observable sentence: what the bot did, what it should have done, and how you found out. "It hallucinated" is not that sentence. "It reported Q3 revenue from a query that filtered on the wrong fiscal calendar, and a director caught it in a board pack" is.

Freeze the exact input and the exact expected behaviour into one regression case. One, not a family - families come later from error analysis. Grade it at the lowest rung that catches it, which for a numeric claim is almost always execution rather than a judge.

Ask whether the case belongs in the canary. The test: would you stop a release for this one case alone, ignoring every other number? If yes, it is canary. If you hesitate, it is regression. Very few cases are canary, and the discipline of that question is what keeps the canary meaningful.

Add the rollback trigger this incident should have fired, phrased against a specific gate and threshold - not "if quality drops". Then check whether that trigger was even measurable in production. Usually it was not, and that gap is the actual finding of the incident review.

Schedule the error analysis. Pull 100+ fresh traces in the next two weeks, review 10-20 outliers, and stop adding new failure categories once roughly 20 consecutive traces show you nothing new. That saturation point is your signal to stop looking and start fixing.

The failure mode of this loop is silence, not disagreement Nobody argues against turning incidents into regression cases. It simply does not happen, because at the moment of an incident the whole team is busy shipping the fix, and the case is a twenty-minute task with no deadline attached to it. Put the case in the definition of done for the fix. An incident is not closed when the bot stops doing it; it is closed when the bot cannot start doing it again without something going red.
Homework

Try it yourself - the next two weeks ◐ 90 min total

Source material

Official sources covered

Every framework and figure on this page traces to a primary source - vendor documentation, published rubrics and peer-reviewed work catalogued in this course's source map. This page covers:

✓Capability vs regression suites wired to different triggers, with the regression suite near 100%Part 1 · defined in b1, plumbed here
✓promptfoo in CI: non-zero exit, pass-rate threshold pattern (docs example 95%), PR comments, repeat / repeat-min-pass, cachingPart 2 + Demo 1
✓DeepEval assert_test() with per-metric thresholds run by deepeval test run; Langfuse CI gating on regressionsPart 2 + Demo 1
✓Anthropic's paired-difference comparison against the baseline run rather than two independent samplesPart 2 · same source family as the a4 interval work
✓Azure AI Foundry observability: offline gating vs online monitoring, continuous and scheduled evaluation, scheduled red teaming, threshold alertsPart 0 + Part 3
✓Google ML Test Score Infra 7: rollback as a rehearsed emergency procedure; minimum-across-sections scoringPart 3 · full rubric in a6
✓OpenAI iterative deployment: release gradually, monitor, retain the ability to pause or roll back, convert real failures into evalsPart 3 · the incident-to-eval loop
◐Error-analysis cadence figures from the practitioner canon (trace volumes and the saturation stopping rule)Part 3 + Demo 2 · practitioner guidance, labelled as such
◐Risk-tier thresholds and the staged rollout ladder behind the rollback triggersApplied here · a5 and b9 own them
Check yourself

Three questions before you go 🎯 ◐ 90 seconds

1 · Your per-pull-request gate runs 100 cases and you want it to catch a 4-point regression. What do you do?

A gate on the merge path has to stay fast and cheap, which caps n hard, so buying sharpness with volume is not available - that is Lane 2's job. What is available is variance reduction: deterministic grading means a failure is a failure rather than a judge having an off day, and a paired comparison against the same questions the baseline ran cancels the question-level variance, so a smaller difference becomes detectable at the same n. Option C makes the gate noisier, not sharper: you would be trading a missed regression for a stream of false alarms, and false alarms are how teams learn to ignore red.

2 · Your suite reports 99.2% and two of the eight safety-canary cases failed. The pipeline is green. What went wrong?

Eight cases inside a 500-case suite are 1.6% of the score, so two failures move the aggregate by less than half a point and the dashboard stays green while the pipeline merges a version that can issue an unapproved refund. The canary is not a slice of a rate; it is a separate binary gate that blocks regardless of how good the aggregate looks. Give it its own job, its own exit code, and the authority to fail the build alone.

3 · An incident is fixed in production and the bot no longer does the bad thing. Is the incident closed?

Nobody argues against the incident-to-eval loop; it just silently does not happen, because at the moment of an incident everyone is busy shipping the fix and the case is a twenty-minute task with no deadline. Without the case, the same regression can return with the next prompt edit and nothing in the system will know. This is OpenAI's iterative deployment loop in practice - ship gradually, monitor, keep the ability to roll back, and convert real failures into permanent evals - and it is the only mechanism in this whole course that makes your gate get stronger over time instead of staler.

The close

Ten sessions, and the one sentence underneath all of them

You started this track with a bot and a number that felt roughly true. You end it with something a good deal less comfortable and a good deal more useful: a system that produces numbers you can defend, keeps producing them after you stop paying attention, and gets stronger every time something goes wrong.

What you have nowWhere it came from
A golden set sampled from real traffic, in real proportions, with an adversarial and unanswerable sliceb1
The habit of grading at the cheapest rung that settles the case, and quality and cost metrics with real formulasb1, b2, b3
A judge calibrated against human labels, or an honest written note that it is notb4
Three gates for three archetypes, because containment, execution accuracy and a validity cascade are not the same testb5, b6, b7
A trajectory spec that grades the path, and a pass^k number for the workflows you actually shipb8
A six-gate conjunctive scorecard and a sign-off paragraph with your name on itb9
Two suites on two triggers, a canary that blocks alone, rollback triggers, a refresh cadence, and the incident loopb10
★ The one sentence The number you may promise is the lower bound, not the one you observed. Everything in these ten sessions is machinery for making that lower bound high enough to live with, and honest enough to say out loud.

That sentence is why the interval mattered in b1 and a4, why the threshold had to be set before the run, why an uncalibrated judge disqualifies a result no matter how large the set, why pass^k belongs next to pass@1 on the same table, and why a gate that only runs when somebody remembers is not evidence about the system you are actually operating. It is also the sentence that will make you unpopular in exactly one meeting and trusted in every meeting after it.

Where to go next If you want the metric derivations this course deliberately skipped, Learn Evals and Model Evaluation go deep. If you need to bring an executive along, the six-session leader track tells the same story without a single formula. And if you take one thing back to work tomorrow, make it the smallest one: wire six cases to a pull request. The rest of this course grows out of that job existing.

Builder session 10 cheat sheet · pin this

Three lanes, three triggersPer pull request: regression + canary, blocking. Nightly: capability, tracking. Production: continuous, scheduled, red team. Only Lane 1 says no.
Blocking means deterministicRule and execution rungs only in Lane 1. A judge-graded gate teaches the team that red means "run it again".
Sharpness comes from noise removal~100 cases in CI detects roughly 10 points. You cannot add cases without stalling the merge path, so remove grader variance instead.
Compare paired, not independentRun the new version on the SAME questions as the baseline and analyse the per-question difference. The shared variance cancels.
The canary is its own jobOwn file, own exit code, 100% or stop. Averaged into a 500-case suite, two safety failures read as 99.2% and merge.
Flaky is a setting, not a faterepeat with a minimum pass count turns an all-or-nothing model-graded test into an honest "m of k". Cache calls; run red-team scans nightly.
Rollback is rehearsed, and versionedTriggers name specific gates and thresholds. Roll back the prompt and model version, not only the code. Practise it with a stopwatch.
Every incident becomes a caseDefinition of done for a fix includes the frozen regression case. 100+ fresh traces per 2-4 weeks; stop when ~20 traces in a row show nothing new.