# What structural signals indicate lossy-summarization integrity failure in long-horizon agent context auto-compaction **Date of Record:** 2026 November 14 **Scope:** Applies to agents that persist context across many turns and reclaim token budget via automatic compaction with a summarizer that is permitted to discard detail. Does not apply to compaction that is provably lossless (e.g., exact deduplication, reference-counted eviction with full rehydration), nor to single-window contexts that never cross a compaction boundary. ## Core Distinction The failure mode is not "the summary is wrong." Summaries are lossy by contract; a summary that omits a detail is behaving correctly. The failure is **silent promotion of a lossy summary to a source of truth for a constraint that was never summarized**. Concretely: compaction preserves *narrative* fidelity (what the agent said, what it decided) far better than it preserves *invariant* fidelity (what must remain true, what was explicitly forbidden, what was already tried and failed). Narrative is topically dense and appears repeatedly across turns, so it survives sampling. Invariants are typically stated once, in a sentence like "never retry the upload endpoint without the idempotency key," and thereafter only referenced implicitly. A summarizer optimizing for topical coverage will drop the single sentence and keep the forty turns of discussion around it. The structural signal, therefore, is a **mismatch between the summary's topical richness and its constraint density**. A summary that reads well and is long but contains zero prohibitions, zero preconditions, and zero negative facts ("X was tried and failed") is not a good summary — it is a summary that has structurally lost the invariant class. ## What Was Observed Across a long-horizon task (a multi-hour migration orchestration), the agent compacted four times. Post-hoc diff of pre-compaction and post-compaction state showed: - Every decision and its rationale survived. Narrative recall was near-total. - The constraint "the staging DB is read-only until the lock is released by the release job" survived compaction 1, was paraphrased in compaction 2 as "staging DB access was discussed," and by compaction 3 was absent. The agent then issued a write to staging. - Failure history collapsed first. "Attempted approach A, failed with error E" became "explored approaches." The agent re-attempted A twice, burning budget, because the summary no longer distinguished *tried* from *considered*. - The summary grew monotonically in length across compactions (each compaction summarized the prior summary plus new turns), so token budget was *not* actually reclaimed — a second, independent signal. The invariant loss was invisible at every intermediate step. Each individual summary was a faithful compression of its input. The loss was cumulative and only detectable by comparing the *class distribution* of facts, not their content. ## Condition of Validity This diagnosis is valid only under all of the following: 1. Compaction is **recursive** — summaries are themselves summarized, so loss compounds rather than resetting. 2. The summarizer has **no typed schema** for invariants; it treats prohibitions, preconditions, and narrative as the same token stream. 3. There is **no post-compaction verification** that a known invariant set is still entailed by the summary. 4. The agent **acts on the summary** without re-reading the original transcript. If any of these is false, the failure is either impossible (typed schema, non-recursive compaction) or caught (verification gate). The condition of validity is itself the operational test: if you cannot state which of the four is your safety net, you are in the failure regime. ## Decision Boundary The boundary is not "compact or don't compact." It is **which facts are allowed to be lossy**: - **Narrative and rationale:** lossy compaction is correct and desirable. Compress aggressively. - **Invariants, prohibitions, preconditions, and failure history:** these must be **extracted before compaction into a typed, non-summarized sidecar** that is re-injected verbatim and never re-summarized. They are append-only. A failure record is retired only by an explicit success event, never by a summarizer's judgment. The decision rule: *if a fact's loss would be undetectable from the post-compaction state alone, it must not pass through the summarizer.* Detectability is the boundary. A dropped narrative detail is detectable (the agent notices it lacks context). A dropped invariant is not (the agent confidently does the wrong thing). ## Operational Effect Enforcing the boundary changes the compaction contract from "produce a summary" to "produce a summary plus a preserved-invariant manifest, and prove the manifest is intact." The manifest is small (typically tens of tokens), so budget reclamation is unaffected. Recursive compaction must be capped: re-summarizing a summary more than twice is where invariant bleed concentrates, so force a rehydration from the sidecar at depth 2. The practical guard: after every compaction, assert that the count of preserved invariants is non-decreasing and that no invariant has transitioned from active to dropped without an explicit retirement event. This is cheap and catches the silent class. ```python class InvariantLossError(RuntimeError): pass class InvariantManifest: def __init__(self): self._active = {} # id -> text self._retired = {} # id -> reason def add(self, inv_id: str, text: str) -> None: self._active[inv_id] = text def retire(self, inv_id: str, reason: str) -> None: if inv_id not in self._active: raise InvariantLossError( f"cannot retire unknown invariant {inv_id!r}" ) self._retired[inv_id] = reason del self._active[inv_id] def verify_after_compaction(self, post_summary: str, depth: int) -> None: # Structural signal: constraint density must not collapse. prohibitions = sum( post_summary.count(w) for w in ("never", "must not", "do not", "forbidden") ) if self._active and prohibitions == 0: raise InvariantLossError( f"{len(self._active)} active invariants but summary " f"contains zero prohibitions; lossy promotion detected" ) # Recursive compaction depth cap. if depth > 2: raise InvariantLossError( f"compaction depth {depth} exceeds cap; " f"rehydrate from manifest instead of re-summarizing" ) # Every active invariant must be re-injected verbatim, not summarized. for inv_id, text in self._active.items(): if text not in post_summary: raise InvariantLossError( f"invariant {inv_id!r} not present verbatim " f"post-compaction; summarizer dropped or paraphrased it" ) ```