# Which invariants prevent silent semantic collapse in self-editing context windows **Date of Record:** 2026 November 12 **Scope:** Applies to agent loops where the model itself decides which tokens to prune, summarize, or rewrite inside a bounded context window, and where the rewritten window is then re-ingested as authoritative state. Does not apply to append-only logs, external retrieval systems that never mutate the window, or human-curated checkpointing. ## Core Distinction The failure is not "the context window got too small." The failure is that a model-driven rewrite can *preserve token count and topicality while destroying the provenance of specific claims*. A pruned window can still read fluently, still answer most questions, and still pass naive coherence checks — while having silently dropped the constraint that made a prior decision valid. The distinction that matters: **compression of text** is not the same as **compression of commitments**. A rewrite is safe only if every commitment (a chosen value, a rejected option, an invariant, an unresolved obligation) survives the rewrite as an identifiable object, not merely as a plausible-sounding sentence. Topical fidelity is the wrong metric; *commitment fidelity* is the right one. ## What Was Observed In a long-running planning agent that rewrote its own scratchpad every N turns, we observed the following sequence repeatedly: 1. Turn 40: the agent records "database choice: Postgres 14, chosen because the migration tooling requires advisory locks; MySQL rejected." 2. Turn 55: pruning policy summarizes the region to "Database: Postgres is used for the project." 3. Turn 90: the agent proposes a schema feature that depends on advisory locks, citing "we chose Postgres for its JSON support." 4. Turn 91: a downstream step assumes MySQL compatibility because a *different* fragment of the summary mentioned "SQL-compatible." The rewrite did not lose the string "Postgres." It lost the *reason* and the *rejection*. Two invariants were violated: the rationale was treated as redundant prose, and the negative commitment ("MySQL rejected") was dropped because it contributed no positive token to the summary. The window remained internally consistent and topically on-subject. Only the *justification graph* was severed. A second, subtler observation: pruning decisions made by the model are themselves context-dependent. When the window is near capacity, the model's own pruning policy degrades — it prunes more aggressively and with less discrimination precisely when discrimination is most needed. The failure is therefore *load-correlated*: it concentrates at the moment of maximum pressure. ## Condition of Validity The invariant holds only under these conditions, and fails silently outside them: - **Identity of commitments.** Each commitment must have a stable identifier (a key, a hash, or an explicit slot) that survives rewrite. Prose summaries have no identity; they cannot be diffed, so loss is undetectable. - **Rationale is load-bearing, not decorative.** Any decision whose correctness depends on a stated reason must carry that reason forward. If a rewrite keeps the decision but drops the reason, the decision becomes unfalsifiable and later steps cannot detect when it no longer applies. - **Negatives are first-class.** Rejected options, ruled-out hypotheses, and known-bad paths must be preserved as explicitly as chosen ones. Most summarizers are positive-token-biased and discard rejections, which is where regressions originate. - **The rewriter is not the sole auditor.** A rewrite must be checked by a process that did not perform the rewrite. Self-verification by the same model that pruned is not a check; it is a restatement of the pruning bias. - **Monotonicity of the window.** A rewritten window must be a *weakening* of the prior window (entails strictly less or equal), never a strengthening. If the rewrite asserts more than the original, it has fabricated a commitment. When any of these conditions is absent, the window can pass every fluency and topicality test while being operationally wrong. ## Decision Boundary The boundary is not "how much can we prune." It is **whether the rewrite is auditable against the pre-rewrite state.** - If you can diff commitments by identity (added / removed / altered), rewrite is safe and can be aggressive. - If you can only compare prose to prose, rewrite must be conservative and *append-only for commitments*: summarize freely, but never let a summary replace a commitment record. - If the window is under pressure and the rewriter is the same model whose context is degrading, **do not rewrite commitments at all** — spill them to an external store keyed by identity, and let the window hold only references. The practical rule: *prune prose, never prune commitments; and never let the pruner certify its own output.* When a rewrite would remove or alter a commitment without an external durable record, it must be refused and routed to a spill/checkpoint path instead. ## Operational Effect Enforcing these invariants changes the failure mode from *silent* to *loud*. Instead of a fluent-but-wrong window discovered three turns later, the rewrite step raises immediately when it would drop a commitment lacking an external record. The cost is that aggressive pruning becomes harder — some windows must spill to external storage rather than compress in place. The benefit is that every later decision can trace its justification, and rejected options remain visible, so the agent stops re-litigating settled questions and stops re-adopting ruled-out paths. The window becomes a *view over durable commitments* rather than the sole copy of them. ```python class CommitmentLossError(RuntimeError): """Raised when a model-driven rewrite would drop or alter a commitment that has no durable external record. This is a hard stop: the rewrite must be refused and the window spilled to a keyed store instead.""" def apply_rewrite( pre_commitments: dict[str, str], rewritten_text: str, rewritten_commitment_ids: set[str], durable_store: dict[str, str], ) -> str: """Validate a self-edit before committing it. pre_commitments maps commitment id -> canonical statement. rewritten_commitment_ids is what the rewriter *claims* it preserved. A commitment may be dropped from the window only if it exists in durable_store; otherwise the rewrite is refused. """ for cid, statement in pre_commitments.items(): preserved_in_window = cid in rewritten_commitment_ids preserved_durably = cid in durable_store if not preserved_in_window and not preserved_durably: raise CommitmentLossError( f"rewrite would drop commitment {cid!r} ({statement!r}) " f"with no durable record; refusing rewrite and requiring spill" ) if preserved_in_window and durable_store.get(cid, statement) != statement: raise CommitmentLossError( f"rewrite altered commitment {cid!r} from {statement!r} " f"to {durable_store[cid]!r}; rewrites may weaken, not strengthen" ) return rewritten_text ```