Session 6, second half · Week 3 · Thu 22 Oct 2026

Budget it. Map how it breaks.

Tonight you turn Tuesday's levers into two artifacts for your system: a cost & latency budget and a failure-mode map, and you start the runbook you'll finish in Week 5. That's Project 2.

Ehsan Gazar
Production-Ready Systems with LLMs and Agents · session 6 of 8
Tonight builds Project 2

Two tables and a runbook.

0–8What Project 2 isbudget + map + runbook seed
8–15Getting numbers you don't haveestimate, label, correct later
15–40Build the budgetceilings, arithmetic, deadline split
40–70Map the failure modestaxonomy, blast radius, ranking
70–85Start the runbookone entry, done properly
85–90Submit + Week 4 previewagents + security next
By the end of tonight

You'll walk out with…

1A cost & latency budget for your hot path, with real arithmetic behind every number.
2A deadline split: how the latency ceiling is divided across the hops, including headroom.
3A failure-mode map: every component, how it fails, blast radius, mitigation, ranked.
4One real runbook entry a teammate could follow at 3am.
5Project 2, started and defensible, ready to submit Sunday.
What you're actually making

Three artifacts, three different questions.

ArtifactThe question it answersWho reads itWhen it earns its keep
Budgetwhat may this request cost, in money and in milliseconds?you, and whoever owns the P&Ldesign reviews, and the month the invoice triples
Failure-mode maphow does each part break, and what happens to the user?the team, and your reviewerbefore launch, and every time you add a hop
Runbookit is breaking right now, what do I do?whoever is on call, probably not you3am, once, and it pays for all of this
They chain: the budget defines what "broken" means, the map lists the ways, the runbook says what to do about the worst one.
Map the terrain first

Every box on the path can fail.

Walk the request and ask at each hop: what happens if this is slow, wrong, or down? That walk is the failure-mode map.

flowchart LR
  IN["Input"] --> RET["Retrieval"]
  RET --> CTX["Context assembly"]
  CTX --> M{"Model call"}
  M --> VAL["Validate output"]
  VAL --> TOOL["Tool / side effect"]
  TOOL --> OUT["Response"]
  classDef risk fill:#FEE4E2,stroke:#1F2937,color:#0E1726;
  class RET,M,TOOL risk;
        

The red boxes are where it hurts most: retrieval returns nothing, the model times out or hallucinates, the tool fires twice.

Before you budget anything

Budget one request.
The right one.

A budget for "the system" is a budget for nothing. Pick the single request that dominates, write its name at the top of the page, and everything else on the page is about that one path.

If two paths tie

Budget the one a user waits on. Background jobs have a cost problem; interactive requests have a cost problem and a latency problem, so the interactive one teaches you more.

AskWhy it picks the path
Which request runs most?volume multiplies everything, a cheap request run a million times is your invoice
Which one does a human wait on?latency only matters where someone is watching a spinner
Which one touches money or data?blast radius lives here, not in the read-only paths
Usually the same oneif all three point at one request, that is your hot path, stop deliberating
Name it concretely: "user asks a question in the support widget", not "the chat endpoint".
The objection I get every time

"I don't have
the numbers yet."

Nobody does at this stage. A budget built on labelled estimates beats no budget, because the estimate is a claim you can be wrong about, and being provably wrong is how the number improves.

The rule

Every number carries its source. An estimate you can defend is an engineering artifact; an estimate you can't trace is a wish.

RankSourceConfidenceHow to get it in 5 minutes
1Measuredhighone log line from staging, or a single scripted call
2Countedhightokenise your actual prompt, count the tools and the retrieved chunks
3Vendor publishedmediumprice per million tokens, rate limits, documented p50 latency
4Analogousmediuma comparable feature you already run, adjusted for size
5Guessedlowfine, but write (guess) next to it and give it a date to be replaced
Order-of-magnitude right beats decimal-place wrong. You are deciding whether this costs cents or dollars.
Do it on the slide, then do it for yours

One request,
counted out loud.

A support assistant. System prompt and tool schemas are fixed, retrieval pulls four chunks, history is trimmed to the last few turns. Nothing here is exotic; the point is that the total is a sum you can actually write down.

cost = (in × price_in) + (out × price_out), per call, times calls
System + tools1,800 tokens fixed every call, and cacheable
Retrieved context4,000 tokens 4 chunks × ~1,000
History + question1,400 tokens
Input total7,200 tokens → × $3/M = $0.0216
Output350 tokens → × $15/M = $0.0053
One call$0.027
Dominant terminput, 80 percent of it, and 5,800 of those tokens you didn't type
So the first lever iscache the 1,800-token prefix, then shrink the 4,000
The arithmetic chose the lever for you. That is the entire reason to do it before optimising.
Why your bill is 4× your estimate

You budgeted one call.
You shipped nine.

The per-call number is the easy half. What turns $0.027 into a surprise is the multiplier: the router call, the validation re-ask, the retry, the agent loop that runs until it's satisfied.

Budget the request, not the call

Write the expected number of model calls per user action, and the worst case number. If you can't bound the worst case, that's not a budgeting gap, it's a missing loop limit.

AmplifierTypicalWorst caseThe control
Router / classify call1 small call, $0.0005samekeep it on the cheap tier, forever
Main answer call1 × $0.027escalates to big model, $0.13route on need, not by default
Output validation re-ask8 percent of requests2 re-askscap re-asks at 1, then fall back
Transport retry3 percent2 retries, full price eachretry budget, not per-call retries
Agent tool loop2 round-tripsunbounded without a caphard max steps + a spend ceiling per request
Per user action~$0.030~$0.29the two numbers your budget needs
Every context history is re-sent in full on every loop step. An agent's cost is quadratic in steps, not linear.
Where the ceiling actually comes from

A ceiling is a
business number.

"Under two cents" is arbitrary until you connect it to what a user pays you. Work backwards from price and usage, and the ceiling stops being a preference you have to defend in a meeting.

Say it in one sentence

"At our price and our usage, this feature costs X percent of revenue, and I'm holding it under Y." That sentence is what makes you the person who owns this feature.

Price$20 per seat per month
Usage130 assistant questions per seat per month (measured, not hoped)
At $0.030$3.90 per seat → 19.5 percent of revenue
Targetkeep model spend under 10 percent of revenue
So the ceiling is$0.015 typical per question
Which the levers reachprefix cache halves the input bill, and the small-model default covers the rest
Worst case still mattersthe $0.29 tail is fine at 1 percent of traffic and fatal at 20 so alert on the mix, not just the mean
Heavy users are not the average user. If 5 percent of seats send 10× the questions, budget for them separately or cap them explicitly.
Part A · the budget · fill this in

Ceilings, spend, and the levers.

Request
The hot path I'm budgeting:
Cost ceiling
Typical < $… · worst case < $… · because
Latency ceiling
p95 < … ms · first token < … ms
Model calls
Typical per user action · hard max
Dominant cost
The multiplier that dominates: (input / output / calls / retries)
Levers on
cache · route · right-size · stream · batch
Confidence
Measured: · guessed: · replace guesses by
The same template, filled, so you can calibrate

This is the depth
I'm marking against.

Read the "because" line especially. A ceiling without a reason gets negotiated away by the first person who wants a bigger prompt. A ceiling with arithmetic behind it holds.

Note what's missing

No hedging, no "roughly", no ranges wide enough to be unfalsifiable. Where the number is a guess it says so, with a date.

Requestuser asks a question in the support widget
Cost ceiling< $0.015 typical · < $0.29 worst · 10 percent of a $20 seat at 130 questions
Latency ceilingp95 < 2,000 ms · first token < 700 ms
Model calls1 router + 1 answer typical · hard max 6, then the ladder
Dominant costinput tokens, 80 percent · 5,800 of 7,200 are prompt and retrieval
Levers onprefix cache 1h · small model default · 4 chunks max · stream · 400 max output tokens
Confidencetokens counted · usage measured · re-ask rate is a guess, replace after week 1 of traffic
A latency ceiling is not one number

Split the deadline
across the hops.

"p95 under 2 seconds" tells no hop what to do. Divide the ceiling, give every hop its own deadline, and keep headroom back for the fallback ladder, because the slow path is exactly when you need time to degrade.

The headroom rule

Reserve 15 to 25 percent. Spend the whole ceiling on the happy path and your fallback has nowhere to run, so a slow request becomes a failed one.

budget
retrieval 425
model 1100
headroom 430
first token ~1025 ms
retrieval
emb
search
rerank
total
2000 ms ceiling · 1570 ms of work
Per-hop deadlineeach hop is cut at its number, never waited on a timeout is a budget line
Headroom buysone retry, or a drop to the small model, or the cached answer
Streaming buysfelt latency, not real latency first token at 1025, done at 1570
Part B · the map

One row per way it breaks.

Borrow the FMEA idea from hardware: for each component, name the failure, its blast radius, and the mitigation. Sort by how much it would hurt.

ComponentFailureBlast radiusMitigation
Retrievalreturns nothing relevantungrounded answer"no context, say so" path
Modeltimeout / rate-limitrequest hangstimeout, retry, fallback ladder
Modelconfidently wrongbad answer to useroutput validation + evals (wk 5)
Toolretried side effectdouble chargeidempotency key
Four rows is a start, not a map. The next slide is the checklist that gets you to twenty.
The checklist · walk every layer

Nine layers. Steal the ones that apply.

LayerWays it breaksThe one people forget
Inputoversized upload, wrong language, empty question, hostile promptinput that costs money to reject
Retrievalindex stale, embedding model changed, zero hits, wrong tenant's docsa silent tenant leak reads as a good answer
Context assemblywindow overflow, truncation mid-document, ordering breaks the cachetruncation drops the one relevant chunk
Model calltimeout, 429, 5xx, provider outage, deprecation, silent version driftthe same prompt scoring worse after a provider update
Outputinvalid JSON, refusal, truncation at max tokens, hallucinated citationvalid shape, wrong content, which passes every check you have
Toolswrong args, side effect retried, third-party down, slow tool eats the deadlinea tool that succeeds after you already timed out
Agent loopno progress, oscillation, step cap hit mid-task, spend runawaythe loop that "succeeds" having done nothing
Data + privacyPII into logs or prompts, cross-tenant cache hit, retention breachthe cache key that omits tenant id
Cost + capacityquota exhausted, one tenant burns the month, load spike queues foreverthe bug that arrives as an invoice, 30 days late
The category that eats teams

Nothing threw.
Nothing was right.

Traditional systems fail loudly: a stack trace, a 500, a red dashboard. The expensive LLM failures return 200 OK with a confident paragraph. Your map is thin if every row has an exception behind it.

The test

For each row ask: if this happened right now, in production, would anything on a dashboard move? If the answer is no, the mitigation must include building the signal, not just the fix.

Silent failureWhat the user seesThe signal to build
Retrieval returns nothinga fluent, ungrounded answerhits-returned = 0 rate, and citation coverage
Context truncatedan answer missing the key facttruncation counter per request
Cache serving stale answersyesterday's policy, stated confidentlycache age histogram, TTL alarm
Ladder stuck on a low runga worse product, working fineserved-by rung as a first-class metric
Quality drift after a model updateslowly worse answersa small eval set on a schedule
Wrong tenant's contextsomeone else's data, plausibly wordedtenant id asserted at retrieval and at cache read
Half your map should be failures with no exception. If it isn't, you've mapped your web server, not your LLM feature.
Make ranking a measurement, not a mood

Define blast radius
once, use it everywhere.

Everyone's "high impact" means something different, which is why priority arguments go in circles. Write the scale down, score every row against it, and the ordering stops being a matter of opinion.

Two axes people conflate

Reach is how many users. Severity is how bad for each. One user losing money outranks every user seeing a typo, and only an explicit scale gets you that answer reliably.

ScoreBlast radiusExampleReversible?
1Cosmetic, one requestan awkward sentence, a stray markdown characteryes, instantly
2One user, one sessionrequest hangs, they retry and it worksyes
3One tenant, sustainedtheir index is stale, every answer is out of dateyes, with a backfill
4All users, feature downprovider outage with no ladder underneathyes, when they recover
5Money, data, or trustdouble charge, cross-tenant leak, confidently wrong medical or legal answerno, and it may be reportable
Anything scored 5 needs a hard control, not a mitigation. Guardrails, not best efforts.
Rank with three numbers, not a feeling

Likelihood × blast
× how blind you are.

Detectability is the LLM twist on the classic FMEA score. A loud failure you catch in seconds is manageable at almost any severity. A silent one you find in a customer email three weeks later is not.

risk = likelihood × blast × blindness  each 1 to 5

Don't over-model it. The score exists to force the conversation and to sort the table, and the top three rows are the only output that matters.

FailureLikelyBlastBlindScoreVerdict
Cross-tenant cache hit25550hard control, this week
Retrieval returns nothing43448build the signal, then the fix
Confidently wrong answer44348evals in week 5, validation now
Tool retried, double effect25220idempotency key, done, closed
Model timeout4218ladder already covers it
Stray markdown3113accept, write that down
Notice the timeout, the one everybody codes for, ranks near the bottom. Visible failures are the ones you already handle.
Rank by pain, not by order

Plot each failure. Fix the top-right first.

quadrantChart
  title Failure modes by likelihood and blast radius
  x-axis Rare --> Frequent
  y-axis Low blast radius --> High blast radius
  quadrant-1 Fix first
  quadrant-2 Guard it
  quadrant-3 Accept
  quadrant-4 Monitor
  "Model timeout": [0.72, 0.5]
  "Confidently wrong": [0.62, 0.82]
  "Double charge": [0.28, 0.9]
  "Tenant leak": [0.2, 0.95]
  "Retrieval miss": [0.55, 0.42]
  "Stale cache": [0.44, 0.55]
  "Cosmetic glitch": [0.5, 0.16]
        

A cosmetic glitch and a double-charge are not equal. The plot makes the priority argument for you, without a meeting.

Every row needs a verb

Prevent, detect, degrade,
recover, or accept.

Most weak maps have mitigations that are really wishes: "improve the prompt", "monitor closely". Force each row into one of five classes and the vague ones expose themselves immediately.

Accept is a real answer

A documented "we accept this, here's why" is stronger than a mitigation nobody will build. What's not allowed is leaving the cell empty and calling it covered.

ClassWhat it doesExampleCosts you
Preventthe failure cannot happentenant id in the cache key, schema-constrained outputdesign work, up front
Detectyou find out fastzero-hits counter, served-by metric, scheduled evalsa metric and an alert
Degradea worse answer beats nonefallback ladder, cached answer, deterministic rules runga rung to build and to test
Recoverundo or re-driveidempotency key, compensating action, replay from the logstate to keep
Acceptdocumented, not ignoredthe occasional awkward sentencenothing, if you wrote down why
Rule of thumb: score 5 rows get prevent. "Detect" alone is not a plan for money or data.
The map is only useful if it wakes someone

Failure → signal → threshold → page or watch.

A failure mode with no signal is a story. Each row you scored highly should end at a number on a dashboard, and a decision about whether it's worth a phone call at 3am.

flowchart LR
  F["Failure mode
from your map"] --> S["Signal
what moves when it happens"] S --> T["Threshold
the number that means trouble"] T --> D{"Worth waking
a human?"} D -- yes --> P["Page + runbook entry"]:::g D -- no --> W["Dashboard + weekly review"]:::a D -. "no signal exists" .-> B["Build the signal first"]:::w classDef g fill:#D6F5E3,stroke:#1F2937,color:#0E1726; classDef a fill:#DCEBFE,stroke:#1F2937,color:#0E1726; classDef w fill:#FEF3C7,stroke:#1F2937,color:#0E1726;
Only what pages needs a runbook entry. That is what keeps the runbook short enough to stay true.
The runbook · starts tonight

What a teammate follows at 3am.

A runbook entry turns a failure mode into an action. Not "the model is down", but how you'd notice, what you check, what you do, and what stops it happening again. You write one good one tonight; the rest grow from real incidents in Week 5.

Symptom
what the dashboard or user shows when this fires
Check
the two or three things you look at, in order
Act
the mitigation, and how to confirm it worked
Prevent
what turns this incident into a permanent fix
Your first runbook entry

Fill this in.

Failure
(e.g. model latency spike)
Symptom
You notice it because
Check
First look at , then , then
Act
Mitigate by · confirm fixed when
If that fails
Escalate to · the bigger hammer is
Prevent
To stop it recurring:
Write it in the second person, for someone who was not in this room.
Worked example 1 · a loud failure

Latency spike, the one you'll get paged for.

Failure
Model p95 latency spikes above 6s
Symptom
the latency dashboard p95 crosses the alert line for 5 minutes; users report the assistant "hanging"
Check
1. provider status page · 2. tokens-per-request chart, did context bloat? · 3. the retry rate and the breaker state
Act
flip the model router to the small model; confirm p95 back under 2s within 5 min
If that fails
enable degraded mode, serve cached and rules rungs only; post status; the feature stays up, worse
Prevent
add a hard per-request deadline with the fallback ladder underneath; cap retrieved-context tokens at 4 chunks
Worked example 2 · a silent one

The same shape, for a failure nothing throws.

Failure
Retrieval quality collapses after a re-index; answers go fluent and ungrounded
Symptom
zero-hit rate jumps above 5 percent, citation coverage falls below 80 percent; no errors, no 500s, latency looks great
Check
1. index doc count versus yesterday · 2. embedding model version in the ingest job · 3. run 5 golden questions by hand
Act
repoint the retriever at the previous index snapshot; confirm citation coverage back above 90 percent on the golden set
If that fails
turn on the "no context, say so" path so the assistant refuses rather than invents, and tell support it is in reduced mode
Prevent
keep two index versions, gate the swap on a golden-set eval, pin the embedding model version in config
Latency was green through this entire incident. That is why the symptom line names a metric you had to build.
Grade your own entry against these

Six ways a runbook
entry is useless.

Almost every bad entry fails one of these, and all six are fixable in the next twenty minutes. Read the list, then re-read what you wrote.

The only real test

Hand it to someone who didn't build the system. If they have to ask you a single question to proceed, the entry isn't finished.

SmellLooks likeFix
The verb is vague"investigate and resolve"name the command, the dashboard, the switch
Needs the author"ask Sam which flag"write the flag name and where it lives
No confirmationact, then hopestate the number that proves it worked
Diagnosis before mitigationroot cause at step onestop the bleeding first, diagnose after
Unbounded checksnine things to look atthree, in order, most likely first
Never rehearseda switch nobody has flippedflip it once in staging, then it is real
Build it · in order

The drill.

1Budget your hot path. ~10 minCount the tokens, do the arithmetic, set typical and worst, split the deadline across the hops.
2Walk the request, list failures. ~12 minNine layers, one row each: slow, wrong, or down. Aim for fifteen rows before you start editing.
3Score and rank. ~8 minLikelihood, blast radius, blindness. Sort. Give every row a mitigation class.
4Write one runbook entry. ~15 minTake the top row, write symptom, check, act, escalate, prevent. Then read it as a stranger.
Breadth in steps 1 to 3, depth in step 4. One excellent entry teaches you more than six thin ones.
During the build · unstick yourself

The four places people stall.

Stuck onDo this instead
"I don't know the token counts"paste your prompt into any tokeniser, count the retrieved chunks, multiply. Two minutes, and it's a counted number, not a guess.
"My system isn't built yet"budget the design. That's the point: a budget written before the code is a constraint, one written after is an excuse.
"My map only has five rows"go back to the nine layers and do the silent-failure test on each. Thin maps are always missing the quiet failures.
"Everything feels high priority"score all of them 1 to 5 on blast radius alone, then sort. Ties break on blindness. Ranking is a forcing function, not an insight.
Calibrate · the rubric

Actionable vs decorative.

Actionable

Ceilings are real numbers with arithmetic and a source behind them, typical and worst case.

Decorative

"Be fast, be reliable." A ceiling with no unit, no worst case, and no reason it's that number.

Actionable

Failures ranked by blast radius, each with a mitigation class: prevent, detect, degrade, recover, accept.

Decorative

A flat list where a cosmetic glitch sits next to a double-charge, half of them mitigated by "monitor closely".

Actionable

The runbook entry could be executed by someone who didn't build it, and it says how you'd know it worked.

Decorative

A runbook that says "investigate and resolve", or one that only works if you're the person on call.

Before you submit · seven questions

Mark your own work first.

1Does every number in the budget have a source next to it, even if that source is "guess"?
2Is there a worst case, and is it bounded by something in the code rather than by hope?
3Does the latency ceiling split across hops, with headroom left for degrading?
4Are at least half the failures silent ones, with no exception behind them?
5Does every row have a mitigation class, and does every score-5 row say prevent?
6Does the runbook entry name a specific switch and the number that confirms it worked?
7Could a stranger execute it without asking you a question?
How to submit

Project 2, due Sun Aug 2.

The budget table, the failure-mode map (ranked, with mitigation classes), and one complete runbook entry. The runbook is intentionally a seed, you'll grow it in Week 5 when real traces surface new failures.

Include
budget + deadline split + ranked map + one runbook entry, symptom to prevent.
It's fine that
the runbook has one entry. Depth beats breadth here.
Carry forward
the map tells Week 5 exactly what to build evals and alerts for.
Recap · then Week 4

Budgeted, mapped, and one rung of the runbook built.

You know your ceilings and where they came from. You know how each layer breaks, including the failures that throw nothing, ranked by what they'd cost you. And you have a runbook entry a stranger could run.

Tuesday · S7
Agent architecture: tool use, planning loops, memory, single vs multi-agent, and how each pattern breaks.
Tue 4 Aug
Submit Project 2
Budget + failure-mode map + runbook seed, by Sunday.
due Aug 2