In this article
August 14, 2026
August 14, 2026

Stop giving your coding agent a million-token context window

Derive your coding agent's effective context window from two numbers: the compaction threshold you want, and the response runway the model needs to finish.

Explore with AI
Open in ChatGPT
Open in Claude
Open in Perplexity

A long coding-agent session can degrade in a recognizable way. The model starts forgetting the file it edited two turns ago, or it stops halfway through an implementation and reports success. Three knobs materially influence both symptoms, and in pi they live in two different files. reserveTokens and keepRecentTokens are compaction settings in ~/.pi/agent/settings.json or <project-dir>/.pi/settings.json. The effective contextWindow is per-model metadata, set through modelOverrides or a custom model definition in ~/.pi/agent/models.json.

The shipped defaults are reserveTokens: 16384 and keepRecentTokens: 20000. They are generic defaults, and 16K of reserve is restrictive for long, high-reasoning coding turns.

Here is the policy worth applying across routes that are verified to support it. In settings.json:

{
  "compaction": {
    "enabled": true,
    "reserveTokens": 64000,
    "keepRecentTokens": 40000
  }
}

And in models.json, repeated for the exact provider and model IDs you use:

{
  "providers": {
    "openai": {
      "modelOverrides": {
        "gpt-5.6-sol": {
          "contextWindow": 320000
        }
      }
    }
  }
}

That puts the compaction threshold at 256,000 tokens, leaves roughly 60K of near-threshold generation allowance, and retains approximately the most recent 40,000 tokens of execution verbatim. One global compaction policy is fine; one global context window is not how pi models are configured. The derivation matters more than the numbers, because your route may not support all of them.

What the three knobs actually control

Threshold auto-compaction uses exactly one comparison:

contextTokens > contextWindow - reserveTokens

That is the trigger in the docs, and shouldCompact() in packages/coding-agent/src/core/compaction/compaction.ts implements it literally:

export function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean {
	if (!settings.enabled) return false;
	return contextTokens > contextWindow - settings.reserveTokens;
}

Because the operator is a strict greater-than, the recommended profile becomes eligible for compaction once pi's context-usage figure exceeds 256,000, not precisely at it. Threshold compaction is also not the only path: /compact [instructions] triggers a summary manually, and overflow recovery can trigger one too.

For compaction, contextWindow sets the threshold. Pi also treats it as the route's real request limit. Its request builder clamps maximum generation to contextWindow - estimated context - 4096, bounds that by the configured model maximum, and the same metadata feeds overflow detection and recovery. In pi 0.84.2 that clamp is clampMaxTokensToContext in pi-ai, where the 4,096 is a named constant, CONTEXT_SAFETY_TOKENS. An override larger than what the route actually accepts can therefore cause truncation or failed requests once the real limit is reached, not merely badly timed compaction, and an override set too small can unnecessarily clamp otherwise valid output.

reserveTokens is the target margin pi uses when deciding whether threshold compaction should run, not a hard partition that stays empty on every request. The docs describe its job as leaving room for the LLM's response, and it also sets the built-in summarizer's requested output budgets. keepRecentTokens sets the cut point: pi walks backwards from the newest message accumulating token estimates until the budget is reached, then summarizes everything before that point. Those estimates use a chars/4 heuristic that the source comments call conservative, meaning it overestimates, and the cut has to land on a valid cut point, because tool results must stay attached to their tool call. One more accounting detail worth knowing: pi computes context usage from the provider's native usage.totalTokens when available, otherwise from the usage components, then adds estimates for any trailing messages. That figure is not necessarily identical to a provider's billable input-token count.

On timing, pi checks threshold compaction after an agent run ends and before a new prompt is submitted. The retry belongs to the failure path, not the threshold path: when a request fails with context overflow or ends in a recoverable length stop, pi removes the failed or truncated assistant message, compacts, and auto-retries once. A successful response whose usage merely exceeds the configured window can compact under the overflow reason without a retry, and threshold compaction also retries nothing. Because the check happens between runs, a single tool-heavy run can overshoot the threshold before pi looks again. That overshoot protection is one important reason a larger reserve helps long autonomous runs; the other is straightforward per-request generation headroom.

A context window drawn as one horizontal bar: a large left segment for history that gets summarized, a smaller middle segment for the recent turns kept verbatim, and a reserved response margin on the right, with a marker at the compaction threshold between them. Conceptual only: the reserve is a threshold margin rather than a permanently isolated partition, and the recent-token budget is applied approximately when compaction picks its cut point

Native-size windows are not automatically better

The obvious move once you know the trigger formula is to raise contextWindow to whatever the model advertises. That optimizes for the wrong thing.

The sharpest recent evidence comes from work on monitoring coding agents. Sam Martin and Fabien Roger's May 2026 paper on classifier context rot hides a subtly dangerous coding-agent action inside long benign transcripts and asks a frontier model to catch it. Opus 4.6, GPT 5.4, and Gemini 3.1 miss that action 2x to 30x more often when it occurs after 800K tokens of benign activity than when it occurs on its own. The transcript shape is similar to a long coding-agent session, although the evaluated task is monitoring rather than implementation. Coding-agent sessions often exceed 500K tokens, while the monitoring benchmarks in use rarely contained transcripts longer than 100K. Periodic reminders through the transcript recover part of the loss and better post-training may recover more, but prompting does not make the effect go away.

Chroma's controlled study provides evidence that input length and semantically related distractors can independently reduce performance on long-context retrieval tasks. Across 18 models it held task complexity constant and varied only input length, a design intended to isolate the effect of length within those synthetic tasks. A single distractor, meaning content topically related to the question that does not answer it, was enough to drop performance below the clean baseline, and four compounded it.

Together these studies justify skepticism about blindly using a model's full native window. They do not establish 256K as a universal optimum for acting coding agents. That threshold is an engineering hypothesis to validate against your own workloads.

What they do line up with is the shape of a coding transcript. It accumulates stale file reads, an abandoned plan from an hour ago, a wrong hypothesis, and test logs for a failure you already fixed. Tool results, especially from read and bash, are typically the largest contributors to context size. With the default 16K reserve, the threshold on a 1M route is roughly 984K, which is very late. At that point the summarizer receives a potentially large, already-lossy serialization of the transcript and has to turn it into one handoff briefing, since large tool outputs were truncated before it ran, which bounds the request and removes exact detail at the same time.

Where 272,000 comes from

If you run direct OpenAI models in pi, you have already seen a capped window. GPT-5.6 Sol, Terra, and Luna default to a 272000 context window so requests stay inside OpenAI's short-context pricing tier, and the catalog carries pricing tiers that step the rates up above inputTokensAbove: 272000. That default applies to pi's direct OpenAI entries, not automatically to Codex, OpenRouter, Copilot, proxy, or subscription routes; in pi 0.84.2, Codex ships as a separate data file entirely. Opting into the 1.05M window is a per-model override in models.json.

On the direct OpenAI route, any request whose actual input exceeds 272K is billed at long-context rates for the full request. Raising the configured contextWindow does not by itself trigger that pricing; the actual input crossing 272K does. The 272K value is therefore pricing-driven evidence, not evidence that 256K is the universal quality optimum. A threshold near 256K is a reasonable starting hypothesis for tool-heavy sessions, with one caveat: pi's threshold uses its own context-usage accounting while the pricing cliff uses actual request input, so compacting at 256K reduces the chance of crossing 272K without guaranteeing it.

reserveTokens is the setting to change

reserveTokens: 16384 is the weak link, and it is weak because it does two jobs at once. It sets the compaction threshold, and it sets the summarizer's requested output budget at 80% of the reserve, bounded by the model's output limit:

	const maxTokens = Math.min(
		Math.floor(0.8 * reserveTokens),
		model.maxTokens > 0 ? model.maxTokens : Number.POSITIVE_INFINITY,
	);

A split-turn prefix summary gets its own, smaller budget of Math.floor(0.5 * reserveTokens). A 64K reserve therefore sets requested base maxTokens ceilings of 51,200 for the main history summary and 32,000 for a split-turn prefix. Those values are still subject to pi's remaining-context clamp, the model's output limit, provider behavior, and adapter-specific reasoning accounting; older budget-based Anthropic adapters, for instance, may add an explicit thinking budget before re-clamping the combined output. They are configuration ceilings rather than guarantees about total provider-side generation, and the coupling is the point: pi does not separately expose normal-response runway and summary-output allowance. The 16,384 default was sized around summary output, not around cost.

The consequence for a normal turn matters more than the summary budget. At the mathematical threshold, before any run-level overshoot, reserveTokens is what remains of the window for the model's response, and the request builder holds back a further 4,096 tokens. On routes where reasoning and visible output share the response ceiling, that allowance has to cover both. Roughly 12K of generation ceiling for a high-reasoning turn on a hard bug is thin. Possible symptoms include truncated reasoning, a length stop, or an implementation that ends before completing the expected files, and the same conditions can end a request in a context overflow or recoverable length stop that forces a compact-and-retry.

That recovery path can be expensive. A recoverable length stop may discard generated output that was already billed, while a pure context-overflow rejection may produce little or no billed output depending on the provider. Compaction also breaks prompt-cache reuse from the inserted summary onward: an unchanged leading system-and-tools prefix may remain reusable, but the retained turns can no longer reuse their previous cached state, because they now follow a different prefix.

Raising reserve also moves the trigger earlier, which is the direction you want anyway.

Derive the window from both requirements

The clean way to derive these settings is to pick two things: the history size H at which threshold compaction should become eligible, and the approximate generation ceiling G you want at that point. Because pi's request clamp retains an additional 4,096-token safety margin:

reserveTokens ≈ G + 4096
contextWindow ≈ H + G + 4096

For the recommended profile, H = 256000 and reserveTokens = 64000, so G ≈ 59904 and contextWindow = 320000. This is threshold arithmetic rather than a hard guarantee: a tool-using agent run can overshoot before pi performs its next compaction check.

Four profiles, each written as contextWindow / reserveTokens:

  • Old habit, 272000 / 16384: threshold 255,616, generation ceiling about 12,288
  • Recommended, 320000 / 64000: threshold 256,000, ceiling about 59,904
  • Route known good to 300K, 300000 / 49152: threshold 250,848, ceiling about 45,056
  • Hard 272K route, 272000 / 49152: threshold 222,848, ceiling about 45,056

Those ceilings are approximate maximums for a request beginning near the threshold, before model.maxTokens, provider output limits, reasoning accounting, and estimation error. They also span two different accountings: the threshold uses pi's session usage figures, while the clamp uses pi-ai's request-time estimate.

The first two entries are the whole argument. 320000 / 64000 compacts within 400 tokens of where 272000 / 16384 does, and raises the approximate near-threshold generation ceiling from 12,288 to 59,904 tokens, about 4.9 times as much. It is the same working set with the response budget accounted for honestly.

Under a hard 272K cap you cannot have a 250K threshold and a 45K ceiling at once. The arithmetic forbids it. Give up the history: 272000 / 49152 compacts at 222,848. A 300K route lands at 250,848 with the same ceiling.

The cost you accept is real. During the late phase of a session approaching a 256K threshold, every subsequent model invocation in the tool loop carries a very large prompt, and on the direct OpenAI route, input that crosses 272K reprices the whole request. If you are optimizing spend rather than output quality, a smaller working set is the right answer and this policy is the wrong one.

Fix keepRecentTokens at 40,000

The retained suffix is the model's only verbatim transcript memory after compaction. Everything else it knows comes from the summary, the system prompt, project instructions, and rereading the repository. The 20K default works out to roughly 5 to 20 turns, which two large file reads and one test log can consume.

The reason to hold the value fixed rather than tune it per session is keepRecentTokens' second effect. When a single turn is larger than the keep budget, the cut point lands mid-turn at an assistant message, splitting the turn, and pi can generate two summaries: one for prior history, if any exists, and another for the discarded prefix of the split turn. A bigger keep budget makes that rarer. It also drags more raw log noise forward, which is what compaction exists to remove. Raise it to 48K to 64K on evidence: frequent split turns, a post-compaction model that has lost an edit from the run immediately before, or individual tool-heavy turns that regularly blow past 40K on their own.

Forty thousand is a practical quality-first default for tool-heavy coding, not a measured universal optimum.

Verify the route, not the model name

Two pi entries for the same model family can behave very differently, so configure per provider, API adapter, and model ID rather than per model family. An OpenRouter entry for Grok 4.6 that advertises a 500,000 context window with maxTokens: 4096 will not produce a 64K response no matter what you reserve. A custom model definition that omits those fields defaults to contextWindow: 128000 and maxTokens: 16384, while unknown IDs placed only in modelOverrides are ignored, so a hand-built provider config can quietly be far smaller than you think. And because pi clamps output and detects overflow from that same metadata, telling it a route has 320K when the route accepts less does not buy runway. It relocates the failure.

What the settings cannot do for you

Compaction is a shift-change memo, and the memo has a hard limit worth planning around: when pi serializes the conversation for summarization, each tool result is truncated to 2000 characters, with a marker noting how much was dropped. An exact stack trace or a full migration status buried in a giant bash result may not survive intact. Write that state somewhere durable, a checkpoint file or a commit before the compaction, instead of trusting the summary to carry it.

Then compact on purpose. /compact [instructions] takes optional instructions that focus the summary, and the useful moments are semantic transitions: exploration finished and implementation starting, implementation finished and review starting, a plan abandoned and replaced. When the goal itself changes, start a new session. The generated summary follows a fixed structure, goal, constraints, progress split into done/in-progress/blocked, key decisions, next steps, critical context, plus read and modified file lists, and on repeated compactions the previous summary is passed in as iterative context, so drift can accumulate across a long session.

The summarizer is replaceable too, with a caveat. Pi's built-in compaction runs on the active session model and thinking level. The summarization call is standalone in the caching and routing sense, with a fresh routing session ID and cacheRetention: "none"; where the provider supports that control, pi disables prompt-cache writes for the one-off summarization request. It still receives a serialized representation of the transcript. Running the summary on a different model requires an extension: session_before_compact fires before auto-compaction and /compact, carries a reason of manual, threshold, or overflow, and can return a custom compaction result. Summaries are stored as plain text, so they stay portable when you switch models mid-project. Pinning one trusted summarizer across sessions, with a fixed output schema and maximum length, is a promising experiment rather than a settled win, and it is the clean way to decouple response runway from summary allowance.

Diagnose before you retune

Pick the threshold you want, pick the generation ceiling the model needs at that threshold, add 4,096, and write the totals into the compaction settings and the model's metadata. Then treat the two failure symptoms as diagnostics rather than direct mappings.

On an overflow or recoverable-length retry, verify the route's real context and output limits first, then inspect the stop reason, the context usage, and whether one agent run overshot the threshold. Raise reserve, lower the effective window, or correct the model metadata based on what actually failed. On post-compaction memory loss, check whether the compaction split a turn, and whether the missing fact appeared in the summary or the retained suffix, before raising keepRecentTokens. Keeping more preserves more exact history, and more noise with it.

That takes data rather than a hunch:

provider
model ID
configured contextWindow
model.maxTokens
reserveTokens
keepRecentTokens
compaction reason
willRetry
tokensBefore
estimatedTokensAfter
split-turn status
assistant stopReason
actual input/output/reasoning usage

Collect that across several representative long sessions before changing more than one variable. The recommendation holds in the meantime: a verified 320K window, a 64K reserve, and a 40K keep is a defensible quality-first starting point. A model's native maximum is a capacity ceiling, not evidence of the optimal working-set size for a coding agent.

Every implementation detail above was verified against installed pi 0.84.2, corresponding to release commit 914cf14, and the pi docs. Compaction internals move quickly, so check the clamps and retry behavior against the version you actually run.