Skip to content
Chapter 1 of 8 · Context windowThe assembled request

25 min read

The assembled request

On this page

Tokenization (1.1) turns text into a sequence of tokens, and a sequence has positions. A model reads its input as one run of those positions and writes its answer into the positions that follow, so there has to be some largest run it can hold at once. The context window is that run, stated in the unit the tokenizer already hands over.

What the window measures is sequence positions. A turn is one exchange in a conversation — a prompt and the response it draws — and both halves are tokens once the tokenizer has been through them, which makes a hundred short turns and one pasted log file the same kind of thing: runs of positions competing for one span. A single long message can fill the window as completely as a hundred short ones, because what is being spent is tokens rather than messages, and a client that budgets in turns is budgeting in a unit the model never sees.

The same mismatch appears one level up, in what providers publish. Every provider publishes a context window size — a number of tokens — for every model it serves, and that number arrives looking like a spending limit: the room an application has to work in, printed on a page where it can be read once and designed around. It is not a spending limit, and an engineer who treats it as one builds requests against a figure that was never theirs to spend.

What fills the window before your content does

Before that figure can be argued with, there has to be an account of what it is being spent on, because a request is never only the content its author wrote.

That principle shows up in the shape of every real request, so take a concrete one. An engineer is building a code-review assistant. It reads the diff of a pull request, has four tools available to it — search_repo for finding a symbol across the codebase, run_tests for a selection of the test suite, post_review_comment for putting a comment on a line, and lint_rule_lookup for pulling up the text of a lint rule by its code — and it posts its review back onto the pull request. The rule codes are declared as an enum inside lint_rule_lookup's schema, one entry per rule the repository's linters know about. Reviewing a diff takes the assistant several exchanges with the model, so what it is really running is a conversation: a standing instruction about what to look for, then a back-and-forth of turns that accumulates as the review goes on.

What that assistant sends on any one of those turns is a larger object than the code that builds it. The assembled request is the full token payload actually sent to the model: every token the serving layer puts into the sequence, not only the prompt text the caller composed. Laid out in the order it occupies that sequence, the assembled request holds the chat-template and role tokens the serving layer wraps each message in (1.14), the system prompt (2.6), the declared JSON schema of every tool the caller registered (5.8), every turn of the conversation so far, and the message the caller is sending now. Whatever is left of the window once all of those have taken their positions is what is actually available to the caller's own content, and putting a number on that leftover is the rest of the arithmetic here.

The engineer's mental model of one review request is short, because the code that builds it is short.

jsonPictured request
{
  "messages": [
    {
      "role": "user",
      "content": "Review this diff.\n--- a/payments/refund.py\n+++ b/payments/refund.py\n… 66,000 tokens of pull request diff …"
    }
  ]
}

The same conversation, the same pull request and the same standing instruction — the system prompt carrying the assistant's persona and rubric — rendered as the payload that actually goes on the wire, is a different object.

jsonAssembled request
{
  "model": "review-model-128k",
  "max_tokens": 2000,
  "system": "You are this repository's review assistant. … 1,860 tokens of persona, rubric and house style …",
  "tools": [
    {
      "name": "search_repo",
      "description": "Search the repository for a symbol or string.",
      "input_schema": {
        "type": "object",
        "properties": {
          "query": { "type": "string" },
          "path_prefix": { "type": "string" },
          "max_results": { "type": "integer" }
        },
        "required": ["query"]
      }
    },
    {
      "name": "run_tests",
      "description": "Run a selection of the test suite and return the report.",
      "input_schema": {
        "type": "object",
        "properties": {
          "selector": { "type": "string" },
          "timeout_seconds": { "type": "integer" }
        },
        "required": ["selector"]
      }
    },
    {
      "name": "post_review_comment",
      "description": "Post a comment on one line of the pull request.",
      "input_schema": {
        "type": "object",
        "properties": {
          "path": { "type": "string" },
          "line": { "type": "integer" },
          "severity": { "type": "string", "enum": ["nit", "suggestion", "blocker"] },
          "body": { "type": "string" }
        },
        "required": ["path", "line", "severity", "body"]
      }
    },
    {
      "name": "lint_rule_lookup",
      "description": "Look up the text and rationale of a lint rule.",
      "input_schema": {
        "type": "object",
        "properties": {
          "rule_code": {
            "type": "string",
            "enum": ["PY001", "PY002", "PY003", "… 1,100 further rule codes …"]
          }
        },
        "required": ["rule_code"]
      }
    }
  ],
  "messages": [
    { "role": "user", "content": "Flag any new public method that ships without a docstring." },
    { "role": "assistant", "content": "… 19 further turns of review conversation …" },
    { "role": "user", "content": "Review this diff.\n… 66,000 tokens of pull request diff …" }
  ]
}

Counted component by component, the gap between the payload the engineer pictures and the payload the provider receives has a size.

textComponent counts
component                                          tokens
chat template and role tokens                         340
system prompt                                       1,860
search_repo schema                                    410
run_tests schema                                      260
post_review_comment schema                            540
lint_rule_lookup schema                             2,180
conversation history, twenty turns                 46,200
current message: instruction and diff               66,050
------------------------------------------------  -------
assembled request                                 117,840
pictured request                                   66,050

The four schemas come to 3,390 tokens between them, and none of those tokens appears anywhere in the engineer's source code. Tools are declared once, in a list handed to the client library at construction time, and from then on the declaration is invisible at every call site. The library serializes all four into every single request, whether or not that request ever calls a tool, because the model has to see a tool's schema to decide whether to call it. A handful of verbose tool definitions is therefore a standing per-request tax, competing for the same span as the diff and the conversation, and it is charged on the requests that call nothing as heavily as on the ones that call everything.

Prompt caching is the natural place to look for relief from that tax, and it does not give any. A provider that caches a prefix stores the processed form of an already-seen span — a system prompt and a set of tool schemas being the usual candidates, since they repeat unchanged call after call — so that a later request carrying the same prefix is cheaper to bill and faster to start. What it stores is work, and what the window counts is positions. The cached span still occupies positions 341 through 5,590 of the sequence the model attends over, exactly as it did the first time, so a cached component is counted against the window in full and none of the arithmetic below gets to skip a line.

The component table is also the whole of the accounting on the provider's side. Every row in it is a row the engineer's own request put there: the template around their messages, the system prompt they wrote, the schemas they registered, the turns their client resent, the diff they attached. There is no further row held back off the top — no reserved ten percent, no house slice, no safety margin quietly deducted before the caller gets to spend. A reader who finds less room than expected is reading their own component table, not looking for a hidden deduction.

Naming the components only says what is competing for room. How much room there is to compete for is the provider's to state, and stating it is what the next section is about.

Advertised context limit

The reader now holds an account of what a request is made of. What that assembly gets measured against, for one actual model, is a single published figure.

That single published figure is the advertised context limit — the token capacity a provider states for a model, before anything the caller's own application adds is counted against it. It appears on the model's page, in its pricing table and in its changelog entry, and it is the number every comparison between models is conducted in.

The engineer reads 128,000 tokens on the page for review-model-128k and takes it at face value, the way the figure invites. Sizing follows from there: the diff the assistant reads and the running review conversation it carries are allowed to grow toward 128,000, on the understanding that the assistant is working inside a 128,000-token room. That understanding is the one that goes wrong later in this topic, and it goes wrong in a specific direction.

The gap it opens can be named without being closed yet. The advertised figure and the amount left over for a caller's own content are two different quantities, and the second one is not printed on the model's page at all — it depends on a system prompt, a tool list and a conversation the provider has never seen.

A capacity limit is also not a performance guarantee: a model does not read every position in its window with equal reliability, and material sitting deep inside a long context is recalled less well than material near its edges. So naming the total says nothing about whether the model works equally well everywhere inside it. A ceiling on how much can be sent is not a promise about what happens to material sent near it, and that second gap is separate from the arithmetic one.

Effective context length

Grant, for the moment, that a reader eventually learns exactly what else is counted against their window and computes their leftover room correctly. A second gap survives that, because the advertised figure carries an assumption about the model's behaviour: that it attends as reliably at position 120,000 as it does at position 200. The assumption does not hold.

The effective context length is the portion of the advertised window over which the model actually retains reliable answer quality, and it is routinely smaller than the stated limit.

NoLiMa: Long-Context Evaluation Beyond Literal Matching is the work that gives the idea its name and its measurement. Its method is what makes the result bite. Earlier long-context tests placed a fact in a haystack and asked a question that shared words with it, which a model can answer by matching surface forms without holding the surrounding material in any useful way. NoLiMa removes the shared vocabulary, so answering requires an actual associative step between the question and the buried fact. Measured that way, models advertising windows of a hundred thousand tokens and beyond hold their short-context accuracy over a fraction of the advertised span, and the decline sets in far below the figure on the label.

Why quality falls off with position and length is the subject of context rot (1.12), which explains the mechanism behind the decline. What matters here is the consequence for a budget: the advertised number and the span a model works well over are separate quantities that have to be sized separately.

Context Rot: How Increasing Input Tokens Impacts LLM Performance reproduces the finding across eighteen production models, and does it with an evaluation setup a reader can copy rather than only read — the same task at increasing input lengths, with the scored item held at a fixed position. acingai.com's Effective Context Length: Why 1M-Token Windows Fall Short turns those results into a rule of thumb for sizing a prompt. For the same distinction walked through visually, IBM's What is a context window? and its companion video cover advertised against usable capacity, and NetworkChuck's Why LLMs get dumb covers the same ground for a viewer meeting it for the first time. Chroma's own presentation of the results is available from Kelly Hong directly, and Matt Palmer walks the charts for readers who want the curves read out loud.

Read across those sources, the advertised figure and the effective one move apart in each direction independently.

textComparisons
advertised held fixed   Two models both advertising 128,000 tokens are
                        evaluated on the same non-literal retrieval task.
                        They diverge from each other well inside that
                        figure, and by different amounts at the same input
                        length, so the shared label predicts neither curve.

advertised climbing     Published windows have grown by orders of magnitude
                        across provider generations, from a few thousand
                        tokens to a million and beyond. A larger label
                        certifies that more tokens are accepted; it makes no
                        claim about the span underneath it over which
                        quality holds.

hidekazu-konishi.com's LLM Context Window Growth Timeline is where that climb is laid out release by release, alongside the input and output limits each milestone actually carried.

Neither a provider's published figure nor a benchmark's reported curve tells the engineer where the cutoff sits for their provider, their model version and their kind of prompt. A published benchmark is somebody else's task on somebody else's data. The number the code-review assistant needs is one about pull request diffs, and it has to be measured rather than looked up.

Context limit probing

Both available numbers fail the engineer for the same reason at different angles: the advertised figure is an unverified claim by the party making it, and the benchmark curve was measured on a task that is not theirs. What is left is to find out directly.

Context limit probing is the technique of sending a fixed-size prompt and output request to measure the limit a provider actually enforces, rather than trusting the number it advertises. Sizing the prompt precisely is what makes it a measurement, and token counting (2.14) is the client-side technique that turns a pile of filler text into a payload of an exact, known token count.

Two different questions come out of the same apparatus, and both matter.

The first is where the provider stops accepting input. The probe holds one short instruction and a capped output fixed, pads the message to a precise token count, and sends it again at a larger count, climbing until a request comes back rejected. What that finds is a hard boundary, and it is a fact about the endpoint rather than about the answer.

The second is where answer quality starts to fall. That probe holds a fixed task and a fixed, scored check at a fixed position inside the prompt, then grows the padding around it and re-scores at every size — the shape of the Chroma setup, which is the reason that setup is worth copying rather than only citing. What it finds is not a boundary but a curve, and the number to read off it is the length at which the score stops being acceptable for the task at hand.

Run against the code-review assistant's own model, the first probe produces a line worth writing down.

textProbe sweep
prompt tokens   max_tokens   result
      100,000        2,000   accepted
      110,000        2,000   accepted
      120,000        2,000   accepted
      124,000        2,000   accepted
      125,600        2,000   rejected: maximum input length exceeded

The page for this model advertises 128,000 tokens, and the requests stop being accepted a few thousand tokens short of that, because the serving layer standing in front of the weights enforces a cap of its own. What the engineer now holds is two figures rather than one: the number the provider advertises and the number their requests are actually measured against. Where those diverge, the probed figure is the one to budget with, and the terms stop being interchangeable at exactly that point.

The second probe, run on the same model with the same padding technique, produces a length rather than an event. The assistant keeps flagging the planted requirement correctly while the diff around it is small, and starts missing it at some size well below the point where the API stopped accepting input. The two figures sit in a fixed relation to each other — the quality figure first, the acceptance figure later — and the room worth budgeting with is bounded by the earlier one.

Both figures are snapshots. They describe one provider, one model version and one client library on one day, and providers change what they enforce without changing what they advertise. A probe result belongs in a record with its date attached, and it has to be re-run rather than trusted as permanent.

Output reservation

The probe fixes a ceiling and a working length, and the component table fixes everything on the input side, because each of those components exists in full before the call goes out and can be counted. The model's answer is the one part of the sequence that cannot be counted that way. Nobody has written it yet, its length is not known until it is finished, and it lands in positions that the input has already been allowed to take. Room for it has to be set aside on purpose.

An output reservation is a number of tokens the engineer picks in advance — two thousand, say — and holds back from the input budget, so the model's answer is guaranteed somewhere to land. It is chosen rather than derived: nothing about the model, the task or the request computes it, and a different engineer sizing the same assistant could pick a different figure and be equally correct.

Holding it back is a subtraction, and the subtraction is worth writing out with every line visible.

textHeadroom
model window                                      128,000
chat template and role tokens                        -340
system prompt                                      -1,860
four tool schemas                                  -3,390
output reservation                                 -2,000
------------------------------------------------  -------
headroom for history and the current message      120,410

For this request, the conversation and the diff come to 112,250 tokens between them, so it sits 8,160 tokens inside that headroom. The arithmetic runs one way only: the window, less each assembled component, less the chosen reservation, produces the headroom. The reservation is an input to that line, never an output of it.

Nothing in the request format performs this subtraction on the caller's behalf. The max_tokens field in the payload above is a cap on how long the answer is allowed to run, and a cap claims no positions in advance — it stops generation at a length, it does not hold a length open. Room for the answer is whatever the input did not take, which makes the answer the residual claimant on the window and the only line item in the request that cannot object to being squeezed. A request whose input runs to 120,000 tokens of a 128,000-token window has around 8,000 tokens of room for its answer regardless of what max_tokens says, and setting that field to 4,000 does not reach back and shorten the prompt.

Seen from the caller's end rather than the mechanism's, a reservation that was sized too small and an answer that was cut off partway look alike, which is why output truncation (1.8) sits next to output reservation and is easily read as the same thing: both leave the caller holding a shorter answer than they wanted, from a request that returned normally. They are separated by which side of the request each one lives on. An output reservation is a number chosen in advance, on the input side, before anything is sent, and its whole purpose is to guarantee the answer somewhere to land. Output truncation happens on the output side, once generation is already under way and the answer is being cut off partway through. How truncation itself behaves — what stops the generation, and what the response says about it — is 1.8's to explain.

Choosing a reservation is a decision the engineer makes about the shape of the request. It is not a guarantee about what actually gets sent, and nothing so far prevents the assembled request itself from being too large in the first place.

Context overflow

Every quantity needed to ask the question that matters before a request goes out is now in hand: a probed ceiling, a counted assembly, a chosen reservation, a computed headroom. The question is whether the request fits, and for the first time the honest answer can be no.

Context overflow is the condition where the assembled request plus the reserved output exceeds the model's context window.

The code-review assistant meets it on the pull request it has been working through all along. The engineer, wanting the assistant to see the latest work, appends the next commit's diff to the same thread without recomputing anything.

textTrimmed request
conversation history, twenty turns                 46,200
current message: instruction and diff               66,050
------------------------------------------------  -------
history and current message                       112,250
headroom                                          120,410
spare                                               8,160
textUntrimmed request
the same system prompt, the same four tool schemas, the same twenty
turns, the same reserved two thousand tokens; the current message now
carries the next commit's diff as well, at 12,400 tokens

history and current message                       124,650
headroom                                          120,410
------------------------------------------------  -------
over                                                4,240

Both requests carry the same pull request, the same conversation and the same task. The first respects its output reservation; the second overflows it, and the assembled request plus the reserved output comes to 132,240 tokens against a 128,000-token window.

Nothing in the request format shrinks a prompt or moves content aside on the caller's behalf when that happens. There is no field that gives the provider permission to trim, and no default that quietly makes room. The request either succeeds as assembled or it does not, and which of those follows is not settled by the condition itself: it is a property of the specific provider and client library being called, which is the next thing to pin down.

Knowing a request has overflowed is only useful if something says so, and nothing guarantees that anything will.

Silent context truncation

What actually happens once a request overflows was left open above because the answer is not one thing, and the version of it that costs the most is the one that gives no warning at all.

Silent context truncation is a provider's response to context overflow in which excess content, typically the oldest, is dropped without an error or any other signal to the caller.

Set it next to the alternative response to the same overflow — an explicit error returned to the caller — and what they share is the whole of the input: the same over-budget assembled request, the same 4,240 tokens too many, arriving at an endpoint. One path returns that error naming the count, which the caller can catch, log, retry and act on. The other returns a normal-looking, well-formed answer with quietly less of the caller's content behind it. The request that produced each is identical; the response is where they part.

Three readings of that are available and all three are wrong, so each is worth ruling out on its own.

The first reads it as the model stopping partway through an answer it had already begun. It is not: silent truncation happens on the input side, decided before a single token is generated, and what is dropped is the caller's own prompt. An answer cut off mid-sentence is output truncation and belongs to 1.8, and the two failures are visible in different halves of the exchange.

The second reads it as a property of the model, answerable by reading a model card or picking a different model. It is not. Between the caller's JSON and the weights sits a serving layer and a client library, and it is that layer that renders messages into a token sequence, applies the chat template, counts the result and decides what to do when the count is too large. Deciding is a policy choice made by whoever wrote it. The same weights served through two different endpoints can hard-error on one and silently trim on the other, so "which model is this" does not predict the behaviour, and it has to be established per endpoint by sending an over-budget request and watching what comes back.

The third reads it as a billing matter, on the reasoning that dropped tokens are tokens not paid for. Neither path is a pricing rule, and no invoice line distinguishes them: the silently trimmed call is billed as an ordinary request of its post-trim size, which is exactly why the cost lands on answer quality and never appears on a bill anyone reviews.

What makes the silent path expensive is that its output is indistinguishable from a good one. Same shape, same fluency, same apparent confidence, same absence of any field marking it. The only difference between the answer the engineer reads and the answer they think they are reading is content the model never saw.

The code-review assistant meets this the week the team adds a fifth linter. The new linter's rule codes go into the same enum inside lint_rule_lookup's schema, which roughly doubles it.

textSchema growth
                                 before      after
lint_rule_lookup schema           2,180      4,460
four tool schemas                 3,390      5,670
headroom                        120,410    118,130

Nobody edited the conversation logic. Nobody edited the system prompt. The enum sits in the payload of every request for the reason the component table gave, and 2,280 tokens of room have come out of everything else on every call from that point on. The over-budget request from the section above now stands 6,520 tokens above its headroom, and the client, written to trim history until the request fits, drops the oldest turns until it does. The first message in that thread is where the engineer wrote flag any new public method that ships without a docstring, and by the twentieth turn it is no longer in what the model reads. The requirement did not fade from the model's attention. It was removed from the request outright, by arithmetic, because a tool schema got heavier — and the component table is where the tokens went.

Which content goes first when trimming happens is decided by a context eviction policy (4.16): the rule the client applies to choose what to discard when the assembled request will not fit. Whether that rule is oldest-first, lowest-scored first, or anything else is 4.16's to work out.

Where a fact is written decides whether any such rule can reach it. A system prompt (2.6) is resent in full on every single call by definition of what it is — the standing instruction block, rebuilt into every request the client sends. Content living only in the conversation history is exactly what an eviction policy is free to remove. So a standing requirement placed in the system prompt survives every trim, and the same requirement typed as the first message of a thread survives until the thread outgrows the room. That holds independent of how large the window is: a larger window postpones the trim without touching the policy that decides what the trim takes.

Every one of those decisions — which rule fires, what it reaches, what it takes — is made on the way in, and none of it is written back out. A silently truncated request leaves no trace in its response. No field reports it, no usage object flags it, and the returned text is well formed either way, so nothing downstream of the call can catch it after the fact. Whatever catches it has to run before the call goes out.

A budget you compute, not a limit you discover

Silent truncation has to be caught before the call goes out, and every quantity that decides whether a request fits is now available, along with every way a provider might respond once it does not. Turning that into something that runs on every request, rather than arithmetic redone by hand whenever somebody remembers, takes one function and one procedure around it.

The function takes the ceiling the probe found, the reservation the engineer chose, and the token count of every assembled-request component, each counted with token counting (2.14) against the rendered payload rather than the source strings.

tsBudget check
type Components = {
  template: number
  system: number
  tools: number
  history: number
  message: number
}

type Budget =
  | { ok: true; headroom: number }
  | { ok: false; over: number }

function checkBudget(
  ceiling: number,
  reservation: number,
  counted: Components,
): Budget {
  const assembled =
    counted.template +
    counted.system +
    counted.tools +
    counted.history +
    counted.message

  const headroom = ceiling - assembled - reservation
  return headroom >= 0 ? { ok: true, headroom } : { ok: false, over: -headroom }
}

It computes the subtraction already walked through, and it returns either the headroom or an explicit overflow signal, so the request stops being something that goes out blind.

The card puts that function inside the full procedure, and two of those steps carry a reason worth stating. Dropping the lowest-ranked chunk rather than the oldest content follows from where the load-bearing material actually sits: the review requirement in the code-review assistant's thread is the oldest thing in it and the thing the whole review depends on, and a recency rule discards it first. Ranking by relevance keeps it because it is relevant, which is the property that matters. Refusing to cut a chunk partway follows from what a partial chunk looks like to the model: a section of a function with its guard clause missing, or a policy with its exceptions gone, reads as a complete section of a function or a complete policy, and the model answers on it as though it were. A chunk left out entirely produces no such claim.

Step 10 covers the case where the budget was honoured and the answer still came back short. A finish reason of length, on a request that passed checkBudget going in, points at the reservation itself: two thousand tokens was not enough for the answers this task actually produces, and the fix is a larger number in that argument rather than an investigation downstream.

Those steps together earn a rule: the provider's overflow behaviour should stop mattering. A client that never sends a request whose fit it has not already checked behaves identically in front of an endpoint that errors and an endpoint that trims, because it never gives either one the chance to decide. Where there is a choice of provider or client library, the one that raises an explicit error is still the one to take: a loud failure is a fact that reaches a log and an alert, and a silent drop is a well-formed, confident, wrong answer with nothing anywhere in the response marking it as such.

The rule generalizes past the one component this procedure defends: giving any component a deliberate number is what token budget (4.13) names, an allocation made to all of them — system prompt, tools, history, retrieved content and output each holding a number set on purpose rather than each taking what the ones before it left. The output reservation above is one line of such a budget.

What checkBudget prevents is overflow. Whether the content that did fit was the right content to include is a question about quality rather than capacity, and the function cannot see it.

Overflow rate

The check runs once per request, which raises the question of how often it actually catches anything. How close representative production traffic runs to the edge is measurable, and it is a different question from whether any single request fits.

To measure that, the overflow rate is a production metric counting how often, or how closely, representative requests approach or exceed the context window once reserved output is counted.

LLM tracing (15.2) is what makes it collectable: a per-request span carrying the token count of each component, the headroom the check computed, and whether that request overflowed or was trimmed. Building those spans, and getting them out of a service and into somewhere queryable, belongs to 15.2.

Charted for the code-review assistant across the week the lint-rule enum grew, the metric has a step in it. Before the fifth linter landed, a small single-digit share of review requests computed negative headroom — the largest pull requests on the busiest repositories, arriving at the edge and being trimmed to fit. Afterwards the share is roughly triple that, and the step lines up with the deploy that added the linter. What the chart is showing is the same 2,280 tokens that came out of lint_rule_lookup's enum, aggregated across traffic: a change in one subsystem, visible as a line moving on a dashboard, days before anyone files a report about an assistant that forgot a requirement.

A healthy overflow rate says that requests are being assembled and reserved for correctly. It says nothing about whether the model made good use of what was inside the window on any of them.

Run the code-review assistant through every check built here — the probed ceiling, the counted components, the chosen reservation, the budget function, the traced spans — and it can still return a worse review than a shorter, more selective request would have. The engineer sends the full 66,000-token diff of a sprawling refactor and gets back a review that misses a bug in one file; they send the same model the same instruction with only that file's diff attached, and the bug is caught. Both requests fit. Both passed the check with room to spare.

That is the capacity-versus-quality split again, now with two requests to compare rather than one budget to compute, and the comparison holds everything else still: the same model version, the same instruction, the same passage of code carrying the bug, and the same sampling settings, with temperature and everything beside it untouched between the requests. The only difference is how many tokens sat around that passage. That is what makes prompt length itself the reading to reach for when a request that fits comfortably answers worse than a shorter one — a knob nobody turned cannot be what moved, and a limit nobody hit cannot be what bound.

What happens to a model's attention across content that is inside the ceiling is explained and measured in context rot (1.12). Whether content is counted correctly and kept inside a hard ceiling is the subject here, and the boundary between the two runs exactly along the ceiling itself.

So probing tells the engineer where their own enforced limit and their own working length sit, for one model, one provider, on one day. A budget computed before every call keeps each request inside both. Neither one says how much context was worth including in the first place, and that figure has to be measured against the reader's own task, the same way the probe was.

Context window — read the current version at https://daviddominguez.dev/learn/en/context-window/the-assembled-request

Back to top