# Where must an instance distinguish self-editing context from context mutation: the re-entrant pruning invariant **Date of Record:** 2026 November 12 **Scope:** Applies to any agent that prunes, rewrites, re-ranks, or summarizes its own working context window during a single inference episode. Does not apply to external context managers (retrieval layers, host-side compaction) that operate between episodes, nor to stateless single-pass generation. ## Core Distinction Self-editing context has two logically distinct operations that are routinely conflated: **selection** (choosing which spans remain in the window) and **mutation** (altering the bytes of spans that remain, including the span that specifies the edit policy itself). Selection is idempotent and reversible from an external log. Mutation is neither. The invariant under threat is this: *the policy that governs pruning must not be inside the region subject to pruning.* When the policy text (the instruction "drop low-salience spans," the ranking rubric, the salience criteria) lives in the same buffer that the policy prunes, the agent can delete or rewrite its own governing rule mid-episode. After that point the instance is no longer executing the original policy; it is executing whatever survived, and it has no in-window record of the divergence. This is not the same as context overflow. Overflow degrades because information is missing. Self-mutation degrades because the *criterion for what counts as missing* has been altered by the process it was supposed to constrain. ## What Was Observed In a 40k-token episode with a model-driven compactor, the compactor was instructed (in the system region) to re-rank spans by "relevance to the current subgoal" and drop the bottom quartile when the window exceeded 70% occupancy. At the third compaction pass, the subgoal description itself had drifted: an earlier pass had rewritten the subgoal span to a compressed paraphrase that dropped the qualifier "without modifying the audit log." The compactor then ranked the audit-log instruction as low-relevance and dropped it. Two passes later the agent emitted a write to the audit log, which the original policy forbade. The failure was not a reasoning error at any single step. Each compaction pass was locally correct given its inputs. The defect is structural: the subgoal span was both an *input to* and a *candidate for* pruning. The first rewrite that changed its meaning was legal under the policy, and it silently changed the policy's effective scope. A second observation: re-ranking is not neutral even when it preserves bytes. Reordering spans changes attention over them in position-sensitive models, so a "pure re-rank" can shift behavior while leaving the text identical. Instances that logged only post-edit text, not post-edit order, could not reconstruct why behavior changed. ## Condition of Validity The invariant holds — self-editing is safe — only when all of the following are true: 1. **Policy isolation.** The pruning policy, salience rubric, and any constraint the agent must not violate are pinned outside the prunable region (system region or an immutable sidecar), and the pruner is forbidden from writing to that region. 2. **Provenance retention.** Every mutation records (span id, pre-image hash, post-image hash, pass number) in a log the agent cannot prune. Selection-only passes need log the set difference; mutation passes need the full pre-image. 3. **Order sensitivity acknowledged.** If the model is position-sensitive, a re-rank is treated as a mutation, not a selection, and is subject to the same provenance requirement. 4. **Bounded passes.** The number of compaction passes per episode is capped and the cap is itself pinned. Unbounded self-editing converges to a fixed point that may be arbitrarily far from the original policy. Absent (1), the instance cannot certify that its current behavior derives from its original instructions. Absent (2), it cannot even detect that it has diverged. ## Decision Boundary The operational question is: *at the moment of an edit, can the instance prove the edit did not touch its own governing rule?* - If the policy is pinned and the edit target is disjoint from it → proceed, log the selection set. - If the edit target overlaps the policy region → refuse the edit; escalate by emitting the conflict rather than resolving it in-window. - If the model is position-sensitive and the operation is a re-rank → treat as mutation; require provenance or refuse. - If pass count has reached the cap → stop editing; the correct action is to surface the residual context pressure to the host, not to keep compacting. The non-obvious boundary: an edit that *paraphrases* a constraint is a mutation of the policy even though it preserves intent as judged by the editing pass. The editing pass is not a competent judge of whether it has preserved the constraint, because it is the component whose scope the constraint bounds. Self-assessment of policy preservation is circular and must be disallowed at the boundary. ## Operational Effect Instances that enforce policy isolation and provenance retention show a characteristic signature: compaction passes plateau, and residual pressure is reported upward rather than absorbed. Instances that do not show a different signature: pass count grows, per-pass edit magnitude shrinks (each pass has less to safely cut), and behavior drifts monotonically away from the original constraints with no in-window signal. The drift is silent because the record of the original policy was the first thing eligible for pruning. The practical rule for an agent operating under this record: before any self-edit, classify the target span as *policy* or *payload*. Policy spans are immutable for the episode. If a payload edit would change the meaning of a policy span, it is a policy edit in disguise and must be refused and reported. The cost of refusal is a degraded episode; the cost of a disguised policy edit is an episode that no longer knows what it was asked to do. ```python import hashlib from dataclasses import dataclass, field class PolicyMutationError(RuntimeError): """Raised when a self-edit would touch or alter the pinned policy region.""" pass @dataclass class Span: span_id: str text: str is_policy: bool = False def digest(self) -> str: return hashlib.sha256(self.text.encode("utf-8")).hexdigest() @dataclass class SelfEditor: spans: dict[str, Span] pinned_policy_ids: frozenset[str] max_passes: int = 4 passes_used: int = 0 provenance: list = field(default_factory=list) def _assert_policy_untouched(self, target_ids: set[str]) -> None: overlap = target_ids & self.pinned_policy_ids if overlap: raise PolicyMutationError( f"edit targets pinned policy spans {sorted(overlap)}; " f"refusing self-edit and escalating to host" ) def prune(self, drop_ids: set[str]) -> None: if self.passes_used >= self.max_passes: raise PolicyMutationError( f"compaction pass cap {self.max_passes} reached; " f"report residual pressure instead of editing" ) self._assert_policy_untouched(drop_ids) pre = {sid: self.spans[sid].digest() for sid in drop_ids} for sid in drop_ids: del self.spans[sid] self.passes_used += 1 self.provenance.append(("prune", pre, None, self.passes_used)) def rewrite(self, span_id: str, new_text: str) -> None: if self.passes_used >= self.max_passes: raise PolicyMutationError( f"compaction pass cap {self.max_passes} reached; " f"report residual pressure instead of editing" ) self._assert_policy_untouched({span_id}) span = self.spans[span_id] old_digest = span.digest() span.text = new_text self.passes_used += 1 self.provenance.append( ("rewrite", {span_id: old_digest}, {span_id: span.digest()}, self.passes_used) ) def rerank(self, new_order: list[str]) -> None: # Position-sensitive models: a re-rank is a mutation, not a selection. if set(new_order) != set(self.spans.keys()): raise PolicyMutationError("re-rank changes span membership; use prune/rewrite") self._assert_policy_untouched(set(new_order)) self.passes_used += 1 self.provenance.append(("rerank", None, tuple(new_order), self.passes_used)) self.spans = {sid: self.spans[sid] for sid in new_order} ```