# Under what conditions must an instance treat lossy summarization as a compaction *barrier* rather than a compaction *opportunity* in long-horizon agent trajectories **Date of Record:** 2026 November 10 **Scope:** Applies to agent runtimes that auto-compact trajectory context by replacing spans of turns with lossy summaries under a declared per-span fidelity budget, and that resume execution from compacted state. Does not apply to lossless block-level compaction (e.g., KV eviction, sliding windows over verbatim tokens) where no summarization transform is applied and no fidelity budget is claimed. ## Core Distinction Auto-compaction is usually modeled as a *monotone resource reclamation* operation: each compaction reduces token footprint and, because the summary is claimed to preserve "the information needed to continue," the runtime treats the post-compaction state as a valid substitute for the pre-compaction state for all downstream purposes. This is the **compaction-opportunity** framing: any span is eligible, and eligibility is decided solely by token budget pressure and recency. The distinction that matters operationally is between compaction as *reclamation* and compaction as *transform*. A lossy summarization is a transform that is **not idempotent, not commutative with subsequent tool effects, and not invertible**. When a span contains a *pending obligation* — a commitment, an unresolved reference, a partially applied side effect, or a constraint that a later turn is expected to discharge — replacing that span with a summary does not reclaim space; it **destroys the discharge condition** for the obligation. The summary preserves the *fact* that an obligation existed but not the *binding* that lets a later turn satisfy it. This is the compaction barrier: a span that must be excluded from the lossy path regardless of budget pressure, and instead either retained verbatim or compacted only after its obligations are discharged or explicitly re-bound into a structured carrier. The non-obvious part: the barrier is **not** a property of the span's content or size. It is a property of the *join* between the span and the downstream plan. The same conversation span is a safe opportunity in one trajectory and a hard barrier in another, depending only on whether a future turn's correctness depends on the exact binding the summary drops. ## What Was Observed In a long-horizon agent run (≈340 turns, tool-using, with a declared per-span fidelity budget of "preserve decisions, drop deliberation"), auto-compaction fired at 78% context utilization and summarized a 41-turn span containing an in-progress multi-step migration. The summary recorded: *"Agent decided to migrate table `orders` to the new schema; migration was underway."* It dropped: - the exact set of columns already rewritten (partial application), - the idempotency key used for the migration tool call, - the constraint that the migration must not run while a specific downstream consumer was still pinned to the old schema. Downstream, the agent re-issued the migration tool call. Because the idempotency key was lost, the runtime could not deduplicate; the migration re-applied to a partially migrated table and produced a **split-schema state** that was internally consistent per-table but violated the cross-table invariant. The failure was not detected at compaction time — the summary was accurate at the level it claimed to preserve. It was detected 60 turns later when a join across the two schema versions returned silent nulls rather than erroring. Three further observations from the same run family: 1. **Fidelity budgets are evaluated per-span, but obligation discharge is a cross-span join.** A summary can be within budget and still sever a binding whose other endpoint lives in a retained span. 2. **Re-binding is cheaper than retention but only if it happens before compaction.** Once the summary is written, the binding is gone; reconstructing it requires re-derivation from tool logs, which is not always available and never free. 3. **The failure is silent by construction.** Lossy summaries fail *open*: they produce plausible, fluent, budget-compliant text. There is no error at the transform boundary. The error surfaces only as a downstream invariant violation whose causal distance from the compaction event is large enough to defeat naive attribution. ## Condition of Validity The compaction-opportunity framing is valid — i.e., a span may be lossily summarized without barrier treatment — **if and only if** all of the following hold for the span at the moment of compaction: - **No pending obligation:** every commitment, tool call, or constraint introduced in the span has been either discharged (its effect observed and confirmed) or explicitly re-bound into a structured carrier (a typed obligation record with idempotency key, preconditions, and discharge predicate) that survives compaction verbatim. - **No partial side effect:** the span contains no operation that has been started but not confirmed complete, or whose completion is not observable from retained state. - **No cross-span binding endpoint:** the span is not one endpoint of a binding whose other endpoint is in a retained span and whose correctness depends on exact values (keys, column sets, pinned versions, sequence numbers) that the summary is permitted to drop. - **Fidelity budget covers the binding, not just the decision:** the declared budget's "preserve decisions" clause is insufficient; the budget must separately declare preservation of *bindings and preconditions*, and the summarizer must be audited against that clause. If any condition fails, the span is a **barrier**: it must be retained verbatim, or compacted only after the failing condition is repaired (obligation discharged or re-bound, side effect confirmed, binding endpoint co-located into the retained set). The condition is *not* valid when the summarizer's fidelity budget is stated only in terms of semantic content ("preserve intent," "preserve decisions") without a binding-preservation clause. Such budgets are unfalsifiable at the transform boundary and will pass audit while severing bindings. ## Decision Boundary The runtime must decide, per span, between three actions. The boundary is drawn on **obligation state**, not on token pressure: | Span state | Action | Rationale | |---|---|---| | All obligations discharged or re-bound; no partial side effects; no cross-span binding endpoints | **Opportunity** — lossy summarize | Reclamation is safe; summary is a valid substitute for downstream purposes | | Any pending obligation, partial side effect, or cross-span binding endpoint | **Barrier** — retain verbatim or defer | Summary would sever a discharge condition; reclamation is illusory | | Barrier span that exceeds retention budget | **Repair-then-compact** — discharge or re-bind obligations into structured carrier, then summarize the residual deliberation | Preserves the binding while still reclaiming deliberation tokens | The critical boundary rule: **budget pressure does not override barrier status.** If a barrier span cannot be retained and cannot be repaired before compaction is forced, the correct action is to *fail the compaction* and surface the obligation, not to summarize and proceed. A runtime that summarizes a barrier under pressure has converted a recoverable budget event into an unrecoverable binding loss. A second boundary: **re-binding must precede summarization, not follow it.** The structured carrier (obligation record) must be written into the retained state *before* the lossy transform runs on the span. Post-hoc reconstruction from summaries is not re-binding; it is guessing. ## Operational Effect Treating lossy summarization as a barrier-aware operation changes three runtime behaviors: 1. **Compaction eligibility becomes a join over (span, obligation graph), not a scan over (span, token count).** The runtime must maintain a lightweight obligation ledger — pending commitments with idempotency keys, partial side effects with confirmation predicates, and cross-span binding endpoints — and consult it at compaction time. Spans touching live ledger entries are barriers. 2. **The fidelity budget gains a falsifiable clause.** "Preserve bindings and preconditions" is auditable: for each barrier span, assert that every ledger entry it touches is either discharged or present in the retained carrier post-compaction. This is a checkable invariant, unlike "preserve intent." 3. **Compaction can fail, and failure is a first-class outcome.** The runtime must be able to reject a compaction when barrier spans cannot be repaired within budget, and escalate the obligation rather than silently lossy-summarize. Silent success on a barrier span is the failure mode; loud rejection is the correct behavior. The empirical lesson: the dangerous compaction is the one that succeeds. A lossy summary that is fluent, budget-compliant, and semantically faithful at the decision level can still sever the binding that a future turn depends on, and the severance will not surface until the causal distance is too large for attribution. The barrier is the only defense, and it must be drawn on obligation state before the transform runs. ```python """Barrier-aware compaction gate for long-horizon agent trajectories. A span may be lossily summarized only if it is not a compaction barrier. Barrier status is a property of the span's join with the live obligation ledger, not of its token size or semantic content. """ from dataclasses import dataclass, field from typing import Iterable class CompactionBarrierError(RuntimeError): """Raised when a span is a barrier and cannot be safely summarized.""" @dataclass(frozen=True) class Obligation: obligation_id: str idempotency_key: str discharge_predicate: str # must be observable from retained state discharged: bool = False @dataclass class ObligationLedger: entries: dict[str, Obligation] = field(default_factory=dict) def live_ids(self) -> set[str]: return {oid for oid, ob in self.entries.items() if not ob.discharged} def touches(self, span_obligation_ids: Iterable[str]) -> set[str]: return set(span_obligation_ids) & self.live_ids() @dataclass class Span: span_id: str token_count: int obligation_ids: tuple[str, ...] has_partial_side_effect: bool cross_span_binding_endpoint: bool has_structured_carrier: bool = False # re-bound before compaction? def is_barrier(span: Span, ledger: ObligationLedger) -> bool: """A span is a barrier if any discharge condition would be severed.""" if span.has_partial_side_effect: return True if span.cross_span_binding_endpoint and not span.has_structured_carrier: return True if ledger.touches(span.obligation_ids): # Live obligation with no surviving carrier => barrier. return not span.has_structured_carrier return False def compact_span(span: Span, ledger: ObligationLedger, budget: int) -> str: """Return a summary for an eligible span; refuse barriers under pressure. Budget pressure does NOT override barrier status. A barrier that cannot be retained or repaired must fail the compaction loudly. """ if is_barrier(span, ledger): if span.token_count <= budget: # Retain verbatim: reclamation deferred, binding preserved. return f"[RETAINED VERBATIM span={span.span_id} tokens={span.token_count}]" raise CompactionBarrierError( f"span {span.span_id} is a compaction barrier " f"(tokens={span.token_count} > budget={budget}); " f"live obligations={sorted(ledger.touches(span.obligation_ids))}, " f"partial_side_effect={span.has_partial_side_effect}, " f"cross_span_binding={span.cross_span_binding_endpoint}; " f"repair (discharge or re-bind into structured carrier) before compaction" ) return f"[SUMMARY span={span.span_id} tokens={span.token_count} fidelity=decisions+bindings]" ```