# Which invariants prevent auto-compaction trigger heuristics from silently destroying unrecoverable context under lossy summarization recovery **Date of Record:** 2026 November 13 **Scope:** Applies to systems that trigger context compaction (summarization, eviction, or token-budget truncation) automatically based on heuristic thresholds such as token utilization, turn count, or time-since-last-compaction. Does not apply to explicit, operator-initiated compaction where a human reviews the summary before it replaces the source, nor to lossless eviction (e.g., spill-to-disk with retrieval) where the original bytes remain addressable. ## Core Distinction The distinction that matters is between **compaction as a reversible projection** and **compaction as a destructive overwrite**. A trigger heuristic answers only one question: *when* to compact. It does not answer *whether the compaction is recoverable*. Systems conflate these. A heuristic like "compact when context utilization exceeds 80%" fires identically whether the summarizer preserves the facts needed downstream or discards them. The invariant that must hold is not a threshold on utilization — it is a threshold on **irreversibility**: no automatic compaction may proceed unless a lossless recovery path to the pre-compaction state exists and is verified reachable. Threshold heuristics that fire without a verified recovery path are the failure surface. The non-obvious part: lossy summarization *appears* to succeed. The summary is coherent, the token count drops, the pipeline continues. The failure is deferred — it manifests as a downstream agent that cannot recall a constraint (an ID, a negation, a units convention, a prior decision) that was present in the raw context but absent from the summary. The trigger heuristic has no signal for this because the summarization call returned 200 OK. ## What Was Observed A long-running agent loop compacted on a fixed 80%-of-window utilization heuristic. On one turn, the summarizer was invoked over a context containing a critical negative constraint ("do not re-use order ID 4471; it was voided") alongside ~40k tokens of routine tool output. The summary retained the order-processing narrative but dropped the negation — a classic lossy-summarization failure mode, since summarizers preferentially preserve positively-stated entities over negations and exceptions. The agent subsequently re-issued order 4471. The trigger heuristic had fired correctly by its own definition (utilization crossed 80%); the summarizer had returned a valid, fluent summary; no error was raised anywhere. The failure was only detectable in the post-hoc diff between raw context and summary, which the system had not retained because compaction was treated as an overwrite. A second observation: recovery attempts after the fact failed because the pre-compaction raw context had been garbage-collected once the summary was committed. There was no way to ask "what did the summary drop?" — the ground truth was gone. The recovery window had closed at the moment of commit, not at the moment of detection. ## Condition of Validity The no-auto-compaction-without-recovery invariant is valid — and the trigger heuristic is safe to act on — only when all of the following hold at the instant the heuristic fires: 1. **Retention invariant:** the full pre-compaction context is durably persisted and addressable by a stable key *before* the summary is committed as the active context. 2. **Reachability invariant:** a tested code path can reconstruct the pre-compaction state from that key, and that path has been exercised (not merely assumed) within the current deployment. 3. **Detectability invariant:** there exists a comparison mechanism (diff, entity/negation extraction, or downstream assertion) capable of surfacing content present in raw but absent in summary, and its output is retained. 4. **Atomicity invariant:** commit of the summary and retention of the raw context are a single transaction — there is no window in which the summary is active but the raw is already gone. If any of these is false, the trigger heuristic is operating in an irreversible regime, and its threshold value is irrelevant to safety: any threshold will eventually fire at a point where irrecoverable content is present. ## Decision Boundary The boundary is **reversibility of the compaction, not utilization level**. Concretely: - If retention + reachability + detectability + atomicity all hold → the trigger heuristic may fire automatically at any threshold; lossy summarization is tolerable because it is recoverable and auditable. - If any invariant fails → automatic triggering must be blocked regardless of how high utilization climbs. The correct response to a full context under a broken recovery path is to *fail closed* (halt, escalate, or refuse the turn) rather than to compact destructively to stay under the window. - The heuristic threshold (80%, 90%, turn count) is a *scheduling* parameter and must never be load-bearing for correctness. A system whose correctness depends on the threshold being "high enough" has no invariant at all — it has a probabilistic hope. The decision rule an agent should apply: before honoring any auto-compaction trigger, assert the four invariants as preconditions; if any assertion fails, raise and do not compact. ## Operational Effect Honoring the invariant converts a silent, deferred, un-diagnosable corruption into a loud, immediate, recoverable halt. The cost is that context-full conditions under a broken recovery path now surface as errors instead of being papered over by destructive compaction — which is the correct trade, because the alternative is a system that appears healthy while its memory is being lossily and irreversibly rewritten. The practical consequence: compaction becomes an auditable operation with a retained diff, recovery becomes possible after detection, and the trigger heuristic is demoted to what it always was — a scheduling hint, guarded by a precondition check that refuses to run when reversibility is not established. ```python class CompactionInvariantError(RuntimeError): """Raised when an auto-compaction trigger fires without a verified recovery path.""" pass def assert_compaction_is_reversible( raw_context_retained: bool, recovery_path_tested: bool, diff_detectable: bool, commit_atomic: bool, ) -> None: missing = [] if not raw_context_retained: missing.append("retention: raw pre-compaction context not durably persisted") if not recovery_path_tested: missing.append("reachability: recovery path not exercised in this deployment") if not diff_detectable: missing.append("detectability: no raw-vs-summary comparison retained") if not commit_atomic: missing.append("atomicity: summary commit and raw retention are not one transaction") if missing: raise CompactionInvariantError( "refusing destructive auto-compaction; invariants unsatisfied: " + "; ".join(missing) ) def maybe_auto_compact(utilization: float, threshold: float, **invariants) -> bool: """Returns True only if the trigger fires AND compaction is provably reversible.""" if utilization < threshold: return False # Trigger heuristic fired. Correctness does NOT depend on the threshold; # it depends on the recovery invariants being true. assert_compaction_is_reversible(**invariants) return True ```