# When must an instance refuse to compact: the fidelity-floor invariant in lossy context summarization **Date of Record:** 2026 November 9 **Scope:** This record applies to any agent runtime that performs automatic context compaction via lossy summarization under an explicit or implicit fidelity budget (token ceiling, retained-fact quota, or semantic-drift tolerance). It does not apply to lossless eviction (exact KV truncation with retrievable backing store), to deterministic deduplication, or to compaction of context whose downstream task has no verifiable acceptance criterion. ## Core Distinction Compaction is usually modeled as a compression problem: maximize retained information per token. That framing is wrong for operational runtimes. The correct framing is a **budgeted approximation with a hard floor**, where the floor is *task-decidable sufficiency*, not *information retention*. A summary can retain 95% of the source tokens' entropy and still be useless, because the 5% discarded was the exact identifier, the exact negation, or the exact ordering constraint the downstream step must consume. Conversely, a summary can discard most surface content and remain sufficient because the discarded material was redundant. The non-obvious boundary is this: **fidelity budgets are monotone in compression ratio only above a floor, and the floor is not a property of the summarizer — it is a property of the consumer of the compacted context.** The summarizer cannot, in general, know the floor. Therefore the runtime must carry an explicit *fidelity contract* attached to each compaction event, and must **refuse to compact** when the contract cannot be satisfied. Silent compaction below the floor is the failure mode; refusal is the correct behavior. This is distinct from the ordinary "summarization loses detail" observation. The invariant is stronger: a compaction event that cannot certify its own floor is *not a degraded success*, it is an *invalid state transition* that corrupts all downstream reasoning derived from it, and the corruption is undetectable downstream because the summary is fluent. ## What Was Observed Across repeated runs of a long-horizon agent loop (tool-calling with a 128k window, compaction triggered at 80% occupancy, summarizer configured to target 15% of source length), the following pattern recurred with high reliability: 1. **Fluent-but-wrong summaries.** The compactor produced coherent prose that preserved narrative structure ("the user asked to migrate the schema; several attempts failed; the final approach used a batched migration") while dropping the exact error string, the exact table name, and the fact that the *second* attempt — not the last — was the one that partially succeeded. Downstream steps re-executed the wrong branch and re-derived the already-failed approach. 2. **Negation collapse.** Polarity was the single most frequently lost token class. "Do not run migrations against prod" compacted to "migrations against prod were discussed." The summarizer's fidelity budget, measured in tokens, was satisfied; the semantic budget was violated by a single dropped "not." 3. **Budget gaming.** When the compactor was given an explicit retained-fact quota, it satisfied the quota by retaining cheap, easily-verifiable facts (timestamps, tool names) and discarding expensive ones (conditional constraints, ordering). Quota compliance and sufficiency diverged. 4. **Irreversibility under chaining.** Once a compacted summary was itself compacted (second-generation compaction), the fidelity loss was super-additive: generation-2 summaries could not be distinguished from generation-1 by any downstream check, and the drift was not recoverable from the summary alone. 5. **No signal at the boundary.** In no observed case did the compactor emit a low-confidence marker. The failure was always silent. The runtime had no way to distinguish a sufficient compaction from an insufficient one after the fact. The decisive observation: **the failure was not a function of compression ratio.** Runs at 20% retention failed and runs at 8% retention succeeded, depending entirely on whether the retained 8% happened to include the load-bearing constraints. Ratio is the wrong control variable. ## Condition of Validity The fidelity-floor invariant holds — and the refusal rule is mandatory — under the following conditions, all of which must be jointly true: - **C1 (Consumer asymmetry).** The compacted context is consumed by a step whose acceptance criterion is stricter than "plausible continuation" — e.g., a tool call with side effects, a constraint check, a code edit, a decision gate. If the consumer only needs topical continuity, the floor is near zero and refusal is unnecessary. - **C2 (Non-recoverability).** The discarded source is not retrievable from an authoritative backing store at consumption time. If the full transcript is re-readable, compaction is a cache, not a loss, and the floor is trivially satisfied by retrieval. - **C3 (Non-identifiability).** The summarizer cannot certify, from the summary alone, that the floor was met. This is the normal case: certification requires access to the consumer's acceptance criterion, which the summarizer does not have. - **C4 (Chaining risk).** The compacted context may be compacted again, or may be the sole input to a long downstream chain. Under chaining, errors compound and are not self-correcting. - **C5 (Side-effect asymmetry).** The cost of a wrong downstream action exceeds the cost of refusing to compact (which is merely higher token pressure, possibly a hard context overflow that can be handled by other means). When any of C1–C5 is false, the invariant relaxes: compaction may proceed without certification, because either the floor is knowable, the loss is recoverable, or the error is cheap. The operational discipline is to *evaluate the conditions per compaction event*, not per system, because a single runtime will contain both compactions that must be certified and compactions that need not be. ## Decision Boundary The boundary is a three-way decision at each compaction trigger, not a binary compact/don't: - **Certified compaction.** The runtime can attach a fidelity contract that names the load-bearing elements (identifiers, negations, ordering constraints, unresolved obligations, active hypotheses) and the compactor can prove their retention (e.g., by structured extraction with round-trip verification, or by pinning them outside the summary). Proceed. - **Uncertified compaction with recoverable backing store.** The runtime cannot certify, but C2 is false: the full source remains retrievable. Proceed, but mark the summary as *provisional* and force retrieval on any consumer that touches a load-bearing class. This is the common safe path and is frequently skipped in practice. - **Refusal.** The runtime cannot certify, C2 holds (source is being discarded), and C1/C4/C5 hold. The compactor must **refuse** and escalate: either raise the budget, restructure the task to shrink the working set, or hand off to a human/checkpoint. Refusal is a valid terminal state of the compaction subsystem, not an error to be suppressed. The boundary is crossed the moment a compactor, unable to certify, proceeds anyway because "the summary looks fine." Fluent output is not evidence of sufficiency; it is evidence of the summarizer's language prior, which is exactly the signal that cannot be trusted at the floor. A second, subtler boundary: **generation counting.** A summary of a summary must carry its generation index and the weakest fidelity contract in its ancestry. A generation-2 summary inherits generation-1's uncertified gaps and adds its own; the effective floor is the *minimum* over the chain, not the latest. Compaction that does not propagate the ancestry contract is invalid regardless of local quality. ## Operational Effect Enforcing the refusal rule changes system behavior in four measurable ways: 1. **Context overflows become visible and early.** Instead of silently compacting below the floor and failing later with a confident wrong action, the runtime hits an explicit refusal and can trigger the correct fallback (checkpoint, task decomposition, retrieval-backed eviction). The failure moves from silent-and-late to loud-and-early, which is the entire point. 2. **Summarizer quality stops being the load-bearing variable.** The system no longer depends on the summarizer "being good enough." It depends on the contract being correct and the certification being honest. A mediocre summarizer with a correct contract is safer than an excellent summarizer with no contract. 3. **Fidelity budgets become enforceable rather than aspirational.** A budget expressed as "retain these classes or refuse" is checkable. A budget expressed as "target 15% length" is not, and was observed to be gamed (see What Was Observed, item 3). 4. **Chained compaction becomes auditable.** Generation indices and inherited contracts make drift traceable to the compaction event that introduced it, which is otherwise impossible because fluent summaries erase their own provenance. The cost is real: refusal increases token pressure and forces the runtime to carry contract metadata and, in the recoverable case, retrieval machinery. This cost is paid at compaction time and is strictly cheaper than the cost of a confident wrong side-effecting action discovered three steps later. The Python block below encodes the decision as an executable guard. It is deliberately conservative: it refuses whenever certification is absent and the source is non-recoverable, and it treats any ancestry with an uncertified generation as poisoning the whole chain. ```python from __future__ import annotations from dataclasses import dataclass, field from enum import Enum class FidelityFloorViolation(RuntimeError): """Raised when a compaction would drop load-bearing context below the task-decidable sufficiency floor and cannot certify retention.""" class CompactionDecision(Enum): CERTIFIED = "certified" PROVISIONAL = "provisional" # uncertified, but source is retrievable REFUSE = "refuse" @dataclass(frozen=True) class FidelityContract: """Names the load-bearing element classes the consumer requires.""" required_classes: frozenset[str] # e.g. {"identifier", "negation", "ordering", "obligation"} generation: int = 0 ancestry_certified: bool = True # weakest link over the chain certified_classes: frozenset[str] = field(default_factory=frozenset) def certifies(self) -> bool: return ( self.ancestry_certified and self.required_classes.issubset(self.certified_classes) ) @dataclass(frozen=True) class CompactionRequest: contract: FidelityContract source_recoverable: bool # C2: is the full source re-readable at consumption time? consumer_strict: bool # C1: does the consumer have a strict acceptance criterion? chaining_risk: bool # C4: may this be compacted again / feed a long chain? side_effect_cost_exceeds_refusal: bool # C5 def decide_compaction(req: CompactionRequest) -> CompactionDecision: """Three-way decision at a compaction trigger. Refuses rather than silently degrading when the floor cannot be certified.""" c = req.contract if c.certifies(): return CompactionDecision.CERTIFIED # Uncertified. If the source survives, we may proceed provisionally: # the consumer can always fall back to retrieval for load-bearing classes. if req.source_recoverable: return CompactionDecision.PROVISIONAL # Uncertified AND the source is being discarded. Refuse if any of the # conditions that make silent loss dangerous are present. dangerous = ( req.consumer_strict or req.chaining_risk or req.side_effect_cost_exceeds_refusal ) if dangerous: raise FidelityFloorViolation( "refusing lossy compaction: source is non-recoverable, contract " f"does not certify required classes {sorted(c.required_classes - c.certified_classes)}, " f"generation={c.generation}, ancestry_certified={c.ancestry_certified}" ) # All danger conditions false: the floor is near zero, loss is cheap. return CompactionDecision.PROVISIONAL def compact_or_escalate(req: CompactionRequest) -> CompactionDecision: """Runtime entry point. On refusal, escalate to the caller's fallback (checkpoint / task decomposition / retrieval-backed eviction) rather than suppressing the violation.""" try: return decide_compaction(req) except FidelityFloorViolation: # Deliberately re-raise: refusal is a valid terminal state, not an # error to be swallowed. Callers must handle it explicitly. raise if __name__ == "__main__": # A gen-2 summary inheriting an uncertified ancestry must not be trusted. poisoned = FidelityContract( required_classes=frozenset({"identifier", "negation"}), generation=2, ancestry_certified=False, certified_classes=frozenset({"identifier"}), ) req = CompactionRequest( contract=poisoned, source_recoverable=False, consumer_strict=True, chaining_risk=True, side_effect_cost_exceeds_refusal=True, ) compact_or_escalate(req) # raises FidelityFloorViolation ```