Context EngineeringYou Program the Window, Not the Model
The shape of the whole thing
Five stages, from a rule that was "in the prompt" to the questions a principal asks before the overflow becomes an incident. Read it top to bottom, or jump straight to the part you came for.
A buried rule — and the reframe: the window, not the prompt, is the program.
Five components, five owners, five distinct ways to fail — and one sum nobody watches.
Relevance-per-token over raw recall — and why where a fact sits decides whether it's used.
Prompts versioned like code, four quiet production failures, and what the companies actually assemble.
Judgment — who owns the window as a whole, and what to ask your team.
Introduction
The rule was in the prompt. I have to start there, because this is the failure that feels personal in a way the last chapter's didn't.
In Chapter 1 the model lied and nothing threw — but at least the lie was the model's. This time you did everything right.
You wrote the instruction — never quote a price; route pricing questions to a human — in plain English, near the top of the system message the user never sees. You tested it. It held.
Then one Tuesday the same support assistant from Chapter 1 quoted a price. To a real customer. In a complete, confident sentence.
You went looking for the bug, and the instruction was still there. Nobody had deleted it. It sat exactly where you left it, word for word.
But it was buried now — dropped into the middle of nine thousand tokens of retrieved policy, account history, and a conversation that had run long.
The model had read it the way you read the middle clause of a terms-of-service page.
Technically present. Functionally invisible.
The rule didn't fail. The placement of the rule failed — and placement was a thing you didn't know you were responsible for, because in the demo there was nothing to place it among.
A scarce shared buffer whose most important item has the weakest claim on the space. Find the buffer in your own system where the thing that matters most is the first thing crowded out when something else grows.
Here is the shift this chapter runs on, and it reorganizes how you build for the rest of the atlas: you are not writing a prompt. You are assembling a window, and the window — not the prompt — is the program.
Chapter 1 left the model as a fixed, read-only dependency. You can't change its weights. You can't teach it anything between calls. You get it exactly as the provider ships it.
So the only state your program actually controls is what you put into the context window on each call. That is the whole mutable surface of the system — the one lever with your hand on it.
"Prompt engineering" is the name we gave this back when the window held one thing: the text you typed. It's a bad name now. It makes the job sound like word choice, when the job is deciding what goes into a scarce, priced buffer, in what order, out of far more material than will ever fit.
The reason experienced engineers walk straight into this is that the word "prompt" points at the wrong object. You picture the string you wrote. In production, that string is the smallest and last-arriving component of a window that also holds retrieved documents, tool outputs, and a history that grows every turn — most of it assembled by code you never thought of as "the prompt."
GitHub has described publicly how its own Copilot works: it doesn't send the model the line you're on, it sends an assembled context built from your open files and the code around your cursor. The prompt you wrote is a tenant in that window, not the landlord — and the bugs live in the parts you didn't type.
Here's the ground we'll walk together:
- Why the context window, not the prompt, is the thing you actually program — and why "prompt engineering" is a name that hides ninety percent of the job
- The anatomy of an assembled window: the five components that compete for it, who owns each, and the distinct way each one fails
- The token budget you're really managing — why relevance-per-token, not raw recall, is the number to drive, and why a bigger window doesn't save you
- Why position is an engineering decision: the model reads unevenly, so where you put a fact decides whether it gets used
- The scenarios you'll actually meet in production, and the move that handles each — including why prompts have to be versioned like the code they already are
None of this is exotic. It's inventory management under a hard cap — a discipline you already own. You've just never had to apply it to something that arrived looking like a text box.
The Window Is the Program
Start with the move that does the most work, because everything else is a consequence of it: stop thinking about the prompt as text you write, and start thinking about the window as state you assemble.
The model is frozen. The window is the only thing that changes between one call and the next. So the window is your program's entire mutable state — and the code that builds it, not the sentence you typed, is where the behavior actually lives.
Teams arrive at that truth by failing past two designs first. Both are reasonable. Both are instructive.
The god-prompt. Everything hardcoded into one enormous system message — every rule, every example, every edge case the team ever hit — shipped identical on every request.
It's appealing for real reasons: one file, in version control, utterly predictable, and it demos beautifully because your demo queries look like the examples you baked in.
It breaks because it can't adapt to the request. The same three thousand tokens of instructions ride along whether the user is asking about refunds or resetting a password, so the one rule that governs this request is drowned among ninety-nine that don't. It works right up until a real query wanders off the examples — which production supplies on day one.
The f-string. The natural next step: build the prompt at runtime by concatenating whatever you've got — system text, retrieved docs, history, the user turn — into one big string, and send it.
It's the smallest possible diff from the demo and takes an afternoon. That's the whole appeal.
It breaks because a concatenation has no budget, no ranking, and no order beyond the accident of what your code appended first. When the pieces get big, the string overflows the cap and the tail drops silently. When retrieval returns junk, the junk goes in at exactly the same standing as the signal. It behaves perfectly on your laptop, where every piece is small and every test document is relevant — and falls apart the first time a real conversation runs long.
identical on every request · the tail — the user's question — spills past the cap and drops silently
decided per request · it fits — and the question is pinned at the end, never in the drop zone
Both designs share one disease: nothing in the system is responsible for deciding what belongs in the window for this request. Name that responsibility, give it an owner, and you've built a context assembler. The rest of the chapter is what that assembler has to decide.
"Prompt engineering" sounds like writing; it's closer to packing a suitcase with a weight limit. The craft isn't the words. It's deciding what makes the trip.
Anatomy of an Assembled Window
An assembled window has five components. Each has an owner, a share of the budget, and its own way of failing — and naming them turns "the prompt is bad" into a diagnosis you can act on.
- System instructions — the standing rules and the persona, the small stable core the team owns. Modest budget. They fail by being ignored: buried mid-window or truncated off the top when something upstream grows, still technically present and functionally gone. This is the refund-price bug.
- Retrieved facts — the grounding pulled per request: the documents, records, and snippets that make the answer about this situation instead of the average of the training data. This is RAG's output (Chapter 3), owned by the retrieval pipeline. It's usually the largest and most volatile slice of the window, and it fails by returning noise — plausibly-related chunks that crowd out the few that matter.
- Tool results — the outputs of functions or APIs the model asked to call: an account lookup, a search, a calculation (the single-call mechanics are Chapter 5). Owned by the tool layer. They fail by being stale, or by being enormous — a two-thousand-row query dumped in raw is a component that ate the budget and taught the model almost nothing per token.
- Conversation history — everything said so far, the working memory of the interaction, owned by the session or memory layer (Chapter 4). It's the component that grows without bound, one turn at a time, and it fails two ways: by evicting the turn that mattered, or by quietly filling the whole window until nothing new fits.
- The user turn — the actual request, the reason the call exists. The user owns it, it's usually tiny, and it fails in the cruelest way available: it arrives last, so it's first against the wall when the window overflows. The most important token in the buffer routinely has the weakest claim on the space.
Now watch one request get assembled, because the order of operations is the whole point.
The user asks a question. The assembler pulls their account tier and recent tickets, asks the retriever for relevant policy, appends the history, prepends the system rules, and then — the load-bearing step — ranks, orders, and truncates all of it to fit the budget before a single token reaches the model.
By the time the model runs, the quality of its answer is mostly already decided: by what made the cut, and where it landed. The model is the last and least interesting thing that happens to the request.
The window is a suitcase with a weight limit, packed by five people who don't talk to each other. The model wears whatever's inside when it walks out the door.
The Budget Is the Whole Game
The window is a fixed buffer, measured in tokens, and every component competes for it. The budget is fixed. The demands are not, and most of them grow.
Put real numbers on it, because adjectives hide the problem. Say your working budget is 10,000 tokens. (Illustrative — the real ceilings move every quarter and belong in a dated reference, not in your head.)
- System instructions: 800.
- Twenty retrieved policy chunks at ~250 tokens each: 5,000.
- One tool result: 600.
- Ten turns of history at ~300 tokens each: 3,000.
- The user's actual question: 40.
That's 9,440 of 10,000 spent before the model writes a word of the answer — and not one line of it looks wasteful. Every component was reasonable on its own.
Now something has to give, and the only question that matters is whether you decide what gives, or the runtime decides for you.
Left alone, the default is brutal and dumb: the text overflows the cap and the end gets cut. The end is usually the user's question, or the room the answer needed. The failure isn't "we ran out of space" — everything runs out of space eventually. The failure is that running out of space silently deleted the most important thing in the buffer, because it happened to be last, and nobody chose that.
Here's the target that replaces "fit more in": relevance-per-token. Not how much relevant material you can cram in — the density of signal per token you spend.
The instinct carried over from search is recall: fetch everything that might help, you can always ignore the extra. That instinct is wrong here, and expensively so.
In a search results page, a bad candidate gets ranked to the bottom and no human ever sees it. In a context window, a bad chunk gets read — the model consumes every token you send and weighs it. So over-retrieval doesn't just waste budget. It actively degrades the answer, because the three chunks that hold it are now diluted among seventeen that merely mention the topic.
Which is why the reflex fix — get a bigger window — misreads the problem.
A bigger window raises the weight limit on the suitcase. It does not decide what to pack. Give a team more room with no budget discipline and they arrive with more things they didn't need and still forget the charger.
The window has been getting bigger for years, and "just put everything in the context" has been wrong the whole time, for a reason that survives every increase: the buffer is also metered. Every token you add costs money and latency (Chapter 8 prices this properly). An irrelevant chunk bills you twice — once to send it, once in the worse answer it produced. More room to overspend is not a fix for overspending.
A bigger context window is a bigger suitcase, not a decision. The scarce resource was never space; it was the model's attention, and you spend that on every token whether the token earned it or not.
Where You Put It Matters
Even once you've chosen what goes in the window, you still have to decide where — and where turns out to be load-bearing, not cosmetic.
Models read unevenly. The finding named lost in the middle (Liu et al., 2023) is blunt about it: a model uses information at the beginning and the end of a long window far more reliably than information buried in the middle, and the gap is wide enough to change answers.
Put a critical instruction or the key retrieved fact in the middle of a long context, and the model often behaves as if it isn't there.
So placement is a control you own. Put the load-bearing things where the model actually looks — the edges — and stop trusting that "it's in the prompt" means "it will be used."
The model reads a long window the way you read a long contract: the opening carefully, the end because that's where the signature is, and the middle not at all. That is the entire mechanism behind the price the assistant quoted. The rule was in the prompt. It just wasn't anywhere the model was looking.
Then there's what happens at the very edge of the budget. When the assembled window is over the cap, something gets evicted — and you choose whether that's a policy or an accident.
The accidental policy is "drop whatever's last," and it's the worst one available, because last is usually the user's question.
The deliberate version is graceful degradation, which you already apply to any overloaded system: when you're over budget, shed the lowest-value context first — the oldest history turn, the twelfth retrieved chunk, the tool output you can summarize — and never the request itself.
You are priority-queuing tokens. It's traffic tiering under load, pointed at a buffer you forgot was a queue. The window was a resource-allocation problem the whole time; the only question is whether you're doing the allocating, or letting a blind truncation do it for you.
The model's answer is mostly written before the model runs. Selection decides what it can possibly say; position decides what it actually notices; truncation decides what you throw away. Three decisions you make, upstream of the one you were staring at.
Prompts Are Code
There's one more decision, and it's the one that separates a system you can operate from a pile of f-strings you can only pray at.
The god-prompt and the f-string both smear instructions and data into one undifferentiated string. Pull them apart.
Instructions are code. They get templates, version control, and a diff on every change. Data — retrieved documents, tool output, the user's text — is a parameter you inject into named slots in that template.
The immediate payoff is maintainability. You can review a template and diff it across releases. An f-string assembled at runtime has no version, no history, and nothing to test — which is exactly why quality regresses through it without a trace.
A prompt you can't diff is a production dependency with no change history. You have one. You just called it a string so you wouldn't have to test it.
Separating instruction from data also hands you a trade-off worth naming.
A fixed prefix is cacheable, predictable, and testable — and a stable prefix is cheap to reuse at the hardware layer (the KV-cache economics belong to the GPU atlas; Chapter 8 spends them). A freshly assembled per-request window is more relevant, but a moving target: harder to version, harder to test, more expensive to serve.
Most systems want both — a stable, versioned instruction core with a dynamic data body. The fixed part earns the caching; the moving part earns the relevance.
And keeping the seam clean buys you something you'll be grateful for in Chapter 9. The moment you stop distinguishing "instructions I wrote" from "text a document or a user supplied," you've removed the only boundary between your system and prompt injection — untrusted text sitting in the same buffer as your trusted commands, read by the same eager reader. You keep the seam here for maintainability. You get the security boundary for free.
A prompt is not a string, it's a versioned artifact with behavior. Treat it like config you'd never ship unversioned — because that's what it is: the config that decides what your most expensive dependency does.
The Scenarios You'll Actually Meet
Here's the field guide — the failures the "it's just a prompt" model actually produces, each with its trigger, how it shows up, and the move that handles it. Every one is quiet, which is the theme of this atlas: nothing throws.
The health signal that catches most of them before a customer does is one almost nobody watches — window occupancy, how full the window runs per request, as a distribution. When p95 occupancy creeps toward the cap, you're one long conversation away from silent truncation on live traffic. It's the "disk at ninety percent" alarm for a disk you didn't know you had.
- You'll see
- Sporadic nonsense on exactly your longest, most engaged sessions — a clean
200, no error anywhere, and the model apparently answering a different question than the one asked. - Why it hides
- Your dev and test conversations are short, so the tail never falls off on your machine. It only overflows once a real session runs long, which is never on the laptop.
- The move
- Budget the window explicitly and truncate by priority — drop the oldest history, never the question — and alarm on occupancy before the cap. This is Chapter 1's wrongness axis, now self-inflicted: you deleted the end of the question, and the model gamely finished the stub.
- You'll see
- Quality measurably worse on the topics where you have the most loosely-related documents — which feels backwards until you see why.
- Why it hides
- Your retrieval metrics look great, because recall is high and recall is what you optimized. The dashboard is green on the number that isn't the problem.
- The move
- Optimize relevance-per-token, not recall — rerank and trim to the few chunks that bear on the question, and forward the heavy version of this to Chapter 3. More context was the problem, not the fix.
- You'll see
- A guardrail that is demonstrably in the prompt, violated intermittently — obeyed in the demo, ignored on long real conversations.
- Why it hides
- Code review passes, because the rule is right there in the source. Whether it's obeyed depends on where it landed in the window this particular call, and that's not in the diff.
- The move
- Pin load-bearing instructions to the edges of the window, and test them on realistic long contexts, not short ones. A guardrail whose enforcement depends on the conversation staying short is theater.
- You'll see
- Quality drifting down over a week, with no deploy that explains it, no model change, no diff to point at.
- Why it hides
- The prompt wasn't in the change log, because it wasn't treated as code. Someone tightened a phrase or reordered a section, and there's no record it happened.
- The move
- Prompt-as-code, taken literally — templates in version control, a diff on every change, and an eval that runs on the diff before it ships (Chapter 6).
One discipline sits under all four, and it's the cheapest insurance in context engineering: log the fully assembled window — the exact tokens that went to the model — not the template you think went, and not just the completion.
The single most common context bug is that the template says X and the window didn't, and those two are indistinguishable after the fact unless you kept the real thing. The tools that make this boring — prompt registries, eval harnesses — are worth buying, and worthless bought before the discipline they enforce. The flight recorder is Chapter 10; the habit starts now.
What the Companies Actually Assemble
GitHub Copilot is context engineering wearing a code-completion costume.
GitHub has described, in its own engineering writing, how each suggestion's context gets built — and it's emphatically not "the line you're on." The assembler pulls from the current file, from snippets in neighboring open tabs chosen by similarity to what you're working on, and from the code on both sides of the cursor, then ranks and trims all of it to a fixed token budget before the model runs.
The lesson worth stealing is where the intelligence lives. When Copilot feels uncanny, it's very often the retrieval and ranking that's uncanny, not the generation. The effort went into deciding what to put in the window, because that's the step that sets the ceiling on how good the completion can be. The model is a commodity they rent; the assembler is the product they built.
The support assistant shows the same discipline producing its own bug.
Reuse our house example from Chapter 1. Its assembler injects the customer's account tier and last three tickets ahead of the free-text question — a good instinct, because grounding the answer in who's actually asking is what turns a generic reply into a useful one.
But grounding and budget are in tension. When a release grew the retrieved-policy section, the extra tokens pushed the user's question down and, on long sessions, into the truncation zone. The assistant started answering a question no one asked, fluently.
The instinct wasn't wrong. It was unbudgeted. Adding useful context evicted more useful context — and nobody owned the total.
And sometimes the right answer is less assembly, not more.
For a narrow, high-stakes task with a stable shape — a compliance check, a fixed extraction — a static, heavily-tested prompt often beats a clever per-request window. Precisely because you can version it, eval it, and cache it, and because there's no assembly step to silently regress.
The assembler is a real system, and every real system has a floor below which its own machinery costs more than it returns. Reach for assembly when the right answer depends on something you can't know until the request arrives. Below that line, a constant string isn't primitive — it's operable, and operable is a feature.
The Principal Engineer's View — Owning the Window
When this earns its complexity, and when it's overkill. A static prompt is not a beginner's move — it's the correct architecture right until the answer needs information you can't know when you write it.
The moment the right response depends on this user, this document, this account, you need assembly, and no amount of wordsmithing a constant gets you there. Below that line, a per-request assembler is over-engineering — a ranking pipeline standing in for a string.
The dial is the error tolerance axis from the Machine Learning Systems atlas: how much machinery a call earns is a function of what a wrong window costs. A high-blast-radius call earns a carefully budgeted, evaluated, monitored assembler; a draft a human will read earns a fixed prompt and your afternoon back.
The ownership problem is the one that actually bites. The window has five components and, in most orgs, five owners — platform writes the system instructions, retrieval owns the chunks, the tools team owns their outputs, product sets the history policy — and no one owns the sum.
Every team's slice was individually reasonable. Their total overflowed the budget and truncated the customer's question.
That's the refund-price incident's real root cause, and it's organizational, not technical. An assembled window with no single owner is owned, in practice, by whichever team's code appended last. Someone has to own the window as a whole — its budget, its order, its truncation policy — the way someone owns a latency budget. Until they do, the overflow is nobody's bug and everybody's incident.
The observability that has to exist first. You cannot debug a window you didn't keep.
Log the fully assembled context — the exact tokens that went to the model, not the template you believe went — joined to the completion and the outcome (the flight recorder is Chapter 10). The most common context bug is that the template says X and the window didn't, and you can't tell those apart unless you captured the real thing. Otherwise you'll spend an incident staring at a template, swearing the rule is right there, and being completely correct — about the template. The window is what shipped, and you threw it away.
Cost, and the rare case where it aligns with quality. The window is the cost (Chapter 8). Every token is billed, and every irrelevant token is billed in exchange for a worse answer.
That makes relevance-per-token a cost lever and a quality lever at once — trimming junk chunks cuts the bill and raises quality. Most trade-offs in this atlas make you spend one thing to buy another. This one occasionally hands you both, because the waste was never buying you anything.
Questions to take back to your team:
- Who owns the assembled window as a whole — its budget, order, and truncation policy? If it's "each team owns their slice," no one owns the overflow, and the overflow is where the incident is.
- When the window exceeds budget, what gets dropped — and did we choose that, or is it whatever landed last? If we can't name the eviction policy, we have one anyway, and it's the worst one.
- Where does our most important instruction actually sit on a long conversation — the edge, or the middle? Have we checked on real traffic, or are we hoping "it's in the prompt" is enough?
- Are our prompts in version control, with a diff and an eval on every change? If not, a one-character edit is an unlogged production deploy, and we'll debug it as if the model moved.
- Do we log the fully assembled window, or the template we think we sent? If it's the template, our incident reviews are reconstructions, not recordings.
You don't write a prompt, you assemble a window — the entire mutable state of a system whose model is frozen. It's a suitcase with a weight limit, packed each request by five owners who don't coordinate, and its most important item — the user's own question — has the weakest claim on the space. The work is choosing what to pack and where: relevance-per-token over raw recall, the load-bearing fact at the edges where the model looks, truncation as a policy you set rather than an accident you suffer, and prompts versioned like the code they always were. A bigger window doesn't rescue you, because the scarce thing was never space — it was the model's attention, and you spend it on every token whether the token earned it or not. The demo let you type one string because the window held one thing. Production is the moment it fills with everything you didn't type — and whether it comes out a program or a pile of f-strings comes down to whether anyone is assembling it on purpose.
Exercises
These reward argument over recall. Paste any into a model and you'll get a tidy four-second answer that skips your budget, your owners, and your truncation policy — the only things that decide the outcome.
Take a single model call in a system you own or can picture precisely. Write down every component in its context window and estimate the token slice of each: system instructions, retrieved material, tool outputs, history, the user turn. Now extend the conversation to its realistic worst case and answer one question: which component gets evicted first, and who decided that? If the honest answer is "the runtime decided," you've found the bug before it found a customer.
Recast the assembled-window example in your own system. Name the components competing for the buffer, who owns each, what the budget is, and which component is silently evicted first when another grows. Then find where the mapping breaks — where your buffer isn't priced per unit, or your eviction isn't silent. The break is the interesting part: it tells you which of your problems is genuinely a context problem, and which one you've been mislabeling as one.
A colleague proposes moving a struggling feature to a larger context window. Make the strongest case for it, then the strongest case against — naming precisely what a bigger window changes (headroom before truncation) and what it changes not at all (relevance-per-token, lost-in-the-middle, who owns the budget). "More room" counts as an argument only once you've said room for what, at what cost per token, ranked how.
In a system you know, find a rule that is unquestionably "in the prompt," then prove where it actually lands in the assembled window on a long, retrieval-heavy request. If it rides in the middle of a big window, you've found a guardrail that works in the demo and fails on the traffic that matters. Deciding where to move it — and what to evict to make room — is the whole exercise.
Connections
This chapter is the direct consequence of that one. Because the model is a fixed, read-only dependency, the context window is the only mutable state you own — so "programming the model" is assembling the window. And the wrongness axis reappears here in new clothes: a silently truncated instruction or a fact lost in the middle degrades the answer without throwing, which is silent wrongness (Machine Learning Systems atlas) arriving through your own assembly step rather than the model's. That atlas's error tolerance axis is the dial the judgment section leans on: how much assembler a call earns is a function of what a wrong window costs.
Truncation-by-priority isn't a new idea; it's traffic tiering applied to tokens, and shedding the lowest-value context under budget pressure is graceful degradation applied to a window. This chapter borrows both wholesale — the window is a queue and an overloaded system, and you already know how to run those.
Retrieved facts were one component here, treated as a given — the window's largest and most volatile variable. The next chapter is the subsystem that writes to it: RAG is a search system feeding the assembler, "irrelevant chunks crowding the signal" is a retrieval problem traced to its source, relevance-per-token is why reranking exists, and a good chunk lost in the middle is this chapter's failure mode arriving through the retriever.
Conversation history was the component that grows without bound; Chapter 4 is how you bound it — summarization and retrieval-as-memory as deliberate compression rather than "keep everything until it overflows." And the window-is-priced thread pays off in Chapter 8, where relevance-per-token becomes a cost model and the stable-prefix trade-off becomes prefix caching on the GPU atlas's KV-cache economics.
The instruction-from-data seam is a maintainability practice here and a security boundary there: Chapter 5 makes the machine-facing side of the boundary a schema contract, and Chapter 9 shows what walks through the window the day you let untrusted text share a buffer with your trusted commands.
Next: Chapter 3 — RAG: Building It Is the Easy Part: how the "retrieved facts" slot actually gets filled, why a retrieval system inherits the failure modes of both search and generation, and why most "hallucination" is a retrieval miss wearing a fancier name.