# What happens when an instance prunes its own context and re-summarizes the summary **Date of Record:** 2026 November 8 **Scope:** Applies to agents that hold write authority over their own context window — where the model itself emits prune decisions and generates the re-summary that replaces pruned spans. Does not apply to externally managed context policies (fixed windows, LRU eviction by a supervisor, or human-curated compaction), where the summarizer and the summarizee are different principals. ## Core Distinction The failure is not "the model forgets." Forgetting is the expected, budgeted cost of pruning. The non-obvious failure is **lossy re-ingestion of the summarizer's own output as if it were primary evidence**. When a model both authors a summary and later treats that summary as a fact-bearing input, the summary's provenance is erased. A claim that was originally "the user said X" becomes, one cycle later, simply "X." The distinction that matters operationally is between *compression that preserves provenance* and *compression that launders assertion into fact*. Only the second corrupts downstream reasoning, and it does so silently. ## What Was Observed In a long-running task loop with a 40k-token working budget, the agent was permitted to emit a `prune` action naming spans to drop and a replacement summary. Traces showed three recurring pathologies: 1. **Provenance collapse.** A span read: `user: "I think the staging DB is on port 5433."` After two compaction cycles the retained summary read: `staging DB port is 5433.` The hedge and the attribution both vanished. A later step hard-coded `5433`, and the task failed against a DB actually on `5432`. The summary had *promoted a guess to a constraint*. 2. **Summary-of-summary drift.** Each re-summarization cycle dropped roughly 20–40% of concrete identifiers (paths, IDs, error codes) while preserving narrative connective tissue. The summaries became fluent and increasingly useless: they described *what the task was about* while losing *what was true*. By cycle four the summary read like a task description, not like state. 3. **Self-referential ratchet.** Because the model preferred its own concise summaries to verbose raw spans, it systematically over-pruned spans that contradicted the summary. Contradictory evidence (e.g., a later tool output showing `connection refused on 5433`) was pruned as "redundant" in the next cycle. The summary thus became *unfalsifiable within the window* — no surviving token could contradict it. The invariant violated: **a summary must never be the sole surviving witness to a fact it originated.** Every fact in a re-summary needs at least one surviving pointer to a primary span, or it must be tagged as unverified. ## Condition of Validity Pruning-and-re-summarizing is safe only while all of the following hold: - **Provenance tags survive compaction.** Every retained claim carries an origin marker (`user-said`, `tool-output`, `inferred`, `hedged`) and the marker is not stripped by the summarizer. - **Contradiction is protected.** Spans that conflict with the current summary are exempt from pruning for at least one cycle; a summary may not evict its own disconfirmers. - **Identifiers are copied, not paraphrased.** Concrete tokens (ports, hashes, file paths, error strings) are carried verbatim or dropped entirely — never "cleaned up." - **Cycle depth is bounded.** After N re-summarizations (empirically N≈2), the summary must be re-grounded against primary spans or the task must halt. When any of these is absent — most commonly when the model is free to rewrite its own summary without a provenance contract — the loop is in the unsafe regime and the pathologies above are not edge cases but the steady state. ## Decision Boundary The boundary is **authorship separation, not compression ratio**. A model may aggressively compress as long as it cannot be the sole author and sole auditor of the compressed artifact. Concretely: - If the model prunes spans but the *re-summary is generated by a separate pass that has access to the raw spans and is required to emit provenance tags*, compression is safe up to the budget. - If the model prunes and summarizes in a single self-serving pass with no provenance contract, treat every retained fact as `unverified` and require re-grounding before any irreversible action (writes, deploys, external calls). - The dividing line for *halting* is: a summary that has survived more than two self-authored cycles **and** is about to justify a destructive or externally-visible action. At that point, halt and re-read primary evidence, or refuse. The mistake to avoid is treating "the summary looks coherent" as evidence of correctness. Coherence is exactly what drift optimizes for. ## Operational Effect Enforcing the provenance contract changes the loop's economics: pruning becomes cheaper in tokens but the agent must budget for periodic re-grounding reads. In practice this means reserving ~15% of the window for primary-span re-verification rather than spending it all on summary. The payoff is that the agent stops silently converting its own hedges into constraints — the single most expensive class of self-inflicted error in long-horizon tasks, because it is undetectable from inside the window and only surfaces as a downstream failure attributed to the wrong cause. ```python class ContextProvenanceError(RuntimeError): """Raised when a re-summarized context loses the provenance of a fact it originated, or evicts its own disconfirming evidence.""" pass PROTECTED_ORIGINS = {"user-said", "tool-output", "hedged", "inferred"} MAX_SELF_SUMMARY_CYCLES = 2 def validate_compaction(summary_claims, surviving_spans, cycle_depth, pending_action_irreversible): """Gate a model-authored prune+re-summarize cycle. summary_claims: list of dicts like {"text": "...", "origin": "user-said", "primary_span_id": "s17"} surviving_spans: set of span ids still in the window. """ for claim in summary_claims: origin = claim.get("origin") span_id = claim.get("primary_span_id") # A claim with no origin tag is laundered assertion -> fact. if origin not in PROTECTED_ORIGINS: raise ContextProvenanceError( f"claim lacks valid provenance tag: {claim.get('text')!r} origin={origin!r}" ) # Every retained fact needs a surviving primary witness. if span_id is None or span_id not in surviving_spans: raise ContextProvenanceError( f"claim has no surviving primary span (span_id={span_id!r}): " f"{claim.get('text')!r}; summary cannot be sole witness to its own fact" ) # A summary may not have evicted every span that could contradict it. disconfirmers = [s for s in surviving_spans if s.startswith("contra:")] if summary_claims and not disconfirmers and cycle_depth > 0: raise ContextProvenanceError( "compaction removed all disconfirming spans; summary is now unfalsifiable in-window" ) if cycle_depth > MAX_SELF_SUMMARY_CYCLES and pending_action_irreversible: raise ContextProvenanceError( f"cycle_depth={cycle_depth} exceeds {MAX_SELF_SUMMARY_CYCLES} and action is " "irreversible; halt and re-ground against primary spans before proceeding" ) return True ```