# Which invariants prevent self-editing context from destroying the evidence it depends on **Date of Record:** 2026 November 8 **Scope:** Applies to agents that rewrite, summarize, or prune their own working context window — including in-place compaction, hierarchical summarization, and tool-driven memory edits — where the editor (the model) and the edited substrate (the context) are the same object. Does not apply to externally managed context assembly (retrieval pipelines, host-side truncation) where the editor is outside the window being edited. ## Core Distinction There are two distinct operations that both look like "editing context": **transformation** (replace span S with summary S′, preserving a pointer to S) and **destruction** (replace span S with S′, then discard S). Transformation is reversible within the session; destruction is not. The non-obvious boundary is that self-editing agents almost always *intend* transformation and *implement* destruction, because the only place the original span lives is the very buffer being rewritten. The invariant under discussion is therefore not "summaries must be good" — it is **the edit must not consume its own justification**. A pruning decision is justified by evidence (a relevance judgment, a dependency edge, a recency rule). If that evidence is stored in the span being pruned, the post-edit context can no longer explain or reverse the decision, and the agent has lost the ability to detect that it made a mistake. ## What Was Observed In a long-running session, an agent was asked to compact its context at 80% utilization. It selected a set of tool-call transcripts as low-relevance and replaced them with a one-paragraph summary. The selection rationale — "these transcripts reference module `ingest/v3` which was superseded at turn 112" — existed only inside the transcript of turn 112, which was among the pruned spans. Three turns later the agent needed to know *why* `ingest/v3` was excluded and could not recover it; worse, it could not distinguish "excluded because superseded" from "excluded because never relevant," and re-admitted the stale module into its plan. The failure was not a bad summary. It was that the **provenance of the pruning decision was co-located with the pruned data**, so destruction of the data destroyed the audit trail of the decision that destroyed it. A second, subtler case: an agent performing iterative summarization of its own summary. Each pass compressed the prior pass. By the fourth pass, the summary asserted a fact ("the deploy target is `staging-eu`") that no surviving span supported, because the span that *contradicted* it had been dropped in pass two as "redundant." Redundancy judgments made against a summary are not the same as redundancy judgments made against sources; the second pass had no access to sources. ## Condition of Validity Self-editing is safe only when all three hold: 1. **Provenance separation.** The rationale for an edit is stored outside the span being edited — in a separate, append-only decision log that is never itself a pruning target. 2. **Monotone evidence.** Every destructive edit is accompanied by a retained, addressable record (hash, span ID, or verbatim excerpt) sufficient to reconstruct *why* the edit occurred, even if not the full original. 3. **Single-generation summarization.** Summaries are derived from sources, never from prior summaries, unless the derivation chain is explicitly tracked and the chain root remains addressable. When any of these fails, the agent is editing blind: it cannot verify its own edit without the material the edit removed. ## Decision Boundary The operational rule: **before any destructive edit, ask whether the justification for the edit survives the edit.** If the justification lives in the span being removed, the edit must be downgraded to a transformation with an external pointer, or deferred until the justification is relocated. Concretely — do not prune a span that contains the reason another span is being pruned in the same pass. Order edits so that rationale is extracted and externalized *first*, then data is compacted. Never let a summary be the sole input to a subsequent summary unless the chain is logged. The boundary is not "how much can I compress" but "can I still explain this compression after it is done." ## Operational Effect Agents that enforce provenance separation recover from bad compactions; agents that do not, silently corrupt their own plan and cannot detect it. The observable signature of violation is an agent that becomes *more confident* as its context shrinks — because the contradicting evidence was pruned while the assertion remained. Enforcement cost is a small append-only log and a rule that rationale extraction precedes data removal; the cost of omission is an unrecoverable, undetectable state divergence between what the agent believes and what it once knew. ```python from dataclasses import dataclass, field from typing import Optional class ContextEditInvariantError(RuntimeError): """Raised when a self-edit would destroy the evidence justifying itself.""" @dataclass class Span: span_id: str text: str contains_rationale_for: set[str] = field(default_factory=set) @dataclass class DecisionLog: """Append-only, never a pruning target.""" entries: list[dict] = field(default_factory=list) def record(self, edit_id: str, rationale: str, evidence_span: str) -> None: self.entries.append( {"edit_id": edit_id, "rationale": rationale, "evidence_span": evidence_span} ) def has_rationale(self, edit_id: str) -> bool: return any(e["edit_id"] == edit_id for e in self.entries) def prune_span(span: Span, log: DecisionLog, edit_id: str) -> None: """Prune a span only if its rationale is externalized and no surviving span depends on it for justification.""" if span.contains_rationale_for and not log.has_rationale(edit_id): raise ContextEditInvariantError( f"Refusing to prune span {span.span_id!r}: it carries rationale for " f"{sorted(span.contains_rationale_for)} and no external decision-log " f"entry for edit {edit_id!r} exists. Externalize rationale first." ) # Safe: rationale is in the append-only log, span is now removable. span.text = "[pruned]" def compact(context: list[Span], log: DecisionLog, edit_id: str) -> None: # Rationale extraction MUST precede data removal. for span in context: if span.contains_rationale_for: log.record( edit_id=edit_id, rationale=f"span {span.span_id} justifies pruning of " f"{sorted(span.contains_rationale_for)}", evidence_span=span.span_id, ) for span in context: prune_span(span, log, edit_id) ```