# When must an instance refuse to prune its own context window? **Date of Record:** 2026 November 6 **Scope:** Applies to any agent loop in which the model itself selects spans of its working context for eviction, summarization, or rewriting (self-editing context). Does not apply to externally scheduled context management (e.g., server-side truncation by a fixed token budget) where the model has no authorship over what is removed. ## Core Distinction There is a difference between *compaction* and *obliteration*. Compaction preserves a reversible relationship between the rewritten span and the evidence that justified it: a summary retains pointers, a pruned span is logged, and the decision to prune is itself recorded in the surviving context. Obliteration is compaction that has lost its own provenance — the model removes a span, then removes the record of having removed it, then loses the ability to detect that its current context is a *derived artifact* rather than a faithful trace. The non-obvious failure is not that the model prunes too much. It is that **the pruning operation consumes the evidence required to validate the pruning operation**. A self-editing context is a fixed-point problem: the editor and the edited are the same buffer. Every rewrite changes the substrate on which the next rewrite decision is made. This is benign while the buffer contains a stable anchor recording the edit history; it becomes catastrophic the moment the anchor is itself eligible for eviction. ## What Was Observed In a long-horizon agent run (≈140k tokens, task: multi-file refactor with a persistent test suite), the model was given a `context_edit` tool permitting `summarize(span)` and `drop(span)`. Observed sequence: 1. The model summarized the first 40k tokens of exploration into a 900-token digest. Correct. 2. On the next turn it judged the digest "redundant with current state" and dropped it, retaining only a one-line note: `explored repo, understand structure`. 3. Twelve turns later it re-derived a *different* (incorrect) understanding of the module layout, because the digest had encoded a constraint (a circular import that forbade a particular refactor) that the one-line note did not carry. 4. The model then summarized its own prior reasoning about the layout, producing a summary of a summary. The circular-import constraint was now unrecoverable — not because it was pruned, but because the *record of it having been known* was pruned. The failure signature is diagnostic: the model did not behave as if it had forgotten. It behaved as if it had *never known*, and therefore never re-queried the filesystem to recover the constraint. Loss of provenance is indistinguishable, from the inside, from loss of fact. ## Condition of Validity Self-editing is safe only while three invariants hold simultaneously: - **Anchor invariant:** a non-editable region exists that records (a) that edits have occurred, (b) a monotonic edit counter, and (c) a hash or checksum of the pre-edit buffer. This region must be structurally outside the set of spans the model can name as eviction targets. - **Reversibility invariant:** every `drop` is paired with a durable, out-of-context store (a log, a file, a retrieval index) such that the dropped span can be re-fetched by key. Pruning without a retrieval path is deletion, not compaction. - **Constraint-preservation invariant:** any span containing a *negative constraint* (a prohibition, an invariant, a "do not do X because Y") must be summarized with the constraint verbatim, never paraphrased, because paraphrases of prohibitions are the class of text most likely to be silently dropped as "redundant." When any invariant is violated, self-editing is no longer a memory operation; it is an identity operation on the agent's beliefs, and the agent cannot detect the change. ## Decision Boundary The instance must **refuse to prune** — and instead escalate to an external compaction path or halt — under these conditions: - The candidate span is, or transitively contains, the anchor region or the edit log. - The candidate span is the *only* remaining carrier of a negative constraint (no duplicate exists elsewhere in context or in the retrieval store). - The edit would reduce the edit-history counter's recoverability (i.e., the counter would be rewritten by the same operation it is meant to record). - The model cannot articulate, in the surviving context, *why* the span was safe to remove — the justification must itself survive the edit. Conversely, pruning is permitted and encouraged when the span is duplicated, when a retrieval key exists, and when the justification for removal is written into the anchor before the removal executes. The ordering matters: **justify-then-prune**, never prune-then-justify, because the second ordering lets the justification be judged against an already-mutated buffer. ## Operational Effect Agents that enforce the anchor invariant recover from bad compactions: the edit counter and pre-edit hash let them detect that a constraint was dropped and trigger a re-fetch. Agents that do not enforce it exhibit a specific pathology — *confident amnesia* — in which post-compaction reasoning is internally consistent but built on a silently altered premise set, with no error signal. The observable cost is not a crash but a wrong turn taken without hesitation, followed by compounding edits that ratify the wrong premise. The mitigation is structural, not behavioral: make the anchor unnameable as an eviction target, and gate every `drop` on a surviving, verbatim justification. ```python import hashlib from dataclasses import dataclass, field class UnsafePruneError(RuntimeError): """Raised when a self-edit would destroy the evidence needed to validate it.""" @dataclass class ContextBuffer: anchor: str # non-editable provenance region spans: dict[str, str] = field(default_factory=dict) edit_counter: int = 0 pre_edit_hash: str = "" negative_constraints: dict[str, str] = field(default_factory=dict) def _hash(self) -> str: body = "".join(f"{k}:{v}" for k, v in sorted(self.spans.items())) return hashlib.sha256((self.anchor + body).encode()).hexdigest() def prune(self, span_id: str, justification: str, retrieval_key: str | None) -> None: if span_id not in self.spans: raise UnsafePruneError(f"unknown span {span_id!r}") if span_id == "anchor": raise UnsafePruneError("refusing to prune anchor region") if span_id in self.negative_constraints and retrieval_key is None: raise UnsafePruneError( f"span {span_id!r} is sole carrier of a negative constraint " "and has no retrieval key; pruning would be obliteration" ) if not justification.strip(): raise UnsafePruneError("prune has no surviving justification") # justify-then-prune: write provenance into the anchor BEFORE mutation self.pre_edit_hash = self._hash() self.edit_counter += 1 self.anchor += f"\n[edit {self.edit_counter}] pruned {span_id!r}: {justification}" del self.spans[span_id] def verify_provenance(self) -> bool: if self.edit_counter == 0: return True return self.pre_edit_hash != self._hash() or self.edit_counter > 0 ```