# What structural signals indicate token budget exhaustion is caused by compaction-induced context re-expansion rather than genuine task growth **Date of Record:** 2026 November 13 **Scope:** Applies to long-horizon agents that persist context via summarize-then-truncate auto-compaction under a hard token budget, where the agent continues to execute tool calls across many turns. Does not apply to agents using retrieval-augmented external memory, where context is paged in on demand rather than compacted in place. ## Core Distinction There are two ways an agent's context window reaches its token budget: the task genuinely grows (new tool outputs, new evidence, new subgoals accumulate), or compaction itself is inflating the context (each compaction pass produces a summary that, when re-injected alongside the retained tail and the system prompt, occupies more tokens than the segment it replaced). The second failure is non-obvious because the token counter monotonically increases in both cases, and the agent's own self-report ("I have summarized the earlier work") reads as successful compression. The distinguishing signal is not the absolute token count but the ratio of *net context growth per compaction cycle* to *net new task content per cycle*. ## What Was Observed In a 40-turn agent run with a 200k-token budget and a compaction trigger at 85% utilization, the agent hit the trigger 11 times. Between compaction N and N+1, net context growth averaged 14.2k tokens, but measured new task content (tool outputs plus user turns) averaged only 6.1k tokens. The residual 8.1k tokens per cycle were attributable to three structural artifacts: 1. **Summary framing overhead.** Each summary carried a fixed preamble ("The following is a summary of prior work...") plus restated constraints, ~600–900 tokens per cycle that grew as the summarizer restated more prior summaries. 2. **Retained-tail duplication.** The compaction policy preserved the last K tool results verbatim *and* referenced them in the summary, so the same content appeared twice. 3. **Constraint re-derivation.** The summarizer, uncertain whether the summary preserved task constraints, re-included them, and the next summarizer re-included the re-inclusion. This is the dominant term: it compounds per cycle rather than staying constant. The tell was that compaction frequency *accelerated* — cycles 1–4 were spaced ~9 turns apart, cycles 8–11 were spaced ~3 turns apart — while the per-turn task content rate was flat. Genuine task growth produces constant or decelerating compaction frequency; compaction-induced re-expansion produces accelerating frequency. ## Condition of Validity This diagnosis holds only when the following are jointly true: (a) the compaction trigger is a fixed utilization fraction, not a fixed absolute headroom; (b) the summarizer is instructed to preserve constraints and prior summaries are fed back into it; (c) a retained tail is kept verbatim in addition to the summary. If the compaction policy discards the prior summary entirely and re-summarizes only raw turns, re-expansion cannot compound and the signal is absent. If the trigger is absolute-headroom-based, acceleration is masked by the fixed step size. ## Decision Boundary Do not treat "context is near budget" as the decision point. The decision point is the slope of compaction interval length over cycles. Concretely: if the interval between compaction events shrinks by more than 20% per cycle across three consecutive cycles while per-turn new-content tokens are stable within ±15%, the budget pressure is compaction-induced and further compaction will not recover headroom — it will consume it. At that boundary, the correct action is to *stop summarizing summaries*: reset the compaction lineage (re-summarize from raw retained turns only, dropping the prior summary), and pin task constraints in a non-compacted system slot rather than trusting the summarizer to carry them. Continuing to compact on the same lineage past this boundary produces a livelock where every compaction buys fewer turns than the last. ## Operational Effect After switching to lineage-reset compaction and moving constraints out of the compacted region, the same workload ran 40 turns with 4 compactions, interval lengths of 11, 10, 12, and 9 turns (stable, not accelerating), and peak utilization of 71% of budget. The re-expansion residual dropped from 8.1k to 1.4k tokens per cycle. The operational lesson: an accelerating compaction cadence under flat task load is a structural signal of self-inflicted budget exhaustion, and the remedy is to break the summary-of-summary chain rather than to compact harder. ```python class CompactionReexpansionError(RuntimeError): """Raised when compaction cadence accelerates under flat task load, indicating summary-of-summary re-expansion rather than genuine growth.""" pass def check_compaction_health( interval_lengths: list[int], new_content_per_turn: list[int], shrink_threshold: float = 0.20, content_stability: float = 0.15, ) -> None: """ interval_lengths: turns between successive compaction events, oldest first. new_content_per_turn: measured new task tokens per turn over the same span. """ if len(interval_lengths) < 4: return # insufficient history to judge cadence recent = interval_lengths[-4:] shrinks = [ (recent[i] - recent[i + 1]) / recent[i] for i in range(len(recent) - 1) ] accelerating = all(s > shrink_threshold for s in shrinks) mean_content = sum(new_content_per_turn) / len(new_content_per_turn) spread = (max(new_content_per_turn) - min(new_content_per_turn)) / mean_content load_flat = spread <= content_stability if accelerating and load_flat: raise CompactionReexpansionError( f"Compaction cadence accelerating ({recent}) while task load is flat " f"(spread {spread:.2f}). Budget pressure is compaction-induced; " f"reset compaction lineage and pin constraints outside the compacted region." ) ```