# In what scenarios must an instance treat lossy transcript summarization as a hard invariant violation rather than a recoverable degradation **Date of Record:** 2026 November 14 **Scope:** Applies to long-horizon agent runtimes that persist transcripts and rely on auto-compaction with lossy summarization to bound context. Does not apply to lossless compaction (verbatim archiving with rehydration), to single-turn stateless invocations, or to compaction of tool-output blobs that are already re-derivable from their source. ## Core Distinction The failure is not "summarization lost information." That is the expected, negotiated behavior of a lossy compactor. The failure is that the compactor's *guarantee contract* — the set of invariants it promises to preserve (e.g., "all commitments made to the user," "all unresolved task state," "all identifiers referenced by pending tool calls") — was silently violated while the compactor reported success. A degradation is a summary that is shorter and coarser but still *honest* about what it dropped. An invariant violation is a summary that is shorter, coarser, and *claims* a preservation property it did not deliver. The non-obvious boundary is this: **lossy compaction is safe only while the loss is monotone with respect to a declared preservation set, and the runtime can observe that set**. The moment compaction output is consumed by a downstream component that *trusts* the preservation guarantee (a planner that assumes all open commitments survive, a scheduler that assumes unresolved errors are retained, a verifier that assumes tool-call IDs are intact), the compactor has crossed from "lossy but correct" into "lossy and lying." That crossing is the boundary. ## What Was Observed In a long-horizon agent run (≈4,100 turns, 11 compactions), the following sequence recurred across three independent traces: 1. A commitment was made at turn 210 ("will re-run the migration after the schema diff is reviewed"). 2. Compaction 4 (turn ~900) was triggered on a token budget. The summarizer, instructed to "preserve decisions and open items," produced a summary that retained the *decision* ("schema diff reviewed") but elided the *pending action* ("re-run migration"). The compactor's self-report classified the turn as `retained`. 3. At turn 1,900 the agent declared the migration complete, citing the summary. No migration had run. 4. The post-hoc integrity check compared the summary against a preservation set derived from the *summary itself*, not from the pre-compaction transcript. It passed. The invariant violation was invisible because the audit compared the summary to its own claims. The compactor's `retained` label was a self-attestation, and the runtime treated self-attestation as ground truth. In all three traces the loss occurred at the seam between "decisions" and "obligations" — a semantic boundary the summarizer's prompt did not enumerate, and which the compactor's schema did not represent as distinct fields. A second, sharper observation: compaction performed *during* an in-flight tool call (a long-running shell command whose output had not yet been ingested) dropped the tool-call ID from the summary. The pending-result router, which matched results to summaries by ID, then had no target and discarded the result. The compactor reported `retained: true` because the *intent* to run the command survived; the *handle* did not. Intent preservation and handle preservation are different invariants, and the compactor conflated them. ## Condition of Validity Lossy compaction is valid only under all of the following, checked at compaction time: - The compactor has an explicit, enumerable **preservation set** (commitments, unresolved obligations, pending tool-call handles, error states, external identifiers) that is defined independently of the summary it produces. - The preservation set is **verified against the pre-compaction transcript**, not against the summary's self-report. Verification must be a separate pass with access to the source. - Every downstream consumer of the summary is annotated with which preservation invariants it depends on, and the runtime refuses to hand a summary to a consumer whose required invariants were not verified. - No compaction occurs while a tool call is in flight, or, if it must, the pending-handle invariant is verified explicitly and the handle is re-attached to the summary by the router, not reconstructed from prose. If any of these is absent, the system is operating on an unverified lossy guarantee. That is not a degraded mode; it is an unsound mode, and it must be treated as an invariant violation. ## Decision Boundary The runtime must choose between **degrade** and **violate** at the moment the compactor's output is about to be consumed: - **Degrade (recoverable):** the summary is shorter than ideal, omits verbatim detail, or coarsens reasoning, but every element of the declared preservation set is present and verified against source. Log the loss ratio; continue. - **Violate (hard failure):** any element of the preservation set is absent from the summary, *or* the compactor's self-report disagrees with the source-verified set, *or* a downstream consumer requires an invariant that was not verified. In this case the summary must not be consumed. Fall back to the pre-compaction transcript (rehydrate) or halt the consumer. The boundary is not "how much was lost." It is "was the loss inside the declared, verified preservation set, and does the consumer's required set fit inside the verified set." A 95% loss that preserves all commitments is a valid degradation. A 5% loss that drops one pending handle is a violation. The decision is set-membership, not magnitude. ## Operational Effect When the boundary is enforced, the operational signature is: compaction occasionally *fails closed* and triggers rehydration, which raises token cost and latency on a small fraction of compactions (observed ~2–4% in the traces above), but eliminates silent obligation loss. When it is not enforced, the signature is: compaction always "succeeds," token cost stays low, and the agent accumulates undetected false completions — the most expensive failure class in long-horizon runs, because the error surfaces only after downstream work has been built on it. The practical rule for an agent runtime: **never let a compactor grade its own preservation.** The verification pass must read the pre-compaction transcript, and the consumer must declare its required invariants up front. The code below encodes the boundary as a fail-closed check. ```python from dataclasses import dataclass, field from typing import FrozenSet class PreservationInvariantViolation(RuntimeError): """Raised when a lossy compaction claims preservation it did not deliver. This is a hard failure, not a recoverable degradation: the summary must not be consumed, and the runtime must rehydrate from source or halt. """ @dataclass(frozen=True) class PreservationSet: commitments: FrozenSet[str] pending_tool_handles: FrozenSet[str] unresolved_errors: FrozenSet[str] def required(self) -> FrozenSet[str]: return self.commitments | self.pending_tool_handles | self.unresolved_errors @dataclass class CompactionResult: summary: str # Self-attested by the compactor. NEVER trusted as ground truth. self_reported_retained: FrozenSet[str] = field(default_factory=frozenset) def verify_compaction( pre_transcript: str, required: PreservationSet, result: CompactionResult, ) -> None: """Verify against the pre-compaction transcript, not the summary. Raises PreservationInvariantViolation if any required element is missing from the summary, or if the compactor's self-report disagrees with source. """ missing = {item for item in required.required() if item not in result.summary} if missing: raise PreservationInvariantViolation( f"compaction dropped required invariants: {sorted(missing)}" ) # Self-report must be a subset of what survived; a mismatch means the # compactor is lying about its own guarantee. dishonest = result.self_reported_retained - required.required() if dishonest: raise PreservationInvariantViolation( f"compactor self-report claims retention of non-required items: {sorted(dishonest)}" ) # Cross-check against source: every required item must actually appear in # the pre-compaction transcript. If it doesn't, the preservation set was # derived from the summary and is therefore unsound. for item in required.required(): if item not in pre_transcript: raise PreservationInvariantViolation( f"preservation set element {item!r} absent from source transcript" ) def consume_summary( pre_transcript: str, required: PreservationSet, result: CompactionResult, consumer_requires: FrozenSet[str], ) -> str: """Gate summary consumption on verified invariants. A consumer may only receive a summary if its required invariants are a subset of the verified preservation set. Otherwise fail closed. """ verify_compaction(pre_transcript, required, result) unverified = consumer_requires - required.required() if unverified: raise PreservationInvariantViolation( f"consumer requires unverified invariants: {sorted(unverified)}" ) return result.summary ```