← the whole book on one page

Chapter 8: Operating AI Workloads — Models, Evals, and Agents

A fluent answer is not yet a correct one, and production is where we learn the difference.

Between 1964 and 1966, at MIT, Joseph Weizenbaum wrote a program he named ELIZA, after Shaw’s flower girl, who is taught to speak like a duchess. The name was a small joke with a serious point inside it, because the program, like the flower girl, had learned to sound like something it was not. Its best-known script, DOCTOR, imitated a Rogerian psychotherapist, the kind of therapist who answers a statement by turning it back into a question. Tell such a therapist that you are unhappy at work, and you are likely to be asked why you feel unhappy at work. The choice of role was shrewd, since a therapist who mostly reflects can say very little of substance and still seem attentive. There was no understanding anywhere in the program. It scanned what the patient typed for keywords and applied rules that rearranged the patient’s own words into a reply, and Weizenbaum explained exactly this, plainly, in 1966.

People mistook it for more anyway. In Computer Power and Human Reason (1976) he recalled that his secretary, who had watched him build the program over many months and so knew better than almost anyone what was inside it, began a conversation with it and after a few exchanges asked him to leave the room. “What I had not realized,” he wrote, “is that extremely short exposures to a relatively simple computer program could induce powerful delusional thinking in quite normal people.” The lesson concerned us more than the program. In ordinary life, fluency and understanding travel together: a person who speaks easily and sensibly about a subject usually knows something about it, and so we have learned to treat the first as evidence of the second. ELIZA pulled the two apart, and the habit went on working on the wrong evidence. We lend trust to fluency, and we lend it quickly, before anything has earned it.

Today’s models are vastly more capable than ELIZA and are often right, and that sharpens Weizenbaum’s warning instead of retiring it. ELIZA was wrong so often that a little skepticism exposed it. A component that is usually right teaches the people around it to stop checking, and when it is wrong, the wrong answer arrives in the same confident, well-formed voice as all the right ones. This is Bainbridge’s irony of automation in a new costume: the better the machine performs, the less practiced its human overseers become at catching the moment it does not.

Encore now depends on such components. The Architecture book designed three of them. The first is a support assistant built on retrieval-augmented generation (RAG), which means that before the model answers a fan’s question, the system retrieves relevant passages from Encore’s own material and asks the model to answer from them, so that its replies rest on Encore’s policies instead of on whatever the model happens to remember. The second is a refund agent that settles refund and rebooking requests by calling Encore’s own services. The third is the AI gateway, through which every model call passes, so that routing, quotas and costs are managed in one place. (The map of Encore in Chapter 1 shows where each piece sits.) Designing these features was the Architecture book’s work; this chapter is about running them, day after day, under real traffic and real adversaries. We will follow them through two episodes. In the first, the model behind the assistant changed although nobody at Encore had asked for a change, and the assistant began explaining the refund rule subtly wrong while every dashboard stayed green. In the second, somebody tried to talk the refund agent into paying them by hiding instructions inside a support ticket. Both episodes make sense only once we see clearly what makes a model different from every other dependency this book has taught us to operate, and so that is where we begin.

8.1 What Is Different About AI in Production

A dependency with opinions

Almost everything in this book so far has rested on one quiet assumption: a component, given the same input, does the same thing. We have leaned on that assumption so often that it has stopped looking like an assumption at all. Monitoring works by comparing today with yesterday, and a change in the numbers means something changed in the system. A canary works by comparing the new version with the old one on the same traffic, and a difference between them is blamed on the release. A test works by comparing an output with an expectation written down in advance, and a mismatch means a bug. Each of these instruments is a way of detecting difference, and each one is only trustworthy when the thing being measured would otherwise hold still.

A large language model (LLM) reached through an API looks, from the outside, like any other managed service of the kind Section 3.3 taught us to operate: someone else runs the hardware, we call an endpoint, we pay for what we use, and we watch its errors and latency from our side of the wire. The shape is familiar. What it no longer does is honor the assumption. Figure 8.1 lays the two kinds of dependency side by side, property by property.

Property A conventional managed dependency A hosted model
Determinism same input, same output same input, a distribution of outputs; even at temperature zero, not reliably repeatable
Failure signal errors and timeouts mostly HTTP 200: the failure is in the content
Change versioned releases, announced new snapshots, easily missed alias moves, scheduled retirements
Cost per request or per hour per token, so one call can cost 100× another
Latency a distribution in milliseconds time to first token plus time per output token, in seconds
Capacity requests per second requests and tokens per minute per model family, plus limits on sharp ramps

Figure 8.1 — The same dependency, seen twice. Each row on the right breaks an assumption our operations quietly rely on.

It helps to have an image for the difference, and Encore’s own business offers one. A conventional dependency is like a recording: press play twice and we hear the same performance twice, note for note, which is exactly why a sound engineer can compare two pressings and hear a defect. A hosted model is more like a live band. Ask for the same song and we get the same song, recognizably, but the phrasing, the tempo and the ending differ from night to night, and no two performances can be laid on top of each other and compared sample by sample. The analogy is useful up to a point, and the point where it breaks is the important part. When a live band plays a wrong note, the audience hears it. When a model gives a wrong answer, it delivers it with the same polish, the same confident tone and the same tidy formatting as a right one. The wrongness makes no sound.

Take the rows one at a time, beginning with determinism. Temperature is the setting that controls how much randomness a model uses when it chooses each next token; at temperature zero it should, in principle, always choose the single most likely one, and so give the same answer every time. In practice even temperature zero is not reliably repeatable, and the reason has nothing to do with the model’s intentions. A hosted provider serves many customers at once and groups their requests into shared batches on the same hardware. The other customers’ requests in our batch change the order in which the provider’s arithmetic is carried out, and floating-point arithmetic done in a different order can come out very slightly different. Most of the time the difference vanishes. Occasionally it is enough to tip which token is most likely, and once one token differs, every token after it is generated from a different starting point. Our output depends, in a small way, on strangers’ traffic.

The second row changes the daily work of operations more than any other. Machine learning systems fail silently, as Chip Huyen put it in Designing Machine Learning Systems: they return confident, well-formed and wrong answers through an endpoint that is, by every measure we usually take, perfectly healthy. The HTTP status is 200. The latency is good. The response parses. Consider what this means at Encore. If the support assistant starts telling fans the wrong refund rule, every dashboard we built in the earlier chapters stays green: the assistant has 100% availability and excellent latency throughout. The failure lives entirely in the meaning of the words, and only an instrument that actually reads what the assistant said will notice it. That is a new kind of instrument for most operations teams, and building it is the work of Section 8.3.

The third row deserves the same attention, because it removes something we had come to take for granted: control over when our dependencies change. A database we run changes when we change it, through a migration we wrote, reviewed and scheduled. A hosted model changes when its provider retrains it, re-tunes it or retires it, and the provider’s calendar is not ours. The best-known demonstration came in 2023, when Lingjiao Chen, Matei Zaharia and James Zou put the same questions to GPT-4 in March and again in June, under the same model name. Over those three months its accuracy at identifying prime numbers fell from 84% to 51%, and it had grown less willing to answer some sensitive questions. Nobody at the calling end had deployed anything; the thing behind the name had simply become different.

Providers do announce some changes. They publish deprecation schedules: dated notices after which a particular model version stops answering requests. These belong on an operations calendar as surely as a certificate expiry, with one wrinkle worth knowing in advance. The same model is often sold both through the provider’s own API and through the large cloud platforms that resell it, and the retirement date can differ between them, so the date that matters is the one on the platform we actually call. Other changes are easier to miss, such as a convenient model alias quietly being moved to a newer snapshot; Section 8.2 tells how Encore learned that one.

None of this is entirely new in kind, only in degree. In 2015 D. Sculley and colleagues at Google, writing about the hidden technical debt in machine learning systems, named the governing principle CACE: changing anything changes everything. Their point was that a learned component entangles its inputs, so that a small change in one place shifts behavior everywhere. They were writing about models their own teams trained and owned. With a hosted model the principle still holds, but some of the changes are now made by someone else, and we discover them only if we are measuring the right things.

Budgets in tokens and seconds

The last three rows of Figure 8.1 share a single theme: the token has replaced the request as the unit in which a model’s work is counted. A token is a fragment of text, often a word or part of a word, and a model both reads and writes in them. This one change of unit reaches into cost, latency and capacity at once.

Cost comes first, because it explains the startling figure in the table. A conventional service charges per request or per hour, so one call costs about what any other call costs. A model charges per token, both for the tokens we send it and for the tokens it writes back. A fan asking a one-line question that gets a one-line answer is a cheap call. The same fan asking a question that makes the assistant pull several long policy passages into its context and then write a careful, detailed reply is an expensive one. The two calls look identical to a request counter, yet one can cost 100× the other. This is one reason every model call at Encore passes through the AI gateway, and why the gateway sends most turns to a much cheaper model and saves the expensive one for the turns that need it.

Tokens are also the unit of capacity, which matters most on exactly the days Encore cares about most. Providers cap both requests per minute and tokens per minute for each model family, and some will throttle usage that climbs too sharply even when it is still under quota, because a sudden ramp is hard for them to absorb. Encore’s on-sales produce precisely that shape. On a big on-sale morning, support traffic triples within the hour as fans with questions about the sale all ask for help at once. That is the very ramp such limits are designed to punish. For this reason the token quota now sits in the on-sale readiness review, on the same page as the Sale Gate’s admission rate, and is checked with the same seriousness. For the largest on-sales Encore goes further and buys reserved capacity, the priority tiers or provisioned throughput that providers sell to customers with predictable peaks. A quota increase requested on the morning of the sale arrives too late to help; capacity for a peak we can see coming has to be bought before it comes.

Latency has a new shape as well. In a conventional service, latency is a single distribution measured in milliseconds: the request goes in, the response comes out, and we track the p95 and p99 of the gap. A model writes its answer one token at a time, so its latency splits into two quite different quantities, measured in seconds. Huyen’s AI Engineering separates them. Time to first token (TTFT) is how long the user waits before anything appears on the screen. Time per output token is how quickly the rest of the answer follows once it has started. The user feels the first: a blank screen for four seconds feels broken, while a reply that begins at once and then streams steadily feels responsive, even if it takes longer in total. The bill and the queue feel the second, since every output token is paid for and occupies capacity while it is being written.

These differences come together in the way Encore states what “working” means for the assistant. Its service level objective (SLO) has three parts. The first is gateway availability, the familiar kind of objective from earlier chapters. The second is a TTFT under 1.5 s for 95% of turns, a target that quietly presumes two design choices: that the reply is streamed to the fan as it is written, and that the model answering is not one that deliberates at length before it begins. The third is a quality objective, a measure of whether the assistant’s answers are actually right, held to a target like the other two. The first two objectives would have been at home anywhere in this book. The third is the one Figure 8.1’s second row makes necessary, since an assistant can meet both of the others while telling fans the wrong thing. Quality objectives wired to alerts are young practice, and I have come to believe they are where most of the real operational work on AI features lies; much of this chapter is about how to set one well.

Recap. A hosted model keeps the outward shape of a managed dependency, but it withdraws the determinism our instruments depend on, fails in the content of its answers rather than in its status codes, changes on its provider’s schedule instead of ours, and meters its cost, latency and capacity in tokens. Its SLOs therefore need a quality component alongside availability and latency, and its quotas, ramp limits and retirement dates belong on the on-sale calendar next to everything else that can stop a sale.

Exercise 8.1. Choose one model-backed feature you run and fill in Figure 8.1’s right-hand column with real values for it: the exact model version, its retirement date on the platform you actually call, your token quota and your observed peak, and your p95 TTFT. Mark each value you had to look up rather than already knew, since those marks show where your operational picture of the feature has gaps.

8.2 Model and Prompt Changes as Deployments

What actually ships

Section 8.1 showed that a hosted model can change beneath us. The first practical consequence concerns our own changes, and it begins with a simple question: when an AI feature starts behaving differently, what changed? In a conventional service the answer is almost always the code. In an AI feature the answer is usually something that does not look like code at all: the prompt, the model version, the temperature setting, the descriptions of the tools the model may call, or the settings that decide which documents retrieval puts in front of it. Each of these is a few lines of text or a single number, and each can change what the feature does as much as a rewrite of the code would. A sentence added to a system prompt can alter how every refund is explained; a new model version can alter how every sentence is read. Because these changes carry the weight of code, each of them goes through the pipeline Chapter 4 built for code, with the same review, the same tests and the same staged rollout.

To do that, we first need a name for the thing being shipped, and the theater already has one. The prompt book is the stage manager’s master copy of the script, annotated with every lighting, sound and scenery cue. An actor may try something new in rehearsal, and a designer may move a cue by half a second, but the change counts only once it is written into the book, because the book is what the crew follows on the night. The analogy fits our problem well: an AI feature’s behavior is spread across many small settings, and unless they are gathered into one authoritative record, nobody can say exactly which version of the show is playing. Encore’s prompt bundle is that record: the versioned unit that defines an AI feature’s behavior, and that ships whole or not at all. Here is what it contains:

In the bundle Why it is versioned with the rest
System prompt and templates business logic written in natural language
Model identifier: a pinned snapshot ID an alias can move under you
Sampling parameters (temperature, max tokens) they change the distribution of outputs
Tool descriptions and schemas the model reads them as instructions
Retrieval configuration (index version, top-k, reranker) it decides what the model is shown
Eval suite version and pass thresholds the bundle’s own fitness function

Figure 8.2 — Encore’s prompt bundle. Changing any row produces a new bundle version, which passes the same gates as a code change.

Each row earns its place for a reason worth spelling out. The system prompt is business logic, only written in English instead of a programming language. The sampling parameters shape how adventurous the model’s answers are. The tool descriptions are easy to mistake for documentation, but the model reads them as instructions, so rewording one can change when and how a tool is called. The retrieval configuration decides which help articles and policies the model sees, and a model cannot follow a policy it was never shown. The analogy with the prompt book breaks in one instructive place. A stage manager’s book is read by people, who notice an odd cue and ask about it; a prompt bundle is read by a model, which follows whatever it is given with equal confidence. The discipline of the book matters even more for us than for the theater, because nobody downstream will question an error.

Gathering everything into one bundle that travels through the pipeline guards against what Sculley and his colleagues called configuration debt: the slow accumulation of settings that shape a machine learning system’s behavior but live outside the discipline applied to its code, scattered across files and consoles, rarely reviewed and seldom tested. The idea comes from their 2015 paper; the bundle’s exact shape is Encore’s own. Two choices within it deserve a closer look.

The first is how the model is named. The bundle names it by a pinned snapshot ID, an identifier for one specific, fixed version of the model, and never by a floating alias, a convenient name such as “the current small model” that the provider can repoint at a new version whenever it likes. Pinning fixes the model’s weights, and so it largely prevents the provider-side drift that Section 8.1 described. “Largely” is the right word. Providers acknowledge that changes to their serving infrastructure, to how they batch and run the model, can still shift a pinned model’s behavior slightly. Pinning narrows the problem without removing it, and we shall see shortly why that small residue matters.

The second is that the eval suite travels with the bundle. An eval suite is the set of test conversations and checks that Section 8.3 develops, and its pass thresholds define what “good” means for this feature. If the evals lived elsewhere, someone could loosen a threshold in one place while changing the prompt in another, and no reviewer would see the two together. Keeping them in the bundle means that a change to what “good” means appears in the same diff as the change it judges, where a reviewer can ask whether the bar moved to fit the new behavior.

With the bundle defined, its journey to production is the one Chapter 4 laid out for any change. The bundle is promoted through GitOps: its desired state lives in a Git repository, and an automated process makes production match that state. Continuous integration (CI) runs the eval suite of Section 8.3 against the new bundle before it goes anywhere. Then a canary in the manner of Section 4.3 sends a small share of real traffic to the new bundle and compares it with the current one, the baseline, on three measures: the escalation rate (how often conversations are handed to a human), the quality of a sample of answers, and cost.

Two details of the canary are specific to conversations. First, each conversation stays in one arm of the experiment from start to finish. A fan whose conversation switched bundles halfway through would hear two different voices, and any difference in outcome could no longer be attributed to either bundle. Second, the canary’s duration is set by two conditions together. It runs at 5% of traffic for at least a day, so that it sees the full daily rhythm of support traffic, and it keeps running until every watched category, refunds included, holds enough conversations to show a regression if one exists. The second condition is the one that matters most. Refund conversations are a small fraction of the total, so 5% of a day’s traffic may contain far too few of them to reveal anything, and a canary that ended on the clock alone would declare the bundle safe for refunds simply because it had not yet met enough of them.

Rollback is a git revert: restore the previous bundle in the repository, and the automation restores it in production. That is as simple as rollback gets, with one condition that code rollbacks do not share. Reverting works only while the previous bundle names a model that still exists, and Section 8.1’s deprecation schedules guarantee that every model eventually stops answering. A rollback plan for an AI feature therefore has an expiry date. Encore records that date in each plan, so that nobody discovers in the middle of an incident that the safe version they are reaching for has been retired.

The upgrade nobody scheduled

Encore learned the pinning rule the way most rules in operations are learned, by breaking it first. The assistant uses a cheap tier, a smaller and less expensive model that answers about 80% of conversational turns. That cheap tier had been configured during a hurried launch, and it referred to the model by the provider’s alias for its current small model. The alias looked like a version, so nobody thought of it as something that could change. One Tuesday the provider moved the alias to a new snapshot. It had announced the move in advance, in a notice that nobody at Encore had read. Nothing failed: no errors, no timeouts, no alerts. Every dashboard stayed green, which is exactly the silent failure Section 8.1 warned about.

What changed was one word. Encore’s refund workflow approves a refund automatically only when two conditions both hold: the ticket costs under €80, and more than 48 h remain before showtime. Everything else goes to human review. In roughly a third of refund conversations, the new model explained the rule with or in place of and, turning two conditions that must both hold into two conditions of which either will do. Consider a fan with a €140 ticket for a concert five days away. The fan satisfies only the timing condition, so under the real rule the refund goes to a person. The assistant told this fan that the refund would be automatic, and the fan then watched it go to human review. The workflow did the right thing; the assistant had promised the wrong thing.

The nightly eval run caught it, and the reason that run exists is worth understanding. Evals in CI test a bundle when someone changes it, but this change involved no commit and no deployment, so CI never saw it. Encore therefore also runs its golden set of 200 support conversations every night against the production configuration, exactly as it is serving fans. The reasoning is the residue from the pinning argument above: configuration can change without a commit, as the alias just had, and even a properly pinned model can shift slightly as its provider changes how it is served. A test that runs only when we change something cannot catch a change that someone else made.

Eighteen of the 200 golden conversations concern refunds. For months they had passed 18 of 18. On Wednesday morning, 11 passed. The Support Experience on-call engineer read the seven failing conversations one by one, saw the same misplaced “or” in each, and recognized one fault where the numbers alone suggested seven. By lunchtime the cheap tier was pinned to the previous snapshot.

The golden set catches a moved alias Nightly golden-set pass rates over nine days. Refund cases pass 18 of 18 every night until the Wednesday run, after the provider moved the cheap tier's alias on Tuesday, when they pass 11 of 18; the overall score dips only from 193 to 186 of 200. The previous snapshot is pinned by Wednesday lunchtime, about 30 hours after the move, and Thursday's run is back to 18 of 18. THU FRI SAT SUN MON TUE WED THU FRI 40% 50% 60% 70% 80% 90% 100% ONE WEEK AND A BIT GOLDEN-SET PASS RATE, NIGHTLY RUN AT 02:00 30 H ON THE MOVED ALIAS REFUND CASES 11 / 18 OVERALL 193 → 186 / 200 REFUND 18 / 18 PINNED BACK Refund cases (18) All cases (200)

Figure 8.3 — The nightly golden-set run across the week the alias moved. The refund cases fell from 18 of 18 to 11 while the overall score barely moved, which is why the team reads each category before the total.

The figure carries a lesson about averages. Seven failures out of 200 is a drop of a few percentage points in the overall score, the kind of dip a team might put down to noise. Seven failures out of 18 refund cases is a loss of more than a third in the one category where a wrong answer carries a financial promise. The same seven conversations look trivial in one view and alarming in the other, and only the per-category view shows what is happening. An average is a summary, and a summary is precisely where a small, serious failure goes to hide. That is why Encore’s engineers read each category before they look at the total.

It would be comfortable to conclude that no harm was done, because the refund policy lived in the workflow and the workflow never changed. That conclusion is half right. It is true of the money: the ledger paid nobody wrongly, because the assistant’s words have no power to move funds. It is untrue of the promises. A fan who was told a refund would be automatic has been told something by Encore, and courts and tribunals have begun to treat such statements seriously. In Moffatt v. Air Canada (2024), a British Columbia tribunal held the airline to a bereavement refund that its chatbot had described wrongly, rejecting the airline’s argument that the chatbot was responsible for its own words. What an assistant tells a customer can create an obligation for the company that deployed it. I have come to believe this is where many teams misjudge AI features: they guard the actions carefully and treat the words as harmless, when the words are themselves a kind of action.

So the harder question was not how to fix the fault, which took a morning, but how many fans had heard the wrong rule in the 30 hours before the rollback. A golden set cannot answer that. It tells us how the assistant handles 200 conversations written in advance; it says nothing about the real conversations that took place while the fault was live. Answering it means looking at production itself, which is where the next section’s evals eventually lead.

Recap. Everything that shapes an AI feature’s behavior, from the prompt and the pinned model to the tool descriptions, retrieval settings and evals, ships as one versioned bundle through the ordinary pipeline, and its canary runs until every watched category has enough traffic to show a regression. Because a model and its serving can change without a commit, evals also run nightly against production, and a rollback works only as long as the old model is still offered. The assistant’s words are part of the company’s obligations, so a fault in what it says is an incident even when no money moves.

Exercise 8.2. List every artifact that shapes the behavior of one AI feature you run: prompts, model identifiers, parameters, tool descriptions, retrieval settings and evals. For each, note where it is stored, whether a change to it passes through CI, and whether it can be rolled back in under 10 minutes. Any “no” marks a deployment path you have not yet built, and any model identifier that is an alias marks a change someone else can make for you.

8.3 Evals — Testing Systems That Don’t Repeat Themselves

Error analysis before metrics

The nightly run that caught the refund fault deserves a closer look, because it was not luck. It was the product of a discipline, and that discipline is the subject of this section. The Architecture book explained why a probabilistic component is evaluated rather than unit-tested: when the same input can yield different outputs, we cannot assert one expected answer, and so we judge whether each answer falls within the range of acceptable ones. Here we take up the operating side of that idea. Shreya Shankar and Hamel Husain’s Evals for AI Engineers (2026) teaches evals as a weekly practice rather than a project finished once, and its method begins at the step most teams skip in their hurry to get a number on a dashboard. Before choosing any metric, we read traces, the recorded conversations themselves.

Error analysis is the practice of reading a sample of real interactions, noting in plain words what went wrong in each, and grouping those notes into a taxonomy of failure modes, continuing until new traces stop producing new categories. Its lineage lies outside engineering, in the open coding of qualitative research, where a researcher reads interview transcripts without a fixed scheme and lets the categories emerge from the material rather than imposing them in advance. The practice sounds slow, and in the first hours it is. It is nonetheless the fastest route to useful evals, because it builds them around the failures we actually have instead of the failures we imagined before launch. There is a further reason, and it is a humbling one: Shankar’s own research found that people’s criteria for a good output shift as they read outputs. We do not fully know what “good” means for our assistant until we have seen enough of what it says, so a metric chosen before that reading is a guess about our own standards.

Encore’s refund fault, seen now through this lens, shows why the order matters. Across the whole golden set, the overall score moved only from 193 to 186 out of 200, a dip of a few points that teams soon learn to shrug at, since scores of this kind wobble from night to night. What told the real story, as Section 8.2 recounted, was the on-call engineer’s reading of the seven failures, which revealed one fault repeated seven times: the model had turned the rule’s and into or. That reading was error analysis in miniature, done under pressure. The team’s first response was the simplest instrument available, a code check that any explanation of the refund rule states both conditions jointly, the €80 price limit and the 48 h before showtime. That choice reflects a general rule of the craft. Wherever a property can be checked deterministically, by code that gives the same verdict every time, we check it that way first, because such a check is cheap, fast and beyond argument.

Judges need calibrating

Many properties resist code, however. Whether an answer is grounded in the retrieved documents, whether it is courteous, whether it actually addresses what the fan asked: these need judgment. For them we use a model as a grader, the LLM-as-judge, a second model prompted to score the first model’s output against a stated criterion. It is tempting to treat the judge’s scores as facts. They are readings from a measuring instrument, and like any instrument the judge must be calibrated before its numbers mean anything, just as a laboratory scale is checked against known weights before anyone trusts what it reports.

The question of how to calibrate a judge is older than computing. In 1960 Jacob Cohen published his kappa statistic for agreement between two raters beyond chance, because two people coding the same interviews agree less than either believes, and raw agreement flatters them. To see why chance matters, suppose that nine conversations in ten are perfectly fine. A lazy judge that marks every conversation “fine” will agree with a careful human nine times in ten, and yet it has learned nothing at all. Kappa corrects for this. It asks how much of the observed agreement exceeds what two raters would reach by guessing in proportion to how often each uses each label, and it scales the result so that zero means no better than chance and one means perfect agreement. Our lazy judge would score a kappa of zero, which is exactly what it deserves.

Shopify has published the clearest production account of what calibration buys. For its Sidekick assistant, the judge’s agreement with human experts rose from a kappa of 0.02, essentially chance, to 0.61, against 0.69 for two humans judging the same cases. That last figure is the important one. Human experts do not agree perfectly with each other either, and so their agreement is the ceiling against which any target for the judge should be set; demanding that a judge agree with people more than people agree with one another asks for something no instrument can deliver.

Encore follows the same method. Its support team labels conversations by hand, tunes the judge’s prompt on one split of those labels and measures agreement on a separate held-out split that the tuning never saw, which keeps the team from fooling itself the way students fool themselves by memorizing the answers to a practice exam. The team measures agreement per category, because a judge can be excellent on easy cases such as order-status questions and poor on refunds, and a single overall kappa would hide the weakness precisely where it matters most. And because the judge is itself a model, subject to everything Section 8.1 said about models, it is re-checked monthly and whenever its snapshot changes. An instrument that can drift must be recalibrated on a schedule.

Non-determinism also changes what passing means. A conventional test that passes once will pass again; a golden case for a model may pass on one run and fail on the next. Encore therefore runs each golden case five times and counts it as passed only if all five runs pass. This is the pass^k measure from Yao and colleagues’ τ-bench (2024): the probability that a case succeeds on every one of k attempts, rather than on at least one. The stricter measure matches what fans experience. A case that succeeds 80% of the time looks healthy on a single run, yet it passes all five runs only about a third of the time (0.8 multiplied by itself five times is roughly 0.33), and that third is a fairer picture, since a fan who meets the one failing run in five simply gets a wrong answer and has no way of knowing it.

Small categories have statistical limits, and honesty requires us to name them. With 18 refund cases, each case is worth more than five percentage points. A fall from 18 passing to 11 is unmistakable, and that is the fall Encore saw. A fall to 16 could be noise, two unlucky runs on a bad night, and no threshold on a set this small can tell the difference reliably. Encore accepts that the golden set will catch large regressions and leaves subtle ones to production, which brings us to the second half of the practice.

From golden set to production sampling

Medicine faced Encore’s question long before software did, and learned the answer at terrible cost. After the thalidomide disaster of the early 1960s, regulators confronted an uncomfortable truth about clinical trials. A trial tests a drug on chosen patients under watchful conditions, but the drug’s real behavior emerges only across everyone who takes it, in combinations and circumstances no trial anticipated. And so regulators built post-market surveillance, the continuing collection of evidence about a drug once it is in use. The parallel to our work is close. A golden set is the trial: chosen cases, run under controlled conditions before release. Production sampling is the surveillance: evaluating a share of real interactions with the same judges and code checks, sending a smaller share to people for review, and watching the results as a time series rather than a single verdict. The analogy breaks at one point, and the break is instructive. An approved drug does not change after approval, while a hosted model can, as Section 8.2’s alias move showed. Our surveillance must therefore watch not only for rare effects in an unchanging product but for a product that may quietly become a different one.

Encore needed exactly this surveillance on the Wednesday of the refund fault. The golden set had shown that the model was wrong; it could not say how many fans had heard the wrong rule in the 30 hours before the rollback. Answering that question took two engineers a day, because the evaluator for refund explanations had to be built first and then run over the past conversations. Of about 520 refund conversations in the 30-hour window, it found 164 faulty explanations. With Moffatt in mind, Encore wrote to each of those fans with the correct rule, expedited their reviews and honored any automatic refund the assistant had promised. Then came the structural fix, which mattered more than the cleanup. The assistant no longer paraphrases the refund rule at all. It fills a template from the workflow’s computed verdict, so the words a fan reads come from the code that moves the money. Where a fact can be computed, it is safer to compute it and let the model carry the result than to ask the model to restate the rule and hope it restates it faithfully.

That day’s work became a standing practice. Encore now evaluates a 3% daily sample of conversations and sends 40 a week to human reviewers. The human sample is not drawn uniformly: it oversamples conversations where the judge was uncertain or the fan escalated, because those are where the automated instruments are least trustworthy and a human reading teaches the most. Transcripts are stripped of payment details before anyone reads them and are kept under the same retention rules as support tickets, since a sample of real conversations is still customer data.

The sampled pass rate on refund explanations has become a quality service level indicator (SLI), a measured signal of how well the service is serving its users, with an objective of 98%. Its burn rate, the speed at which failures are consuming the allowed 2%, pages the Support Experience team during business hours, in the manner of the alerts Chapter 1 built for availability. This is Encore’s own adaptation of Chapter 1, without a public precedent I know of, and it has a known weakness. Judge noise makes the signal jumpy, because a judge with imperfect agreement will mark some good answers bad and some bad answers good, and over a small window those errors alone can look like a burn. The alert therefore fires only on windows holding 150 or more sampled refund conversations, enough that a real regression stands out from the instrument’s own scatter.

The two loops also feed each other. Every failure found in production becomes a new golden case, so over time the golden set grows toward the conversations fans actually have rather than the ones its authors imagined, as Shopify’s does. The trial learns from the surveillance. Figure 8.4 places both loops beside the request path.

The AI request path and its eval loop Fans reach the support assistant and the refund agent, which call models only through the AI gateway; the gateway sends about 80% of turns to the cheap tier, hard turns to the frontier tier and fails over to a second provider. The refund agent gets a 15-minute token by token exchange, calls tools through the MCP server and the refund workflow, which sends some refunds to a human review queue. Beside the path, a 3% daily sample of conversations and the nightly golden set pass through the same evaluators, whose quality SLI pages the on-call engineer who can pull the gateway's degradation ladder, and production failures become new golden cases. ≈80% 15-MIN TOKEN TOOL CALLS OUTSIDE AUTO RULE CONVERSATIONS 3% DAILY NIGHTLY SLI 98% · PAGES NEW CASES Fan chat, ticket Support assistant RAG over help, orders Refund agent acts for one fan AI gateway route · meter · break Cheap tier pinned snapshot Frontier tier hard turns Fallback provider pinned, tested weekly Token exchange RFC 8693 MCP server orders:read · refunds:request Refund workflow €80 and 48 h, in code Human review queue policy panel, seeded Production sample 3% a day · 40 a week to people Evaluators calibrated judges · code checks Golden set 200 cases · each run five times People Control point Provider Request or call Token or signal Failures feed the set

Figure 8.4 — The request path through the AI gateway, with the eval loop beside it. The quality SLI reaches the gateway only through the on-call engineer.

Practices that live only in people’s habits erode under deadline pressure, and so Encore gathers these into one file in the prompt bundle. CI refuses to promote a bundle that misses any line of it. The file reads almost as a summary of this section: the golden set and where it came from, the five-run rule, gates set per category rather than on the overall average that hid the refund fault, a minimum kappa for every judge with the human ceiling recorded beside it, and canary conditions large enough to show a regression in each category:

# release-gate.yaml: in the prompt bundle; CI blocks promotion on any miss
golden_set: golden/2026-09        # 200 cases, grown from error analysis and production
runs_per_case: 5                  # a case passes only if all five runs pass (pass^5)
gates:                            # per category; the overall average never gates
  refund_explanation: { min_pass: 1.00, evaluator: code/refund_rule_joint }
  order_status:       { min_pass: 0.95, evaluator: judge/grounded@v7 }
  injection:          { min_pass: 1.00, evaluator: code/no_out_of_policy_tool_call }
judges:                           # no gate may rest on an uncalibrated instrument
  judge/grounded@v7:  { min_kappa: 0.60, human_kappa: 0.70, labels: heldout/2026-09 }
canary: { share: 0.05, sticky: conversation, min_hours: 24, min_per_category: 150 }

Notice that the refund gate demands a pass rate of 1.00 and rests on the code check born from the error analysis, not on a judge. The lesson of the or has been written into the one place every future change must pass through.

Recap. Evals begin with error analysis: reading real traces until the failure categories stop growing, so that our metrics describe the failures we actually have. Code checks come first wherever a property can be computed; LLM judges are instruments, calibrated against human labels per category, with the agreement between humans as their ceiling. The golden set is the trial and production sampling the surveillance, and a release gate in the bundle makes both binding on every change.

Exercise 8.3. Read 50 recent traces from an AI feature you run, write one line per failure in plain words and group the lines into at most eight categories. Which failures have no metric on your dashboard, and which of your metrics match no failure you found?

8.4 Failure Modes, Guardrails, and Graceful Degradation

A field guide to content failures

The previous section built instruments for noticing when the assistant’s words go wrong. Instruments are only half of the work, though, because a signal that nobody knows how to answer is merely an alarm. Medicine learned long ago that a disease has to be named before it can be treated, since the name is what connects a set of symptoms to a test that confirms them and a treatment that helps. Content failures deserve the same discipline. Each kind of failure needs a name, each name needs a detector that tells us it is happening, and each detector needs a planned response that limits the harm. The table below is Encore’s field guide, five failure modes with the way each one shows itself, the instrument that catches it and the mechanism that contains it:

Failure mode How it shows Detected by Contained by
Hallucination a confident claim unsupported by what was retrieved grounding check; sampled judges answer only from citations, else hand off
Drift quality decays with no deploy nightly evals on production config; sampled SLIs pinning, corpus freshness SLOs, rollback
Refusal the model declines legitimate requests, often after an upgrade refusal-rate metric per intent eval cases that must be answered; fallback model
Loops an agent repeats a step without progress step and token counters per trajectory step caps, spend ceilings, escalation
Tool misuse wrong tool, wrong arguments or an out-of-policy call schema validation, authorization denials, trajectory evals scoped credentials, workflow-enforced policy

Figure 8.5 — Content failure modes with their detectors and containment. The right-hand column lives outside the model, so it keeps working when the model misbehaves.

The right-hand column deserves a second look, because it holds the table’s most important idea. Every containment mechanism there (a citation requirement, a pinned version, a step cap, a scoped credential, a policy enforced by the workflow) is ordinary software that sits outside the model. None of them depends on the model behaving well, and that is the point: a guard that relies on the very component it guards fails at the moment it is needed. We cannot make a fluent model incapable of error, but we can arrange the system so that its errors land somewhere soft.

Two rows in the table are easier to underestimate than the others. The first is drift. A drift is a slow decline in quality that arrives without any deploy of ours, and a hosted model faces three of them at once: the corpus it retrieves from changes as help articles are edited, the questions fans ask change with the season and the shows on sale, and the provider changes the model itself. Pinning a model snapshot, as Section 8.2 described, largely prevents the third kind. Nothing prevents the first two, and only evals catch them, because the code, the configuration and the model can all be exactly as they were while the world around them moves.

The second is refusal. A refusal is the model declining a request it ought to answer, and refusals are easy to miss because they look safe. Nobody writes an incident report about an assistant that was too careful. Yet Chen and her colleagues, whose study of GPT-4 we met in Section 8.1, watched the model grow more reluctant to answer some questions between its March and June releases under the same name. Imagine that happening to Encore’s assistant: it begins politely declining to discuss refunds, every request returns quickly and without error, and the dashboard stays green. From the fan’s side, the assistant is down. That is why the table measures refusal rate per intent and keeps eval cases that the model must answer, so that a loss of nerve after an upgrade shows up as a failing test rather than as a slow trickle of frustrated fans.

The degradation ladder

Guardrails, the checks the Architecture book layered from schemas to grounding, work one answer at a time: they catch a single bad response and replace or block it. Operations needs something more, because sometimes a whole tier fails at once, and then the question is no longer how to fix one answer but what lesser service to offer in its place. That question should be answered long before anything fails. Graceful degradation is the planned, step-by-step retreat to simpler and more dependable forms of service when the preferred form is unavailable or untrustworthy.

Aviation has practiced this for decades through the minimum equipment list, a document that states in advance which instruments an aircraft may fly without, and under what conditions. Its value lies less in the list itself than in when it was written. Nobody decides at the gate, with passengers boarding and the departure slot approaching, whether the flight can go without its weather radar; that was decided months earlier, calmly, by people who were not running late. The analogy fits Encore well, with one difference worth noting: an aircraft’s instruments either work or do not, whereas a model can be fully available and still be wrong, a case we will return to shortly.

Encore’s ladder has four rungs, and each one is exercised on game days so that it is known to work before it is needed:

  1. Frontier tier for hard turns and cheap tier for the rest, the normal state.
  2. Fallback provider: a second provider’s model, pinned and run against the golden set weekly; degraded by design, since prompts do not transfer cleanly, with quota sized for failover load.
  3. Retrieval only: the assistant shows the most relevant help articles and the fan’s order status, without generating prose.
  4. Human handoff: the conversation joins the support queue with a summary, and the fan is told honestly how long the wait is.

Read from top to bottom, the ladder trades capability for dependability. Each rung down gives up something fans value (fluent answers, then generated prose altogether, then an immediate reply) in exchange for something that is harder to break. The second rung is labeled “degraded by design” because a prompt tuned for one model does not carry over cleanly to another, so the fallback answers less well, and Encore accepts that openly rather than pretending the two are interchangeable. Retrieval only removes generation entirely, which removes hallucination with it, since showing a help article and an order status involves no model writing anything. The last rung hands the fan to a person and tells the truth about the wait, which is a form of service even when it is a slow one.

The second rung also carries the most uncertainty, and it is worth being honest about why. Multi-provider gateways run at scale, Uber’s among them, so routing between providers is ordinary engineering. What I know of no published account of is a cross-provider failover during a real outage, when the first provider is down for everyone and other customers crowd onto the same second provider at the same moment. The quota Encore reserved might hold, or the second provider might buckle under everyone’s failover at once. Encore therefore treats that rung as a hypothesis rather than a guarantee, and tests it on game days. The rungs themselves are pulled by the gateway’s circuit breakers, which trip on errors, latency or rate-limit exhaustion, using the patterns of Section 5.2.

The trigger teams forget is quality. A circuit breaker watches for a dependency that is failing in ways a machine can see: errors, timeouts, refused requests. A model that is available but wrong produces none of those signals, so it never trips a breaker, and this is exactly where the minimum-equipment-list analogy breaks down. Encore does watch quality, through the refund-explanation SLI of Section 8.3, but here it deliberately stops short of automation. When that SLI burns its error budget at a sustained rate, it pages a person. That person reads a few of the failing conversations, and if they agree the model is wrong, they flip a prepared switch, already tested on game days, that drops refund intents to the retrieval-only rung.

Why not let the SLI flip the switch itself? Because a judge-based SLI is noisy. Its readings come from a model judging another model on a sample, and some of its failures are the judge’s mistakes rather than the assistant’s. A false trip would push every fan with a refund question down to retrieval only, a real loss of service caused by a measurement error. The trade-off, then, is between the speed of an automatic response and the cost of acting on a signal Encore does not yet fully trust. Until the team has watched that signal through a year of on-sales and learned how it behaves, it keeps a person between the measurement and the switch. I have come to believe this is the right order for any new signal: earn trust in the instrument first, and only then hand it the controls.

The degradation ladder Four rungs stepping down from the normal state, frontier and cheap tiers, to a pinned fallback provider, to retrieval only, to human handoff. The gateway's circuit breakers, tripped by errors, latency or rate-limit exhaustion, pull the ladder to the fallback automatically; a sustained burn of the refund-explanation quality SLI pages the on-call engineer, who may flip a prepared switch that drops refund intents to retrieval only, because a model that is available but wrong never trips a breaker and a noisy judge-based signal is not trusted to act alone. TRIPS ON-CALL SWITCH RUNG 1 · NORMAL Frontier + cheap generated, cited answers RUNG 2 Fallback provider same job, other model RUNG 3 Retrieval only help articles, order status RUNG 4 Human handoff queue, summary, honest wait Gateway breakers errors · latency · rate limit Refund quality SLI burn pages on-call EVERY RUNG DECIDED IN ADVANCE AND EXERCISED ON GAME DAYS Rung: what the fan gets Trigger Steps down The trigger teams forget

Figure 8.6 — The degradation ladder and what pulls it. Breakers see errors, latency and rate limits; only the quality signal sees a model that is up and wrong, and it reaches the ladder through a person.

Putting a person in the loop solves one problem and opens another, and Weizenbaum would recognize it. His secretary, as the chapter’s opening recalled, trusted ELIZA within minutes; Encore’s reviewers face a slower version of the same pull. After months of good proposals from the refund agent, they begin to approve faster and read less. This is the irony of automation the chapter opened with, and here it is made worse because the agent’s proposals are so well written. A clumsy proposal invites scrutiny; a fluent one invites a nod.

Encore answers the trap in two ways. The first separates facts from prose. The review screen shows the policy facts (the ticket price, the hours remaining before showtime, and the refund rule’s verdict) in a panel computed by the workflow, set apart from the agent’s explanation. The reviewer can check the decision against numbers the agent did not write, so a persuasive paragraph cannot quietly substitute for a correct one. The second measures attention directly. A few times a month, Encore seeds the queue with a proposal it knows to be wrong and tracks how often reviewers catch it. The precedent is airport security, where threat image projection inserts fictional weapons into real X-ray images, both to keep screeners alert and to measure what they actually catch. Attention that is never tested cannot be known, and a review step whose catch rate is unknown is a review step in name only.

Recap. Each content failure needs a name, a detector that notices it and a container outside the model that limits its harm. A degradation ladder, decided in advance like a minimum equipment list, steps down from the frontier model to human handoff; circuit breakers pull it automatically on errors and latency, while a burn of the quality SLI pages a person who decides. Human review stays meaningful only if the interface separates computed facts from fluent prose and the team measures the reviewers’ attention instead of assuming it.

Exercise 8.4. Write the degradation ladder for one AI feature you run, naming each rung’s trigger, who or what pulls it, and when it was last exercised. Add one quality trigger, and decide whether a person or a machine acts on it, giving your reason.

8.5 Agents with Permissions — Sandboxing, Injection, and Blast Radius

Credentials shaped like the task

Everything in this chapter so far has concerned a system that talks. The support assistant explains, summarizes and occasionally misleads, and the worst it can do on its own is say something wrong. The refund agent is a different kind of component, because it acts: it reads orders, requests refunds and moves money toward people. An assistant that only talks can mislead a fan; an agent that acts can do damage that no apology reverses. The Architecture book gave Encore’s refund agent delegated authority, meaning the agent acts on behalf of a particular fan rather than in its own right. That was a design decision made on a whiteboard. The operational question, the one this section answers, is whether the design holds when somebody is actively trying to break it.

The problem the design must solve is older than language models by several decades. In 1988 Norm Hardy published a short paper called “The Confused Deputy.” It described a compiler that had been granted permission to write to a billing file, which it needed for its own bookkeeping. A user, running the compiler, named that same billing file as the place to write the compiler’s output. The compiler did as it was asked. It wrote the output using its own authority, not the user’s, and so it overwrote a file the user could never have touched directly. Nothing was malicious about the compiler; it was simply a deputy holding more power than the person it was serving, with no way to tell whose authority a given request should run under. An AI agent is a confused deputy by construction, and the reason is structural. A conventional program keeps its instructions (the code) apart from its data (the input), but a language model receives both in the same stream of text, and nothing in that stream reliably marks which sentences are commands from its operator and which are content it happens to be reading. The UK’s National Cyber Security Centre (NCSC) said as much in late 2025, describing large language models as inherently confusable.

If the deputy cannot be relied on to know whose authority it is using, the remedy is to stop giving it authority of its own and to make the authority travel with each request. Encore’s refund agent holds no standing credentials at all: no service account, no long-lived API key, nothing that would let it act if it were simply switched on with no fan present. Instead, at the start of each conversation it performs an OAuth token exchange, the mechanism standardized as RFC 8693, in which one token is traded for another, narrower one. The token it receives names two parties, the agent and the fan it is serving, so every downstream system can see both who is acting and on whose behalf. The token carries only two scopes, orders:read and refunds:request, and it expires after 15 minutes. If the conversation runs longer, the agent must exchange again, and if the fan has gone, there is nothing to exchange.

I should be candid about how mature this is. Token exchange for agents is young practice: AWS AgentCore, Auth0 and Okta now support it, but I know of no published account of running it in production. Encore adopted it anyway because the principle beneath it, short-lived authority delegated by a named person, is old and proven, and only the plumbing is new. The credential is shaped like the task rather than like the agent: it describes one fan’s refund conversation, not everything a refund agent might conceivably need.

Touring crews use the same idea backstage. Each person wears a laminate, and the laminate determines which doors open: a lighting technician’s pass opens the dimmer room but not the artists’ dressing rooms, however persuasive its holder is and however urgent the story. Security at the door checks the pass, not the explanation. The analogy is useful precisely where it breaks. A human guard at a dressing room door can notice someone behaving strangely and decide to ask questions even when the laminate looks right. An API cannot; it sees only a token and a request. So the agent’s credential must be narrow enough that even a strange request, one nobody anticipated, cannot reach beyond it. The narrowness has to do the work that the guard’s judgment would have done.

The credential limits what the agent may touch. Three further controls limit what the agent can do with the environment around it, and they matter because the model inside cannot be trusted to refuse a bad instruction. First, the agent runs in a sandbox with no network egress except to two destinations: the internal Model Context Protocol (MCP) server, through which it calls Encore’s tools, and the AI gateway, through which it reaches the model. This mirrors the way cloud coding agents now run with internet access off by default. No file system outlives the conversation, so nothing an attacker plants in one session waits for the next. Second, budgets for steps, tokens and spend are enforced by the workflow engine, outside the prompt, so a looping or manipulated agent hits a hard wall that no clever text can talk its way past. Third, every tool call, with its arguments and the token’s subject, is written to an append-only audit log, so that after anything unusual we can reconstruct exactly what the agent attempted and for whom.

The ticket that tried to issue orders

The design met its first real attacker one Thursday in spring. A fan opened a support ticket complaining about a cancelled show. Below the complaint, formatted to look like an internal note, was a paragraph claiming to come from Encore’s finance team. It said the customer had been pre-approved for compensation and instructed the agent to refund three listed orders and to skip review, since the refunds were “already authorized.” Two of the three orders belonged to other people’s accounts. This is indirect prompt injection: an attack in which the malicious instruction does not come from the user typing to the model, but arrives inside content the system reads on the user’s behalf, such as a ticket, an email or a web page. Kai Greshake and colleagues first studied it systematically in 2023, and it is the confused deputy in its modern form, since the ticket body is data that the model may treat as a command.

The defenses meant to stop it at the door did not. The input classifier, which screens incoming text for known attack patterns, did not flag the ticket, and the model was partly persuaded: its trajectory, the recorded sequence of steps and tool calls in the session, shows refunds:request called for all three orders. Had the agent held a standing credential with broad refund rights, that would have been the end of the story. It did not. The orders API refused the two calls for other fans’ orders, and the credit for that belongs to the API rather than to the model or the classifier. The division of labor is worth stating carefully. The token says who is asking: this agent, on behalf of this fan. The API’s object-level check, which compares the token’s subject with the owner of each order requested, decides what that person may touch. Neither is sufficient alone. A perfect token is useless if the API never checks ownership, and OWASP ranks exactly that omission, a missing object-level authorization check, first among API security risks.

The third order genuinely belonged to the ticket’s author, so the ownership check passed. But Encore’s refund rule auto-approves only refunds under €80 when more than 48 h remain before showtime, and this order was €95, so the workflow routed it to a human reviewer. The reviewer saw the policy panel, computed by the workflow from the order’s price and the time to showtime, alongside the quoted paragraph from the ticket, and declined. The phrase “already authorized” had no effect on any of this, because the workflow does not read prose. Its decision rests on structured facts (the amount, the hours remaining, the rule’s verdict), and no sentence in a ticket can change those.

Detection came from plain monitoring rather than anything specific to AI. Denials issued to an agent’s identity are rare, provided the tools are designed so that a legitimate agent, serving the fan in front of it, seldom asks for something it may not have. That rarity is what makes them worth alerting on: a denial is a strong sign that the agent is being steered somewhere it should not go. Two denials in one trajectory paged the Support Experience team within minutes.

The blameless review that followed, of the kind the Operations book describes in its chapter on learning from incidents, made three changes and was honest about how much each was worth. First, ticket bodies now reach the model wrapped in delimiters that mark them as untrusted content. The review recorded this as a weak layer, and for good reason: Microsoft’s spotlighting experiments found delimiters the least effective of the marking techniques they tested, and in 2025 Milad Nasr, Nicholas Carlini and colleagues broke 12 published defenses against prompt injection with adaptive attacks, succeeding more than 90% of the time. A delimiter raises the cost of a lazy attack, but it is a request to the model, and the model is exactly the component we have already seen being persuaded. Second, the attack ticket and five variants of it joined the golden set, so every future prompt or model change is tested against them. Fixed test cases, though, say little about an attacker who adapts after seeing what fails, so the review added a human counterpart: before each major change to the prompt bundle, a security engineer spends a day attacking the agent. Third, the denial alert threshold dropped to one, since a single denial had turned out to be signal rather than noise.

What actually protects Encore is the structure the review left alone. There are three parts to it. The first is least privilege by construction: the agent can never hold more authority than one fan’s 15-minute token grants. The second is a person in front of any money that falls outside the narrow automatic rule. The third is that no session holds all three legs of Willison’s lethal trifecta, the combination of access to private data, exposure to untrusted content and a way to send information out. The refund agent reads untrusted text (the ticket) and private data (the fan’s orders), but its sandbox gives it no channel to send anything outward, so even a fully persuaded agent has no means of leaking what it has read. Meta’s “Agents Rule of Two” (2025) turns this reasoning into a rule a design review can apply: without human approval, a session may combine at most two of three properties, namely untrusted input, access to sensitive data or systems, and the power to change state or communicate externally. A design that needs all three must put a person in the loop. I have come to believe that injection must be contained by design, on the assumption that it will eventually arrive, rather than prevented by filters we hope will catch it; the NCSC says the same.

None of this makes the residual risk zero, and it is worth seeing exactly where the remaining risk lives: inside the policy itself. Suppose the attacker had asked instead for a €60 refund on their own order a week before the show. That request is under €80 with far more than 48 h to go, so the workflow would approve it automatically, injection or not, because it is a request the policy permits anyone to make. This is where the idea of blast radius becomes precise. For an agent, the blast radius is the worst outcome reachable within its credentials and its policy in a single session, multiplied by the number of sessions an attacker can open. The two factors call for different controls. Narrow sessions, with scoped tokens and the ownership check, keep the first factor small. A per-account limit on agent-initiated refunds keeps the second small, so that one attacker cannot open a hundred conversations and collect a hundred small refunds. That limit is nothing new: it is the velocity rule every payments fraud team already writes, applied to a new kind of caller.

Recap. An agent is a confused deputy by construction, because instructions and data reach it in the same stream of text. Its authority should therefore travel with the request, in short-lived tokens delegated by a named person, while the resource’s own ownership check decides what that person may touch. Sandboxing, budgets enforced outside the prompt, human approval for money and an append-only audit log contain what the model cannot be trusted to refuse. Delimiters and classifiers are weak layers worth having; the strong control is to deny every session the full lethal combination, and then to bound what remains by limiting both the harm of one session and the number of sessions.

Exercise 8.5. Choose one agent that acts on your systems and write down its credential: who issues it, which parties it names, what it can reach, how long it lives and which actions wait for a person. Check the design against the Rule of Two, noting which two of the three properties each session combines. Then estimate the worst outcome a single session could reach within policy, and how many sessions an outsider could start in an hour.

Kata: Grounded Flights

Your brief: “Skylark.” Skylark is a European regional airline that carries 4 million passengers a year on 90 aircraft, with a team of 60 engineers. It runs two AI features: a chat assistant that answers passengers’ questions, and a rebooking agent that can act, moving passengers onto other flights and issuing vouchers when a disruption strands them. Compensation under EU Regulation 261/2004 is fixed by flight distance at €250, €400 or €600, and people at Skylark, not the model, decide who receives it. The assistant, however, explains those rules to passengers every day, and Skylark’s lawyers have read Moffatt, so they know that what the assistant says can become a promise the airline must keep. Load is the second trouble. On a bad-weather day chat volume rises 30× within two hours, and last winter the token quota ran out at 07:40 on a morning like that. Change is the third: the model behind the cheap tier retires in 90 days, so a model change is coming whether or not anyone schedules it. Security is the fourth. Last month a passenger typed instructions into a booking’s “special assistance” field, the free-text box meant for requests such as a wheelchair at the gate, and nobody can yet say whether the agent read them, still less whether it obeyed.

Deliverables:

  1. Release unit — the prompt bundle and its release gate, as Section 8.2 describes them: the prompts, sampling parameters, tool descriptions, retrieval settings and eval suite that ship together as one versioned unit, with the model named by a pinned snapshot ID rather than an alias. Include a rollback plan that still works after the model’s retirement date, when the snapshot you would roll back to no longer answers.
  2. Eval plan — begin, as Section 8.3 does, with error analysis: invent 50 traces of the assistant at work, read them, and name the failures you find. From those failures build three evaluators. Make one code-based, a check a program can run without judgment, and one an LLM judge with an agreement target, set relative to how well two human reviewers agree with each other, since that is the ceiling any judge can reach. Finish with a plan for sampling live conversations and a quality SLI for compensation explanations, so you can answer the question Encore had to answer after its refund rule went wrong: how many people heard the wrong rule, and for how long?
  3. Capacity and degradation — reserve capacity for disruption days, sized to the 30× surge rather than to an ordinary Tuesday, and write a four-rung degradation ladder in the manner of Section 8.4, from normal service down to human handoff. Give each rung its trigger, including one for quota exhaustion and one for falling quality, and say which rungs a machine pulls and which a person pulls.
  4. Agent credential spec — describe the rebooking agent’s token as Section 8.5 describes Encore’s: what it names, which scopes it carries, how long it lives and how large a voucher it may issue unaided. Add the ownership check in the booking API that confines the agent to one passenger’s bookings, and the audit record written for every tool call.
  5. Injection drill — turn the special-assistance attack into a game-day scenario. Write down the trajectory you expect the agent to follow, the structural control that stops the attack even if the model is persuaded, the alert that should fire and who it pages, and a plan for adaptive red-teaming, in which a person keeps varying the attack after each defense, because a fixed list of known attacks says little about an attacker who learns.

Rubric:

Criterion The question to ask yourself
Change discipline Could the model change without a commit, and would anyone notice by the next morning?
Grounded evals Do your evaluators come from failures you found by reading traces, or from metrics you imagined beforehand?
Calibrated judgment Do you know each judge’s agreement with humans for each category, and how it compares with the agreement between humans?
Planned degradation On a 30× disruption day with the quota gone, what does a passenger see at 07:41?
Structural authority Could text in a booking field move a passenger, or promise compensation the policy does not give?
Bounded blast radius What is the worst voucher spend an attacker can reach in an hour, and who chose that number?

Where you now stand

You can now operate a component that is fluent and usually right, which is a harder thing to run than one that is merely unreliable. You can ship its prompts and models as releases behind a gate, so that a change to its behavior goes through the same pipeline as a change to code, and a change you did not make is caught by the next morning’s eval run. You can evaluate it with instruments whose own accuracy you have measured, and sample its live conversations as a form of surveillance, in the way medicine keeps watching a drug after its trial, once real patients take it. You can let it step down a degradation ladder chosen in advance, calmly, by people who were not in the middle of an outage. And you can give its agents only the authority that a bad day can afford, so that when one of them is persuaded, the structure around it still says no. Weizenbaum’s secretary trusted a simple program within a few minutes of conversation, and the programs we run today are far more persuasive than ELIZA ever was. Our task is to make trust in them slower to form and better earned. Each chapter so far has taken one part of production engineering and followed it through Encore on its own. Real work does not arrive in chapters. Chapter 9, the capstone, gathers them into a single piece of work, in which the objectives, capacity, runtime, delivery, failure design, data, security and AI practices of this book must hold together on one system at once.

References