Defend a tradeoff
Chapters and challenges so far taught you to build, debug, and design LLM systems. This one trains the meta-skill that separates senior engineers from the rest: taking a real fork in the road — RAG vs fine-tune, build vs buy, agent vs pipeline, sync vs batch, small model vs big — and defending a decision with evidence. Not the tool you like; the call you can put on the record, with the criteria you weighed, the thing you give up, and the signal that would change your mind. A decision you can’t defend is a guess.
Learning objectives
- Turn a hard technical fork into a defensible decision, not a preference.
- Enumerate real options, then score them on explicit, weighted criteria.
- State what you give up and the conditions under which the decision flips.
- Defend five staff-level forks: RAG vs fine-tune, build vs buy, agent vs pipeline, sync vs batch, small+scaffolding vs big model.
- Grade a tradeoff defense the way an interviewer or a design review would.
1 · The decision framework essential
Picture the moment this skill actually gets tested. You're in a design review, or the final round of an interview, and someone leans back and asks: "Why did you pick RAG here and not fine-tuning?" You know the answer is RAG — you've built three of them — so you say "RAG is just better for this." And you feel the room's temperature drop half a degree. You were right, and you still lost the exchange, because you delivered a verdict without a defense. The person across the table can't tell whether you reasoned your way to that answer or just reached for the tool you're most comfortable with. That gap — correct conclusion, invisible reasoning — is exactly what this section is here to close.
Here's the reframe that makes it click: a technical decision is not a fact you look up, it's an argument you construct. Think of a courtroom. A good lawyer doesn't win by asserting the verdict more confidently; they win by walking the jury through the evidence in an order that makes the conclusion feel inevitable. Your job in a design review is identical — you are not defending a preference, you are presenting a case whose conclusion the reviewer can reconstruct for themselves. The verdict ("use RAG") is nearly worthless on its own; the chain that produces it is the whole of the value. This is why two engineers can reach the same answer and one gets promoted for it: the difference is entirely in whether the chain was visible.
So what is that chain? It's a fixed loop of six moves, and the reason we drill the loop instead of memorizing answers is that the answers shift with context while the loop never does. State the goal and hard constraints — the constraints, not your taste, do most of the deciding. Lay out two to four real options, never one option and a strawman. Score each on the same short menu — quality, cost, latency, maintenance, risk — so the comparison stays honest. Then the move almost everyone skips: weight those criteria for this situation, because a chat UI and a nightly batch job care about wildly different things. Decide — and if the pick doesn't fall out of the weighted scores, either your weights are wrong or you're overriding them on a hunch, so fix that before you commit. Finally, name what you traded away and the exact signal that would flip you.
The common mistake worth calling out by name: engineers treat "enumerate the options" and "name the flip condition" as optional politeness and sprint straight to the verdict. Those two bookends are the reasoning. Skip the options and you never actually compared anything — you rationalized a foregone conclusion. Skip the flip condition and you can't prove you'd ever change your mind under new evidence, which quietly exposes the pick as a preference wearing a decision's clothes. Everything below is this one frame, filled in with a different fork each time — so internalize it here and the rest of the page is deliberate repetition.
Every one of the forks below is worked with the same six-step frame. Interviewers and design reviews are really testing whether you have a frame — the specific answer matters less than whether the reasoning is legible. Learn the loop once; apply it to anything.
This is the whole lesson in one picture: the repeatable loop that turns a hard fork into a decision you can defend. Read it left to right — every fork below is just this frame filled in with different options.
- Goal + constraints — start here, not with the tool you like. Write down what success is and the hard limits (a latency SLA, a budget, a compliance rule). The constraints do most of the deciding.
- Enumerate options — list 2–4 real alternatives. One option and a strawman isn't a decision; it's a justification you wrote after the fact.
- Score on criteria — rate each option on the same short list: quality, cost, latency, maintenance, risk. Same axes every time, so the comparison is honest.
- Weight by what matters — the key step. The criteria are not equal for this context: a chat UI weights latency high, a nightly batch job weights it at zero. The weighting is where the real judgement lives.
- Decide — the pick now falls out of the weighted scores. If it doesn't, your weights are wrong or you're overriding them on a hunch — fix that before you commit.
- Name the tradeoff — the step juniors skip: say what you gave up and the specific signal that would flip the decision. Without it you have a preference, not a decision.
In short: Whenever a fork stalls you, drop back to these six boxes. The specific answer matters less than being able to walk the loop out loud — that legibility is the whole skill.
The criteria are almost always the same short list — quality, cost, latency, maintenance/ops, risk, time-to-ship — and the whole skill is in the weighting. The right answer for a nightly batch job is the wrong answer for a chat UI because the weights differ, not because the options changed. Make the weights explicit and the decision defends itself.
2 · Fork — RAG vs fine-tune essential
This is the fork you will be asked to defend more than any other, and it's also the one people get wrong most confidently. The trap is that both options sound like they teach the model your stuff, so the choice feels like a coin flip between two roads to the same place. It isn't. They do fundamentally different jobs, and conflating them is the single most common mistake in this whole topic.
Here's an analogy that keeps them straight. Imagine hiring an expert consultant. There are two ways to make them useful on your account. One: send them to a six-month training program that rewires how they think and speak — afterwards they carry that new skill everywhere, but the day your company's policies change, the training is stale and you'd have to send them back to school. Two: hand them a well-organized binder of your current documents that they consult while they work — they look nothing up from memory, they cite the page, and when a policy changes you just swap a sheet in the binder. Fine-tuning is the training program; it reshapes behavior — style, format, tone, a narrow skill — and bakes it permanently into the model's weights. RAG is the binder; it supplies knowledge at the moment of the question and is only ever as current as the binder you keep.
That's the precise definition to hold onto: fine-tuning changes how the model behaves; RAG changes what the model can see. The reason "just fine-tune it on our docs" is usually the wrong instinct is that facts are exactly the thing you should not weld into weights. Weights are expensive to change (a re-training run), they can't cite where an answer came from, and by the time training finishes they already lag reality. Knowledge that moves — weekly docs, prices, policies, a product catalog — belongs in a binder you can update in seconds, not in a brain you'd have to re-educate. Fine-tuning earns its keep when the thing you want is a consistent form (always answer in this JSON shape, always adopt this house voice) or a compressed skill that a big prompt can't cheaply reproduce.
Keep one more thing in view as you read the table below: these two are not enemies. The mature production answer is frequently both — fine-tune for the form, RAG for the facts — and the whole art of defending this fork is being able to say precisely which of the two you're actually reaching for and why. The scorer that follows exists to turn that judgment into a number you can put on a slide.
Read it as two lanes separated by the dashed line: the top lane is fine-tuning, the bottom lane is RAG. Follow each lane's arrows left to right to see where the knowledge ends up.
- Top lane (fine-tune): your docs become training data that is trained into the model's weights (the purple box). The knowledge is now baked in — the note on the right is the cost: it's frozen at training time, needs a re-train to update, and the model can't say where an answer came from.
- Bottom lane (RAG): your docs live in an index you can update in seconds; at ask time the relevant piece is retrieved into the query context and handed to an unchanged model (green) that reads it and cites it.
- The payoff is the contrast between the two purple/green endpoints: fine-tuning moves knowledge into the weights, RAG keeps it outside in an index. That single difference is why fresh, citable facts point to RAG.
In short: if the answer must be current and sourced, keep knowledge in the index (RAG); if you're changing style or format instead, that's the weights (fine-tune).
Decision: default to RAG. For a docs Q&A bot that must answer from a corpus that changes weekly, RAG puts the knowledge in context at query time; fine-tuning bakes it into weights you'd have to re-train every time the docs change. Fine-tuning changes behavior (style, format, a narrow skill) — it's a poor way to add knowledge. This is the XT4 decision, defended out loud.
| Criterion | Weight | RAG | Fine-tune | Big prompt (long-context) |
|---|---|---|---|---|
| Quality on fresh facts | high | strong (cites sources) | weak (stale at train time) | strong but token-heavy |
| Cost per call | med | moderate (retrieval + context) | low (small tuned model) | high (huge context every call) |
| Time to ship | high | days | weeks (data prep dominates) | hours |
| Maintenance | med | re-index, not re-train | re-tune when base/docs change | trivial |
| Freshness | high | update the index | requires re-training | paste new docs |
The defense: weight freshness and time-to-ship high (the docs change weekly, we need it this sprint) and RAG dominates — it wins the two heaviest criteria and ties on quality. The scorer below makes that ranking concrete rather than asserted.
decide.pydef score_options(options, weights):
"""options: {name: {criterion: 0..5}} (higher = better on that criterion).
weights: {criterion: weight}. Returns options ranked best-first."""
ranked = []
for name, scores in options.items():
total = sum(scores[c] * w for c, w in weights.items())
ranked.append((name, round(total, 1)))
ranked.sort(key=lambda x: x[1], reverse=True)
return ranked
# Fork: RAG vs fine-tune vs long-context prompt for a docs Q&A bot.
weights = {"quality": 3, "cost": 2, "latency": 1, "maintenance": 2, "time_to_ship": 3}
options = {
"RAG": {"quality": 4, "cost": 4, "latency": 3, "maintenance": 4, "time_to_ship": 5},
"Fine-tune": {"quality": 4, "cost": 2, "latency": 5, "maintenance": 1, "time_to_ship": 1},
"Big prompt": {"quality": 3, "cost": 1, "latency": 2, "maintenance": 5, "time_to_ship": 5},
}
for name, total in score_options(options, weights):
print(f"{name:12} {total}")
print("decision:", score_options(options, weights)[0][0])
RAG 46
Big prompt 38
Fine-tune 26
decision: RAG
This tiny program turns "I think RAG is better" into a ranking you can put on a slide. It scores each option against weighted criteria and prints them best-first — the decision becomes arithmetic instead of an assertion. It's pure stdlib, so it runs with a plain python decide.py.
score_options(options, weights)takes two dicts: each option's 0–5 rating on every criterion (higher = better), and how much each criterion weighs in this context.sum(scores[c] * w for c, w in weights.items())is the whole idea — multiply each rating by that criterion's weight and add them up. A great score on a criterion you weighted low barely moves the total; that's intentional.ranked.sort(key=lambda x: x[1], reverse=True)orders the options by weighted total, highest first, so the winner isranked[0].- The
weightshere encode this fork's reality:qualityandtime_to_shipat 3 (we need it this sprint, on fresh docs) outweighlatencyat 1. Change those weights and you change the winner — which is the point.
What the output means: Three lines ranked best-first — RAG 46, Big prompt 38, Fine-tune 26 — then decision: RAG. RAG wins because it takes the two heaviest criteria; the number is the defense.
Try this: Bump latency's weight to 5 and drop time_to_ship to 1 (pretend this is a latency-critical, no-deadline path). Watch Fine-tune climb — proof that the decision lives in the weighting, not the options.
3 · Fork — build vs buy a component intermediate
Every engineer feels the pull to build. Building is the fun part — it's why you got into this — and there's a seductive story that building your own vector DB or eval harness gives you control, saves the subscription fee, and fits your needs exactly. Teams act on that story constantly, sink three months into a homegrown component, and then spend the next two years maintaining a worse version of something they could have rented. If you've watched a "we'll just build a quick internal tool" balloon into a permanent staffing cost, you've lived this fork.
The clarifying question isn't "can we build it?" — of course you can, you're engineers — it's "is this the thing our customers pay us for?" Think of a restaurant. A great restaurant builds its recipes, its dishes, the experience at the table — that's the moat, the reason people come. It does not mill its own flour, generate its own electricity, or manufacture its own dishwashers. Those are real, necessary, and utterly undifferentiated: buying them frees the kitchen to spend every hour on the food. Your product has the same shape. There's a thin layer that is the product — the reason a customer chooses you over a competitor — and a thick layer of plumbing underneath that every company in your space also needs and none of them wins on.
So the precise principle is: build your differentiator, buy the undifferentiated heavy lifting. A managed vector database, a hosted eval tool, an off-the-shelf LLM gateway — these are plumbing. Someone else has already battle-tested them across a thousand customers, they carry the vendor's on-call pager at 3am instead of yours, and they let a small team point its scarcest resource — engineering hours — at the layer no one else can ship. The common error is to price only the visible cost (the vendor's monthly bill) and ignore the invisible one (the salaries, the maintenance, the opportunity cost of every week not spent on the product). When you "buy," you're not just buying software — you're buying focus, and focus is what a small team is actually short on.
Read the table with that lens: the columns that look like "buy loses" (less control, a metered bill) are real costs, but weigh them against the column that decides most cases — whether the component is your moat. It almost never is, which is why the default leans buy and the interesting work is naming the specific conditions that flip it.
Decision: buy the undifferentiated component; build only your moat. For a vector database, an eval harness, or an LLM gateway, the honest question is: is this the thing customers pay us for? Almost never. A managed vector DB, a hosted eval tool, or an off-the-shelf gateway is battle-tested, maintained by someone else, and lets your small team spend its scarce build-hours on the product no one else can ship.
Read it top-to-bottom as a stack: the green band on top is what makes you money; the boxes beneath are the machinery every company in your space also runs.
- The green top band is your differentiator — the thing a customer chooses you for. It is the one layer you should build, because no vendor can ship your moat for you.
- The three blue boxes — vector DB, eval harness, LLM gateway — are undifferentiated plumbing: necessary, but identical across every rival. Each is available managed/hosted/off-the-shelf, so you buy them.
- The arrows down into the amber bar are the payoff: every hour you don't spend rebuilding plumbing becomes an hour spent on the moat. Buying isn't just cheaper software — it's focus.
In short: Build the thin layer that is your product; buy the thick layer that isn't. The default leans buy because the component is almost never your moat.
| Criterion | Build it | Buy it (managed) |
|---|---|---|
| Fits our exact need | perfect | 80–90% (config, not custom) |
| Time to first value | weeks–months | hours |
| Who fixes it at 3am | you | the vendor's on-call |
| Ongoing maintenance | yours forever | theirs |
| Cost at scale | fixed infra + salaries | per-usage (can exceed build) |
| Is it our moat? | only if it's the product | rarely |
The defense: engineering time is the scarcest resource on a small team. Every week spent building a vector DB is a week not spent on the differentiated product. Undifferentiated heavy lifting is precisely what you pay a vendor for — you're buying focus, not just software. This is the same capability-vs-control weighing as PE4's API-vs-self-host call, one layer up the stack.
4 · Fork — single agent vs pipeline of steps advanced
"Agent" has become a status word, and that's the source of the mistake this fork exists to correct. There's an unspoken sense that a fixed pipeline is what juniors build and a self-directing agent is what serious engineers ship — so teams reach for the agent to look advanced, then spend weeks trying to reproduce a bug that only appears on one run in twenty, in a loop that took a different path each time. If you've ever stared at a trace where the model decided to call the same tool four times for reasons you can't reconstruct, you've felt the true cost of choosing an agent where a pipeline would do.
Strip away the branding and the distinction is simple. A pipeline is a recipe: classify, then retrieve, then answer, then validate — fixed steps in a fixed order, the same path every single time. An agent is a person handed a goal and a toolbox and told "figure it out" — it decides its own next move at each turn. The recipe is boring and that is precisely its virtue: you can test each step in isolation, you know exactly which stage failed when something breaks, the cost is bounded because the number of steps is fixed, and it runs the same way today as it did yesterday. The agent buys you one thing the recipe can't offer — the ability to handle situations you didn't foresee — and it charges for that flexibility in every other currency: determinism, token spend, and debuggability all get worse.
So the precise framing is: determinism and flexibility are the two ends of one lever, and you can't have both maxed at once. An agent isn't a "better pipeline"; it's a different tool for a different shape of problem — the open-ended kind whose branches you genuinely cannot enumerate in advance (novel research, multi-step debugging, "do whatever it takes"). The common error is to assume the whole task is open-ended when in truth 90% of it is a knowable recipe with one uncertain step buried inside. The senior move that falls out of this is a hybrid you'll see repeated across production systems: wrap the uncertainty in structure. Run a deterministic pipeline, and where — and only where — the path is truly open, drop in a small, contained agent with bounded steps, a tool allowlist, and a validation gate on the way out. A pipeline around the loop, not a free-running loop pretending to be a system.
Decision: pipeline by default; reach for an agent only where the path is genuinely open-ended. A fixed pipeline (classify → retrieve → answer → validate) is deterministic, cheap, testable step-by-step, and easy to debug — you know exactly which stage failed. An agentic loop that plans its own tool calls buys flexibility for tasks whose shape you can't predict, at the cost of determinism, token spend, and debuggability.
The dashed line splits two ways to run a task: the top row is a fixed pipeline, the bottom is an agent loop. Follow the arrows in each.
- The top row is a pipeline:
classify → retrieve → answer → validate, straight arrows, one direction. Every run takes the identical path, so each box is unit-testable and the total cost is bounded by the fixed number of stages. - The bottom row is an agent: the model and the toolbox point at each other with a two-way arrow — the model picks a tool, sees the result, and picks again, for as many turns as it likes. Nothing fixes the order.
- The two labels are the payoff contrast: green (deterministic, testable, bounded) under the pipeline versus amber (flexible, nondeterministic, unbounded) under the agent. That loop-back arrow is exactly what buys flexibility and costs you reproducibility.
In short: A pipeline is a straight line you can test stage by stage; an agent is a loop that handles the unforeseen but varies its path. Default to the line; add the loop only where the path is truly open.
| Criterion | Weight | Fixed pipeline | Single agent (tool loop) |
|---|---|---|---|
| Determinism | high | fixed path, reproducible | path varies per run |
| Debuggability | high | known failing stage | trace a nondeterministic loop |
| Cost / latency | med | bounded (N steps) | unbounded (loops, retries) |
| Flexibility | med | only the paths you built | handles the unforeseen |
| Eval surface | high | each stage unit-tested | end-to-end only, harder |
The defense: most production tasks have a knowable shape. Weight determinism, debuggability, and eval surface high — as any team running this in production must — and the pipeline wins decisively. "Agent" is not a maturity level; it's a tool for open-ended tasks, and you pay for its flexibility in every dimension a pipeline is strong. When you do need an agent, contain it: bounded steps, a tool allowlist, and a validation stage on the way out — a pipeline wrapped around the loop.
5 · Fork — sync/realtime vs batch professional
This is the fork with free money sitting on the table, and most teams walk past it. Batch APIs typically hand you roughly a 50% discount on tokens — the same model, the same prompt, half the bill — and yet teams pipe enormous overnight jobs through the real-time endpoint at full price out of pure habit, because that's the endpoint they wired up first. On the flip side, the opposite error is just as common and more visible: someone puts a chat feature on a batch queue to save money and now a user is staring at a spinner for ninety seconds. Both mistakes come from not asking the one question that decides this fork.
That question is embarrassingly simple: is a human blocked, right now, waiting on this exact response? Picture a coffee shop. When you order at the counter, you stand there until your drink is made — that's synchronous, and no amount of savings justifies making you wait longer, because your time is the scarce thing. But the beans were roasted last night in a giant batch when nobody was standing around waiting, because roasting one bag at a time on demand would be absurdly wasteful. Same product, two delivery modes, and the deciding factor was never the coffee — it was whether anyone was blocked at that step.
The precise principle: latency and cost-per-token trade against each other, and which one you weight to zero depends entirely on whether someone is waiting. A chat turn weights latency as critical and the token discount as irrelevant — you'd never trade a responsive UI for a coupon. A 50,000-row nightly classification job weights throughput-per-dollar as critical and latency at literally zero — nobody is watching, so the discount is close to free money. The common error is treating this as a technical-capability question ("can we batch this?") when it's really a product question ("is this on a human's critical path?"). Notice the elegance the framework buys you here: the flip condition is baked right into the deciding question, so the moment a human starts waiting on an individual result — even one row inside an otherwise-bulk job — that specific path snaps back to synchronous.
Decision: route by whether a human is blocked on this exact response. If someone is waiting (chat, an API endpoint) it must be synchronous — no discount is worth a user staring at a spinner. If nothing is blocked (overnight classification, backfills, bulk summarization) batch it and take the ~50% token discount. This is the AP1 decision, defended with the gate question.
Read it top-down as a single decision: the purple box at the top is the only question you ask, and the two arrows fork to the two answers.
- The purple gate box holds the deciding question — is a human blocked, waiting right now? Everything hangs on this one product question, not on any technical capability.
- The YES arrow (green) routes to synchronous/realtime: chat or an API endpoint, answered in seconds at full price. Latency is critical here, so the discount is irrelevant.
- The NO arrow (amber) routes to a batch queue: an overnight job or backfill where minutes-to-hours is fine and you collect the ~50% token discount — close to free money because nobody is waiting.
In short: The gate question is the flip condition — the moment a human starts waiting on an individual result, that path snaps back from batch to sync.
| Criterion | Weight (chat) | Weight (bulk) | Sync/realtime | Batch |
|---|---|---|---|---|
| Latency | critical | irrelevant | seconds | minutes–hours |
| Cost per token | low | critical | full price | ~50% off |
| Is a human blocked? | yes | no | required | forbidden |
| Throughput / $ | low | critical | poor | excellent |
The defense: the same model, same prompt, two delivery modes — the decision is entirely about which criterion you weight. A chat turn weights latency critical, so the 50% saving is irrelevant. A 50,000-row nightly job weights throughput-per-dollar critical and latency at zero, so batching is close to free money. Naming the weights is the defense; the flip is built into the question "is anyone blocked?"
6 · Fork — small model + scaffolding vs big model tech-lead
"Just use the biggest model" is the most comfortable sentence in the room, and that comfort is exactly why it deserves scrutiny. It sounds safe, it's easy to defend, and nobody gets fired for picking the frontier model. But at scale it can quietly become the most expensive decision on the team — a per-call price paid millions of times a month, for capability the common cases never actually needed. This is a tech-lead fork because winning it means arguing against the safe-sounding default with something more durable than an opinion.
The insight that reframes it: a big model raw is a brilliant generalist you interrupt for every question, while a small model wrapped in scaffolding is a competent junior with a good reference library and a checklist. Give a cheap, fast model retrieval to ground its facts, a tight prompt to steer it, output validation to catch its slips, and a retry/repair loop to recover from them, and on the common cases it frequently matches the frontier model at a fraction of the cost and latency. The frontier model still wins the genuinely hard reasoning — the long tail — which is why the lead's real answer is almost never "one or the other" but both: the small model handles the easy majority, and the hard minority escalates to the big one.
But here's the part that makes this tech-lead material rather than an architecture preference: the choice is ultimately a break-even calculation, and you must compute it rather than argue it. A big model has near-zero fixed cost but a high price per call; a small tuned or self-hosted model carries a real upfront build cost but cheap calls thereafter. Below some monthly volume the big model is simply, unarguably cheaper — building your own would be premature optimization. Above that volume the per-call savings finally repay the build and the small model pulls ahead. The common mistake is to have this debate as a clash of preferences ("small is elegant" vs "big is safe") when it's a line-crossing problem with an actual number attached. The tool below finds that number, and a lead who states the number — "we're at 200k calls/month, break-even is ~1M, so we ship the big model and revisit at 5x" — ends the argument in a way no amount of conviction can.
Decision: push the cost/quality frontier — a small model with good scaffolding beats a big model raw, until it doesn't. A cheap, fast model wrapped in retrieval, tight prompts, validation, and a retry/repair loop often matches a frontier model on the common cases at a fraction of the cost and latency. The frontier model still wins on the hardest reasoning — so the lead's move is usually both: small model for the easy majority, escalate the hard tail to the big one (the hybrid from PE4).
The build-vs-buy-style flip here is a pure break-even: a big model has near-zero fixed cost but a high per-call price; a small tuned model (or a self-hosted one) carries an upfront build and cheaper calls. Below some volume the big model is simply cheaper; above it, the small one wins. Don't argue it — compute it.
It's a cost-vs-volume chart: the horizontal axis is calls per month, the vertical axis is total cost. Each option is a line; where they cross is the answer.
- The amber line (big model) starts near the origin — almost no fixed cost — but climbs steeply, because you pay a high price on every single call.
- The blue line (small + scaffolding) starts high up the axis — that's the upfront build cost — but rises gently, because each call afterward is cheap.
- The red dot is the break-even volume (~1M calls/month in the lab below). Left of it, amber is lower → the big model is cheaper and building your own would be premature. Right of it, blue is lower → the per-call savings have repaid the build and the small model wins.
In short: The decision is a number, not a preference — find where the lines cross and say "we're below break-even, ship the big model, revisit at 5x volume."
flip.pydef when_finetune_flips(rag_infra_monthly, ft_fixed_cost, ft_infra_monthly,
rag_cost_per_call, ft_cost_per_call, months=12):
"""Big-model/RAG path: low fixed cost, higher per-call. Small-tuned path: big
upfront build, cheaper per-call. Return the monthly volume where the cheap
per-call path wins over the horizon."""
per_call_gap = rag_cost_per_call - ft_cost_per_call # expensive path pays more/call
infra_gap = (rag_infra_monthly - ft_infra_monthly) * months
if per_call_gap <= 0:
return "cheaper-per-call path never wins at these rates"
calls_total = (ft_fixed_cost - infra_gap) / (per_call_gap * months)
if calls_total <= 0:
return "cheaper-per-call path already wins from call 1"
return f"small/tuned model wins above ~{round(calls_total):,} calls/month"
print(when_finetune_flips(
rag_infra_monthly=300, ft_fixed_cost=40_000, ft_infra_monthly=200,
rag_cost_per_call=0.004, ft_cost_per_call=0.0008))
small/tuned model wins above ~1,010,417 calls/month
This helper answers the one question that settles a small-vs-big (or build-vs-buy) cost argument: at what volume does the cheaper-per-call option actually win? One path has a big upfront cost but cheap calls; the other has no upfront cost but pricey calls. It's stdlib — run it with python flip.py.
per_call_gap = rag_cost_per_call - ft_cost_per_callis how much more the expensive-per-call path pays on every single call. This is the money that slowly repays the upfront build.infra_gapcompares the two paths' ongoing monthly infra over the horizon — a credit (or debit) applied before we count calls.- The guard
if per_call_gap <= 0catches the case where the "cheaper" path isn't actually cheaper per call — then it never wins on cost and no volume changes that. calls_total = (ft_fixed_cost - infra_gap) / (per_call_gap * months)is the break-even: the monthly volume at which the per-call savings, over the horizon, finally cover the upfront build.
What the output means: small/tuned model wins above ~1,010,417 calls/month — below ~1M calls/month the big-model/RAG path is cheaper (no build cost); above it, the cheap-per-call path pays for itself. State the number, don't argue the preference.
Try this: Cut ft_fixed_cost to 5000 (a cheap adapter instead of a full build) and re-run. The break-even plummets — showing that the flip point is driven by the upfront cost you're trying to amortize, which is exactly the lever a lead negotiates down.
The defense: at low volume the frontier model is both cheaper (no build cost) and higher quality — start there, ship this week. The break-even (~1M calls/month here) is where the small-model investment pays for itself; below it, building your own is premature optimization. A lead states the number, not a preference, and revisits it as volume grows — the same discipline as PE4's API-vs-self-host break-even.
7 · Tech-lead — defending it in the room tech-lead
You've done all the analysis. Now you have to survive the meeting. This is where good decisions go to die — not because the reasoning was wrong, but because the engineer defended it like their ego was on the line. Someone pokes at the choice, the engineer digs in, the exchange turns into a contest of who's more stubborn, and the room learns nothing except that this person can't be moved by evidence. The skill this final section teaches is the opposite reflex, and it's the one that separates a senior engineer from a tech lead.
Reframe what the meeting is actually for. A design review is not a trial where you're the defendant fighting to keep your decision alive — it's a peer review of your reasoning, and the best outcome is that the reasoning holds up because smart people tried to break it and couldn't. That means your goal is not to be right; it's to make the decision legible and falsifiable — legible so the room can follow the chain that produced it, falsifiable so the room can see exactly what evidence would overturn it. A decision you present as unfalsifiable ("we should just do RAG, it's obviously better") gives the reviewer nothing to check and, paradoxically, is the weakest possible position: it can't be verified, only believed or rejected on faith.
So walk the frame out loud, deliberately, in order: here are the two or three options I weighed; here are the criteria and how I weighted them for this context; the evidence points to X; we are giving up Y; and the signal that would flip me is Z. That last clause does the heaviest lifting and is the one juniors omit. Naming the flip condition is the tell of real reasoning — think of it as showing your work in a way a grader can't fake. Anyone can announce a preference; only someone who genuinely reasoned the decision can state the specific, measurable condition under which they'd cheerfully pick the other option. A decision you would never reverse under any evidence was never a decision at all — it was a choice you made first and defended afterward. The common mistake, then, isn't picking the wrong option; it's treating disagreement as an attack to repel rather than a stress test that, if you pass it, makes your call bulletproof. The rubric below turns that stance into five things a reviewer can actually grade you on.
A lead's job in a design review is not to be right — it's to make the decision legible and falsifiable. Walk the frame out loud: "here are the two or three options I weighed; here are the criteria and how I weighted them for this context; the evidence points to X; we're giving up Y; and the signal that would flip me is Z." That last clause is what separates a defense from a rationalization — a decision you'd never reverse under any evidence was never reasoned, it was chosen.
Read it left to right as the script you say out loud in the meeting — five boxes, each a clause of the defense, in the order you deliver them.
- Options I weighed and Criteria + weights set up the reasoning: two-to-three real alternatives, then the criteria weighted for this context — legibility, so the room can follow you.
- Evidence → pick is the hinge: the choice follows from the weighted evidence rather than being asserted up front.
- What we give up is the honest cost — naming it, not hedging it away, is what makes the defense credible.
- The green final box, marked ★, is the flip condition: a specific, measurable signal that would reverse the call. It's the payoff element — falsifiability — and the single strongest seniority signal in the whole chain.
In short: Options → criteria+weights → evidence → tradeoff → flip condition. Walk all five out loud; the last one is the tell that you reasoned rather than guessed.
| Dimension | Meets bar | Above bar |
|---|---|---|
| Options enumerated | Names 2+ real alternatives, not one and a strawman. | Enumerates the true option space and says why the losers lost. |
| Criteria explicit & weighted | Lists the criteria (cost/latency/quality/maintenance/risk). | Weights them for this context and justifies the weighting. |
| Decision follows from evidence | The pick is consistent with the criteria. | The pick is derived from the weighted scores — reproducible, not asserted. |
| Tradeoff acknowledged | States at least one thing given up. | Names the full cost of the choice honestly, without hedging it away. |
| Knows when it flips | Can say a condition that would change the decision. | Names a specific, measurable flip condition (a volume, a latency SLA, a scale). |
Score each dimension Meets or Above. All five at least Meets = a defense that survives a design review. Any dimension you can't hit means you have a preference, not a decision — go back to the frame in §1 and fill the gap before you commit.
Your teammate says "let's fine-tune a model on our docs so it just knows the answers." The docs change weekly. In one sentence, defend the counter-decision.
Show answer
A reviewer accepts your "use a fixed pipeline, not an agent" decision but asks: "when would you change your mind?" Why is having a crisp answer more important than the answer itself?
Show answer
Exercise AC5.1 — Defend a real fork
Context: A defensible decision beats a confident opinion. Running the §1 framework on a real fork — goal, options, weighted criteria, and a measurable flip condition — is what turns ‘I prefer’ into ‘we decided.’
Your task: Take a fork you're facing (or one of the five) and produce a one-paragraph defense using the framework, then grade it against the rubric.
Requirements:
- Write the goal + constraints and enumerate 2–3 real options
- Score the options on weighted criteria with
decide.py - Where cost is the axis, find the break-even with
flip.py - Write the defense: decision, evidence, what you give up, and the measurable condition that flips it
- Grade against the rubric — if any dimension isn't at least ‘Meets’, you have a preference, not a decision
💡 Hint: The measurable flip condition is the part that makes it a decision — if you can't name what would change your mind, you haven't decided yet.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: ‘I think we should use RAG’ is a preference; a decision is falsifiable. The difference is whether anyone can check the constraint and the reversal condition.
Your task: Rewrite ‘I think we should use RAG’ into a defensible decision statement and say what it must contain.
Requirements:
- State the four required parts: the choice, the driving constraint, the rejected alternative, and the reversal condition
- Anchor the choice on a binding constraint (e.g. weekly-changing knowledge that must cite sources)
- Explicitly reject the alternative on that constraint (fine-tuning can't update fast or cite)
- Make it falsifiable — anyone can check whether the constraint holds and whether the reversal condition has been met
💡 Hint: The reversal condition is what turns an opinion into a decision: name the measurable thing that would make you switch.
Show solution
A defensible decision names the choice, the driving constraint, the rejected alternative, and the reversal condition.
Rewrite: ‘We will use RAG rather than fine-tuning because our knowledge changes weekly and must cite sources (the binding constraint); fine-tuning cannot update fast enough or cite. We will revisit if freshness stops mattering and per-query latency becomes the dominant cost.’
‘I think’ is a preference; the rewrite is a decision because it is falsifiable — anyone can check whether the constraint holds and whether the reversal condition has been met.
Context: RAG vs fine-tuning is the most common architecture fork, and it's usually settled by one question about what you're actually changing — knowledge or behavior.
Your task: Give the tradeoff table that decides RAG vs fine-tuning and the one question that usually settles it.
Requirements:
- Compare on the load-bearing dimensions: knowledge freshness, citations/provenance, behavior/format/tone, and per-query cost/latency
- Show RAG wins on freshness and citations; fine-tune wins on behavior/format and per-query cost
- State the settling question: are we changing what the model knows or how it behaves?
- Note they compose — fine-tune for form, RAG for facts
💡 Hint: Map the axis to the choice: changing knowledge points to RAG, changing behavior points to fine-tune.
Show solution
| Dimension | RAG | Fine-tune |
|---|---|---|
| Knowledge freshness | Update the index instantly | Retrain to update |
| Citations / provenance | Natural (returns sources) | None — baked into weights |
| Behavior / format / tone | Weak lever | Strong lever |
| Per-query cost/latency | Higher (retrieval + long context) | Lower (no retrieval) |
The settling question: ‘Are we changing what the model knows or how it behaves?’ Knowledge that changes → RAG. Fixed behavior/format/style → fine-tune. They also compose: fine-tune for form, RAG for facts.
Context: Single agent vs fixed pipeline is a reliability-vs-flexibility fork. Most problems labelled ‘agent’ are really pipelines with one uncertain step, so you should be able to defend both directions.
Your task: Say when you replace a flexible single agent with a fixed pipeline of steps, and defend both directions.
Requirements:
- Argue the pipeline case: well-understood stable tasks — testable, cheaper, lower-latency, predictable, per-step evals
- Argue the agent case: open-ended or per-request-varying paths you can't pre-wire
- Name the tradeoff explicitly (flexibility for reliability/observability)
- State the default: start with a pipeline and introduce agentic freedom only at the steps that genuinely need it
💡 Hint: Favor the pipeline when the steps are known and ordered; reach for the agent only where the next step depends on intermediate findings.
Show solution
Pipeline wins when the task is well-understood and stable: each step is testable, cheaper, lower-latency, and predictable. You trade flexibility for reliability and observability — you know exactly where it failed.
Agent wins when the path is open-ended or varies per request: you cannot pre-wire every branch, so you pay for flexibility with unpredictability and harder debugging.
| Favor pipeline when | Favor agent when |
|---|---|
| Steps are known & ordered | Steps depend on intermediate findings |
| Reliability & cost matter most | Coverage of long-tail requests matters most |
| You need per-step evals | The task space is too large to enumerate |
Default: start with a pipeline; introduce agentic freedom only at the steps that genuinely need it. Most ‘agent’ problems are pipelines with one uncertain step.
Context: Defending a decision under a pointed performance attack is a real skill: you concede the true cost, then re-anchor on the dimension the decision actually rests on — without getting defensive.
Your task: Defend a RAG choice on the merits when a senior engineer says ‘your RAG adds 200 ms of retrieval latency for nothing — just fine-tune.’
Requirements:
- Concede the real cost honestly (retrieval adds latency, and name the mitigations: caching, streaming)
- Re-anchor on the binding constraint the decision rests on (freshness + citations), not the attacked dimension (latency)
- Show you considered the alternative by stating the reversal condition (you'd switch if freshness stopped mattering)
- Separate the tunable dimension they attacked from the binding one that drove the choice
💡 Hint: You win by making the binding constraint explicit, not by out-arguing them on latency.
Show solution
Acknowledge the true part, then re-anchor on the binding constraint:
- Concede the real cost: ‘You're right that retrieval adds latency — ~200 ms, and we can cut it with caching and streaming.’
- Re-anchor: ‘But the reason we're not fine-tuning is freshness and citations, not latency. Our KB changes weekly and legal requires source links. A fine-tune is stale the day after training and can't cite.’
- Show you considered it: ‘If freshness stopped mattering, I'd switch — that's the reversal condition I wrote down.’
The move: separate the dimension they attacked (latency, tunable) from the dimension the decision actually rests on (freshness, binding). You win by making the constraint explicit, not by defending latency.
Context: Sync vs batch is decided by one question — is a human blocked waiting? — but the answer that usually wins in production is a hybrid that gets batch economics and realtime freshness.
Your task: Choose sync per-request inference vs nightly batch for a scoring feature and give the hybrid that often wins.
Requirements:
- State the decision driver: does the consumer need the answer at request time, and how fresh must it be?
- Contrast the two (sync: user waits, seconds-fresh, per-request cost; batch: consumed later, hours-fresh fine, amortized cost)
- Describe the winning hybrid: pre-compute the predictable bulk in batch, serve from cache, fall back to sync only on cache-miss
- Name the cost of the hybrid (two code paths) so the tradeoff is honest
💡 Hint: Pre-compute the 90% you can predict overnight and reserve sync inference for the new or changed entities.
Show solution
Decision driver: does the consumer need the answer at request time, and how fresh must it be?
| Sync/realtime | Batch |
|---|---|
| User waits on the result now | Result consumed later / in bulk |
| Freshness = seconds | Freshness = hours/day is fine |
| Pay per-request cost & latency | Amortize cost, batch discounts, no latency SLA |
The hybrid that usually wins: pre-compute in batch for the predictable bulk (all known users overnight), serve those instantly from a cache, and fall back to sync inference only for cache-misses (new/changed entities). You get batch economics for 90% of traffic and realtime freshness where it matters — the best of both, at the cost of two code paths.
Context: ‘Just use the biggest model’ is the safe-sounding default; defending a smaller model plus scaffolding requires a decision doc that proposes the experiment which settles it, not an argument.
Your task: Defend a small-model-plus-scaffolding design against ‘just use the biggest model’ with a decision doc: claim, evidence, tradeoffs, and how you'd prove it.
Requirements:
- State the claim: a small/mid model wrapped in retrieval + validation + a router that escalates only hard queries beats big-model-everywhere on cost and reliability at equal quality
- Give a comparison across cost/request, latency, factuality, failure modes, and eng complexity
- Concede the real cost (more moving parts to maintain) rather than hiding it
- Propose to prove it: build both, run the golden eval, commit only if quality matches at materially lower cost
- Set a reversal condition (if the big model's price drops below the scaffolding's blended cost, collapse to the simple design)
💡 Hint: Defend a tradeoff by proposing the experiment that settles it — ask for the week to run the eval, and let the numbers, not the debate, decide.
Show solution
Claim: a small/mid model wrapped in scaffolding (retrieval for facts, structured-output validation, a router that escalates only hard queries to the big model) beats ‘big model everywhere’ on cost and reliability at equal quality for our task.
| Dimension | Big model everywhere | Small + scaffolding |
|---|---|---|
| Cost / request | High, on every call | Low; big model only on escalation |
| Latency | Higher | Lower for the common path |
| Factuality | Priors, can hallucinate | Grounded in retrieval + validated schema |
| Failure modes | Opaque | Localized to a component you can test |
| Eng complexity | Low | Higher (more moving parts) |
How to prove it (not argue it): build both, run the golden eval, and compare quality, cost/request, and p95 latency. Commit to the scaffolding only if it matches the big model's quality at materially lower cost. Concede complexity is a real cost — scaffolding is more to maintain — and set the reversal condition: if the big model's price drops below the scaffolding's blended cost, collapse to the simple design.
Defense in the room: ‘Biggest model is the safe default and I'm not against it — I'm asking for one week to run the eval. If scaffolding doesn't win on the numbers, we ship the big model and I'll have proven it.’ You defend a tradeoff by proposing the experiment that settles it.
✓ Checkpoint — you can move on when you can…
- Run the six-step framework: goal+constraints → options → criteria → weights → decide → name the tradeoff.
- Defend RAG-by-default and say the four conditions under which fine-tune wins.
- Argue buy-the-undifferentiated-component, and when the answer flips to build.
- Justify pipeline-over-agent on determinism/debuggability, and when to reach for an agent.
- Route sync vs batch from "is a human blocked?" and compute a small-vs-big break-even.
- Grade a defense on all five rubric dimensions — including a specific, measurable flip condition.