Budgeting the Context Window: Dividing a Fixed Space

The window wasn't full and the constraints still vanished — because six claimants compete for one space and nobody allocated it. Making history the residual rather than a claimant is what makes the scheme work.

A research agent with a large context window kept losing its constraints around turn twenty. The window wasn't full. The problem was that nobody had decided how the space was divided, so it was allocated by accident — in arrival order, first come first served, with the accumulated history quietly consuming whatever the earlier claimants hadn't taken.

Context is a budget with several competing claimants. Left implicit, the allocation is decided by whichever tool returned something large.

The six claimants

Every turn, these compete for the same space:

1. System prompt. Fixed, small, stable. Should be a page.

2. Tool definitions. Fixed per phase. Grows with the tool count, which is one more argument for a curated list.

3. Structured run state. The work ledger, constraints, decisions. Small, regenerated each turn by the loop.

4. Retrieved material. Variable, and the biggest discretionary lever.

5. Accumulated history. Prior turns and tool results. → Grows without bound by default, which is why it ends up eating everyone else's allocation.

6. The current turn. The immediate task and any constraint restated before acting. Small, and the most attended position.

Set the budget explicitly

Pick a working budget well below the window limit — leaving headroom is what prevents the emergency compaction that drops something important — then allocate:

working budget: 32,000 tokens

system prompt        1,000   fixed
tool definitions     2,000   fixed per phase
run state            1,500   capped, regenerated
retrieved material  10,000   hard cap
history             16,500   residual — compacts to fit
current turn         1,000   always reserved

⚠️ Note that history is the residual, not a claimant with its own allocation. That single choice is what makes the scheme work: everyone else takes their fixed share, and history compacts automatically to whatever remains. Give history its own budget and it competes with retrieval, and one of them will lose unpredictably.

The rules that follow

Fixed claimants go first, in stable order. System prompt and tool definitions are the cache prefix. Anything that reorders or rewrites them costs you the cache on every call.

State is capped and regenerated, never accumulated. If the ledger exceeds its cap, that's a signal the run has too many items in flight — not a reason to raise the cap.

Retrieval gets a hard cap, and the cap is a feature. Ten thousand tokens of retrieved material forces a relevance decision. Uncapped retrieval returns its top N regardless of whether any of it is relevant, and the irrelevant portion is paid for on every subsequent turn.

The current turn is always reserved. Never let compaction eat the space the actual task needs.

What happens when a claimant overflows

Each needs an explicit policy, because the default — silently truncate whatever is at the end — is the worst option:

  • Retrieval over cap → return fewer results with an explicit note that more matched. The agent can then narrow its query.
  • A single tool result over cap → write it to a reference and return a handle plus a summary. This is the most common overflow and the easiest to fix.
  • History over its residual → compact the oldest span, having already extracted anything worth keeping into state or notes.
  • State over cap → escalate. Something is wrong with the run's structure.
  • Constraints and the current task → never dropped, under any pressure. If the budget can't fit them, the run should fail rather than proceed without them.

🔍 Instrument the allocation

Log per-claimant token counts on every turn:

turn 14   sys 1,020 | tools 1,980 | state 1,340 | retr 9,800
          hist 15,200 | turn 890   | total 30,230 / 32,000

Three things become visible immediately: which claimant is growing, when compaction fired, and whether retrieval is using its cap or wasting it. A jump in any column is a defect with an address — and this is the single most useful instrument for long-run debugging, because it turns "it lost the plot" into "retrieval took 18k on turn 12."

💡 The anti-pattern

Filling the window because it's available. A larger window removes the forcing function that was producing better runs — when space was scarce, someone had to decide what mattered, and that discipline was worth more than the constraint cost.

Set the working budget at a level that forces the decision, independent of what the window permits. If a bigger model arrives with more room, that's headroom for the occasional large task, not a reason to raise every allocation.

The takeaway

Six claimants compete for one space, and unallocated space gets taken by whichever tool returned something large. Fix the shares: prompt and tools first for cache stability, state capped and regenerated, retrieval hard-capped so relevance is forced, the current turn always reserved, and history as the residual that compacts to fit. Then log the per-claimant counts each turn — that table is where long-run failures become visible before they become mysterious.

Keep reading

Similar posts

Matched on shared tags and category — the more bars, the stronger the overlap with what you just read.