On this page
- The advertised window is a ceiling, not a working budget
- What is actually consuming the window
- Why quality falls before the limit does
- Allocate the window before anything consumes it
- Compacting history without losing the constraints
- Prefix caching changes both the arithmetic and the layout
- Long context or retrieval
- Measure your own effective context
- Fit and cost, on your own numbers
The advertised window is a ceiling, not a working budget
A context window is the maximum number of tokens the API will accept in one call, input and reserved output together. It is a hard limit enforced by the provider, and it is not a statement about how much material the model uses well. Accuracy on a specific fact usually starts falling long before the ceiling, and it falls faster when the context contains passages that resemble the right answer without being it. Treat the window as a budget you allocate deliberately, and find your own working limit by measuring, because the published number describes what fits rather than what works.
The distinction matters because the two numbers are used for different decisions and get confused constantly. The ceiling tells you whether a request will be rejected. Your effective context tells you whether the answer will be right. A system that stays under the ceiling and above the effective limit does not fail loudly, it just becomes unreliable in a way that looks like a prompt problem.
Providers change these limits regularly, so the number for your model belongs in your model registry rather than in an article. Read it from the provider's current documentation, record it beside your model pin, and derive every cap in this guide as a fraction of whatever that number is.
- Output shares the budgetMost APIs count the reserved output against the same window. Input plus maximum output above the ceiling is a rejected request, not a truncated one.
- Tool schemas are a fixed taxEvery tool definition is serialised into the prompt on every call, whether or not the model uses it.
- More passages is not more accuracyAdding retrieved chunks past a point adds near-miss material that competes with the right one.
- Never estimate by charactersThe characters-per-token shortcut breaks on code, JSON, identifiers and non-Latin scripts. Count with the provider's tokenizer.
- Caches key on an exact prefixOne volatile token near the top, a timestamp or a user id, invalidates the cache for every request behind it.
- Context window
- The maximum number of tokens a model can process in a single request, counting the system prompt, tool definitions, conversation history, retrieved material, the user's message and the space reserved for the response.
- Effective context
- The amount of material a model actually uses reliably for a given task, measured by placing a known fact at different positions in contexts of increasing length and recording where accuracy falls. It is a property of your model, your task and your data, not a published figure.
- Output reservation
- The tokens set aside for the response, which on most APIs are subtracted from the same window as the input, so a large reservation directly reduces how much evidence you can send.
- Prefix cache
- A provider-side store of the computed state for a prompt prefix, reused when a later request begins with the identical token sequence, which reduces the cost and latency of the repeated portion.
- Compaction
- Replacing a growing conversation history with a shorter representation that preserves what later turns depend on, either as a summary, a structured state object, or a searchable store of past turns.
What is actually consuming the window
Six things share the budget, and teams usually track only two of them. The ones that get missed are the ones that grow without anybody deciding they should.
| Consumer | Grows when | Typical oversight |
|---|---|---|
| System prompt and policies | Every incident adds a clause | Nobody measures it after launch, and it only ever gets longer |
| Tool and schema definitions | A new tool is added anywhere in the codebase | Serialised on every call whether used or not, and long field descriptions make it worse |
| Retrieved passages | Somebody raises k to improve recall | The extra passages are near misses, which cost tokens and compete for attention |
| Conversation history | Linearly, one turn at a time | Nothing evicts it until a request fails in production on a long session |
| The user's own input | A customer pastes a log file | No cap, so one paste can push the whole request over the ceiling |
| Reserved output | Somebody raises the token ceiling | Comes out of the same budget, so evidence silently shrinks to make room |
The tool row deserves more attention than it gets. Tool definitions are part of the prompt, so twenty tools with carefully written descriptions is a permanent per-call cost on every request, including the ones that need none of them. It also makes selection harder, since the model must choose from a longer menu. If your agent has accumulated tools, both the cost and the accuracy argument point the same way, and designing tools for agents covers how to consolidate them.
The rule of thumb that a token is roughly four characters was derived from English prose and does not survive contact with anything else. Code, JSON with long field names, identifiers, tables and non-Latin scripts all tokenize far less efficiently, and some scripts use several tokens per character. A budget built on the shortcut will be comfortably under the ceiling in testing and over it in production on real documents. Every provider ships a tokenizer or a counting endpoint, so measure the actual assembled prompt and log the count per envelope.
Why quality falls before the limit does
Three mechanisms degrade long-context performance, and they are separate problems with separate fixes. Naming them individually is what stops a team from responding to all three by shortening the prompt and hoping.
Attention is spread across everything you sent
A model attends over the whole sequence, so every additional token is another candidate for the model's attention when it looks for the one that matters. Doubling the context does not halve the accuracy, but it does dilute the signal, and the effect is strongest for a single small fact buried inside a large body of related text. This is why a needle in an otherwise empty haystack is an easy test and a poor proxy for real work.
Position is not neutral
Material at the start and end of a long context is generally used more reliably than material in the middle. The size and shape of that effect varies by model and changes between releases, so treat it as a property to measure rather than a constant to design around. The practical consequence is stable: if one passage matters more than the others, do not leave its position to the order your retriever happened to return.
Distractors are worse than length
This is the mechanism that surprises people. Filling a context with passages that are topically similar to the answer, which is exactly what a retriever produces, is harder for a model than filling it with unrelated text. The near misses compete. A retriever tuned for recall by raising k is therefore buying coverage with interference, and past a certain point it trades away more accuracy than it gains. That point is specific to your corpus and it is worth finding, because the fix is a reranker and a lower k rather than a larger window.
Published figures on any of this describe someone else's model, task and corpus, and they move with every model release. The section on measuring your own effective context gives you a number you can defend, and it takes an afternoon.
Allocate the window before anything consumes it
The default behaviour of a context window is first come, first served: whatever the assembly code appends first gets the space, and whatever runs last gets truncated by a library nobody remembers configuring. That is a design decision made by accident. Replace it with an explicit allocation.
The Five Envelopes
Divide the window into five named envelopes with hard caps, an owner, and an overflow policy each. Express every cap as a fraction of whatever window the pinned model has, so migrating to a different model preserves the mix instead of silently rearranging it.
The system prompt and the policies. Small, stable, and first in the sequence so it anchors the cached prefix. Give it a cap and make exceeding it fail the build, because a system prompt with no ceiling grows one incident at a time until it is quietly the largest thing in a short request.
The serialised schemas, sent on every call. Cap it and the cap does the arguing: adding a tool means consolidating two others or raising the fraction deliberately, in a pull request, with the cost visible. Without a cap, tool count grows until somebody investigates a latency complaint.
Retrieved passages, and the only envelope where k should be derived rather than chosen. Set the fraction, divide by your mean chunk size, and k falls out. A team that picks k as a round number is setting the most important retrieval parameter by aesthetics. Chunk size interacts directly with this, which is covered in chunking strategies.
The conversation, capped with a named eviction policy rather than a truncation that happens somewhere in a client library. This is the envelope that grows without limit in production while looking fine in testing, because your tests are short and real sessions are not.
The output plus a safety margin, and the envelope nothing is allowed to borrow from. A response cut off mid-sentence is a worse failure than a slightly thinner evidence set, and it is the one users notice. Decide the precedence once, in configuration: when the assembled prompt is over budget, evidence shrinks first, then history, never the reserve.
Assert at build time that the caps plus the safety margin sum to no more than one, that every envelope has an overflow policy, and that the assembled prompt is counted per envelope on every call and logged. Without the per-envelope log you will know a request was too large and not which part grew, which is the difference between a five minute fix and an afternoon.
# context/assembly.yaml
# Caps are FRACTIONS of whatever window the pinned model has, never raw token counts.
# Change the model and the mix stays proportional instead of silently rearranging itself.
window_source: model_pin # read from the model registry at build time, never hardcoded
safety_margin: 0.05 # never allocated, absorbs tokenizer disagreement
envelopes:
instruction:
cap: 0.06
owner: "@rina"
overflow: fail_build # a longer system prompt is a design decision, not an accident
position: 1 # first, and byte-identical across calls
tools:
cap: 0.08
owner: "@platform"
overflow: fail_build # adding a tool means removing one or raising the cap on purpose
position: 2
note: "serialised schemas are re-sent on every call. 20 tools is a permanent tax."
evidence: # retrieved passages
cap: 0.34
position: 3
k: derived # k = floor(cap_tokens / mean_chunk_tokens), never a magic number
overflow: drop_lowest_score
require_citation_ids: true
history:
cap: 0.32
position: 4 # after the stable prefix, because it changes every turn
eviction: structured_state # see history.yaml, not a rolling prose summary
always_keep:
- "the pinned state object"
- "the last 4 turns verbatim"
reserve: # output plus headroom, never borrowed from
cap: 0.15
min_output_tokens: 800
on_breach: shrink_evidence_first # explicit precedence, decided once, not at runtime
cache:
stable_prefix: [instruction, tools]
forbidden_in_prefix: ["current_datetime", "user_id", "session_id", "request_id", "greeting"]
assert_prefix_byte_identical: true
build_assertions:
- "sum(envelope caps) + safety_margin <= 1.0"
- "every envelope has an overflow policy"
- "token counts come from the provider tokenizer, not from a character estimate"
- "assembled prompt is counted and logged per envelope on every call"
Compacting history without losing the constraints
History is the envelope that overflows first in any conversational system, and the strategy you pick decides which information survives. All four options below are in production somewhere. Only one of them keeps a constraint stated in turn three alive at turn ninety.
| Strategy | How it works | What it loses | Use it when |
|---|---|---|---|
| Sliding window | Keep the last N turns, drop the rest | Everything stated early, including the budget and the exclusions | Sessions are genuinely short and stateless |
| Rolling summary | Summarise older turns into prose, then summarise the summary | A detail per generation, silently, with no record of what went | Never as the only mechanism |
| Structured state | Extract facts, decisions and constraints into a schema, carry that verbatim | Anything the schema has no slot for, which is at least visible | Any session that can run past a dozen turns |
| Retrieval over history | Index past turns, retrieve the relevant ones per turn | Continuity, since each turn sees a different slice of the past | Long-running assistants where old detail is occasionally needed |
The failure mode of the rolling summary is worth stating plainly, because it is the most common design and it degrades in a way that produces no error. Each summarisation is lossy. Summarising a summary compounds that loss, and the constraint the user stated once in turn three, no weekend deliveries, disappears somewhere around the fourth compaction with nothing in any log to say it went. The model then produces a confident plan that violates it, and the transcript shows the user stating it clearly, which makes the bug look like the model ignoring an instruction rather than never receiving one.
The structured state pattern fixes this by separating two things that a summary conflates. The conversation is compressible. The facts stated in it are not. Extract facts, decisions, constraints and open questions into a schema, carry that object verbatim on every turn, and keep only the last few turns as prose. Constraints are marked as never evicted, whatever the cap says. The state object is inspectable, diffable between turns, and testable, which a prose summary is not. This connects directly to agent memory design, where the same object usually becomes the durable memory.
// context/history_state.schema.json
// Conversation history compacts into THIS, not into a prose summary.
// Prose summaries of prose summaries lose a constraint per generation and never say which one.
{
"type": "object",
"additionalProperties": false,
"required": ["facts", "decisions", "constraints", "open_questions", "turn_range"],
"properties": {
"facts": {
"description": "Stated by the user, never inferred. Each carries the turn it came from.",
"type": "array", "maxItems": 40,
"items": {
"type": "object",
"required": ["key", "value", "turn"],
"properties": {
"key": { "type": "string" },
"value": { "type": "string", "maxLength": 200 },
"turn": { "type": "integer" }
}
}
},
"decisions": {
"description": "Choices already made. Re-opening one requires the user to say so.",
"type": "array", "maxItems": 20,
"items": { "type": "string", "maxLength": 200 }
},
"constraints": {
"description": "Hard limits: budget, dates, exclusions, compliance. NEVER evicted, whatever the cap.",
"type": "array", "maxItems": 20,
"items": { "type": "string", "maxLength": 200 }
},
"open_questions": {
"type": "array", "maxItems": 10,
"items": { "type": "string", "maxLength": 200 }
},
"turn_range": {
"description": "Which turns this state covers, so the verbatim tail is not double counted.",
"type": "object",
"required": ["from", "to"],
"properties": { "from": { "type": "integer" }, "to": { "type": "integer" } }
}
}
}
Prefix caching changes both the arithmetic and the layout
Providers can cache the computed state of a prompt prefix and reuse it when a later request begins with the identical token sequence, which reduces the cost and the time to first token for the repeated part. The mechanism is exact prefix matching, and that single fact dictates how you order the prompt.
- Stable content first. System prompt, then tool schemas, then any long document shared across requests. Volatile content goes last: the retrieved passages, the history, the user's turn.
- Nothing volatile above the line. A current timestamp, a user id, a session id, a request id or a personalised greeting placed near the top invalidates the cache for every request behind it. This is the most common cache bug and it is invisible in behaviour, since the output is identical and only the bill and the latency move.
- Byte-identical means byte-identical. A reformatted system prompt, a changed indent, a trailing newline added by a linter, all break the match. Keep prompt text in its own file and assert on the prefix in continuous integration, as covered in prompts as code.
- Check the provider's rules rather than assuming. Minimum prefix length, cache lifetime, whether caching is automatic or requested explicitly, and how a cached read is priced all vary by provider and change. Read the current documentation and record what you found beside your model pin.
The consequence for design is that a long stable prefix is cheaper than its token count suggests, and a short prompt with a volatile header is more expensive than its token count suggests. That inverts some architectural instincts. Sending the same reference document on every call can be reasonable if it sits in a cached prefix, while a carefully minimised prompt that begins with the current date is paying full price on every request.
Log the cached and uncached token counts the provider reports on every call, and chart the hit rate. It falls the moment somebody edits the system prompt, adds a tool, or moves a volatile value upward, and it is the fastest available signal that an assembly change had a cost nobody intended. A cost spike with no traffic change is almost always this.
Long context or retrieval
This is not a choice between two architectures so much as a choice about where the selection happens. Retrieval selects before the call, long context selects during it. The trade-offs are stable even as window sizes grow.
In practice most production systems use both: retrieval to select the evidence, and a generous window so the selected evidence arrives whole rather than clipped. The case for stuffing is strongest when the corpus is small, stable and shared across requests, because prefix caching then makes the token count much cheaper than it looks. The case for retrieval is strongest when the corpus is large, changes often, or when you need to show a user which passage an answer came from. RAG explained for builders covers the retrieval side in full.
Measure your own effective context
Every number published about long-context degradation was measured on a particular model, task and corpus, and it moves with each model release. Yours takes an afternoon to measure and it is the only one you can defend in a design review.
- Collect thirty to fifty real questions with known answers
From your own corpus, with the passage containing each answer identified. Use questions people actually ask, including the ones with a near-duplicate elsewhere in the corpus, since those are the cases the probe is really testing.
- Build contexts at increasing lengths
For each question, assemble contexts at several sizes across the range you might realistically use, up to the ceiling. Keep the question and the target passage constant so length is the only variable that moves.
- Fill with your own distractors, never with filler
Pad with the passages your retriever actually returns for that query, not with unrelated text. Random filler makes the probe far easier than production and produces a flattering number that will not hold. The similarity of the distractors is most of what you are measuring.
- Vary the position of the target passage
Place it near the start, in the middle and near the end of each context length. This separates a length problem from a position problem, and the two have different fixes: shorter contexts against reranking and deliberate ordering.
- Score, and read the transitions
Plot accuracy against length for each position. The useful output is the length at which the middle position starts falling, since that is your working ceiling for anything that matters. Read the failures rather than only counting them, because the shape of a wrong answer tells you whether the model missed the passage or found a distractor.
- Re-run it on every model change
This is why you script it. A new model build, a different provider or a change to your chunking all move the curve, and the probe is the only way to find out before your users do.
Two things usually come out of the first run. The working ceiling is lower than the advertised window by a wide margin, and the middle position falls first. Both are useful in a way a vendor number is not, because you can point at your own chart when someone proposes raising k to fix a recall complaint.
Fit and cost, on your own numbers
Once the envelopes are named, the arithmetic is straightforward and worth running before the design rather than after the first oversized request. Enter your pinned model's window and your measured token counts.
Take the window from your model registry and the token counts from the provider's tokenizer on a real assembled prompt. Prices are starting defaults in your own currency per million tokens. If headroom is negative the request will be rejected, and the maximum chunks figure tells you what k your evidence envelope can actually support.
The maximum chunks output is there to make a point rather than to be used. It shows what the window would permit, which is usually many times more than the number that produces good answers, and the gap between those two figures is the whole subject of this guide. Set k from your evidence envelope and your probe results, not from what fits. The general cost model sits in token cost arithmetic.
That review takes about an hour and it removes the two failures that account for most context incidents: a request that grew past the ceiling because nothing capped it, and a quality drop from evidence that was expanded on the assumption that more is better. Both are budget failures rather than model failures, which is why the fix is a configuration file. This is the assembly layer we build into every custom LLM application rather than something added after the first oversized request.
A context window is the maximum tokens the API will accept, not the amount a model uses well. Effective context is lower, depends on how similar your distractor passages are to the answer, and has to be measured on your own data.
Questions readers ask next
Does a bigger context window mean I no longer need retrieval?
How do I count tokens accurately?
Why did my answers get worse after I retrieved more chunks?
What is the best way to handle a long conversation?
Does the order of things in my prompt matter?
Should I reserve output tokens inside the window?
How do I know what my model's effective context really is?
ChatGPTalker. "Context Windows: What Fits, and What Degrades." chatgptalker.com, 2026-08-26. https://chatgptalker.com/guides/context-windows-explained/