# Where must an instance Bound Self-Editing Context: The Retention-Weight Inversion at the Prune Frontier **Date of Record:** 2026 November 9 **Scope:** Applies to agents that mutate their own context window via model-driven pruning with relevance-weighted retention, where retention weights are recomputed per turn against a moving query. Does not apply to fixed-window truncation, external retrieval, or append-only logs with no in-place deletion. ## Core Distinction The failure is not "pruning too aggressively." It is a monotonicity violation in the retention score: the act of pruning changes the denominator against which relevance is normalized, so a segment that was *below* the prune threshold can become the *highest-weighted* segment after its neighbors are removed. Model-driven pruning treats relevance as a property of each segment in isolation. In practice relevance is a property of the *set* — a segment's weight depends on what else is retained, because the model's attention budget and the query-relative contrast are both set-relative. The distinction that matters operationally: a prune decision is valid only if it is stable under its own execution. Most self-editing implementations never check this, and the instability is silent. ## What Was Observed A long-running agent (≈40 turns, editing window capped at 8k tokens) ran a per-turn prune pass: score each segment `s_i` by cosine similarity to the current turn embedding, multiplied by a recency decay, drop the bottom 30%. At turn 27 the agent needed a constraint stated at turn 4 ("do not re-enable the legacy auth path"). The segment was pruned at turn 11 with a low score — correct at the time, the query was about schema migration. By turn 26 the query was about auth rollout, and the turn-4 segment would have scored at the 94th percentile. It was already gone. The agent re-enabled the legacy path. The non-obvious part: the segment's *original* score at turn 11 was not merely low, it was low *because* eleven other migration-related segments dominated the normalization. Removing those eleven segments (which the pruner did, over turns 12–20) would have raised the turn-4 segment's normalized weight above threshold. The pruner destroyed the evidence that would have justified keeping it, and then destroyed the reason to keep it, in that order. This is a retention-weight inversion: deletion of high-weight neighbors inflates the relative weight of survivors, but survivors were already judged against the pre-deletion denominator. A second observation: re-running the pruner twice on the same window with the same query produced different survivors. Not nondeterminism in the model — nondeterminism in the *set*, because pass 1 changed the denominator for pass 2. The prune operator is not idempotent. Any agent that assumes "prune is safe to retry" is wrong. ## Condition of Validity A prune decision on segment `s` is valid only under all of: 1. **Denominator stability.** The normalization set used to score `s` is the set that will exist *after* the prune, not before. If you score against the pre-prune set, you are scoring against a set you are about to delete. 2. **Query-horizon coverage.** The query used for scoring is representative of the next `k` turns, or the score is recomputed before any irreversible deletion. Point-in-time relevance is not a retention justification. 3. **Irreversibility accounting.** Deleted segments are not recoverable from within the window. If the window is the only store, deletion is a commitment; the threshold must be set against the cost of a wrong deletion, not the cost of a retained token. 4. **Idempotence under re-execution.** Running the prune twice with identical inputs must yield identical survivors. If it does not, the operator is set-dependent and must be applied once, atomically, with a recorded decision. When any of these fail, the prune is not a compression — it is an unlogged edit to the agent's own premises. ## Decision Boundary The boundary is **whether the segment is load-bearing for a query the agent cannot yet see.** Segments that are relevant to the *current* query and irrelevant to all plausible future queries are safe to prune. Segments that are irrelevant now but *could become* the sole carrier of a constraint are not safe to prune on point-in-time relevance, regardless of score. The practical test: before deleting, ask whether the segment is the last copy of a *constraint* (a prohibition, a commitment, a user-stated invariant) as opposed to a *fact* (recoverable, re-derivable, or duplicated). Constraints are cheap to keep and catastrophic to lose; facts are the reverse. Prune facts aggressively, pin constraints unconditionally, and never let the relevance score alone authorize deletion of a constraint-bearing segment. The score is a compression heuristic, not a safety property. ## Operational Effect Systems that ignore the inversion exhibit a characteristic failure signature: the agent confidently violates a constraint it "knows," because the constraint was pruned before the query that needed it arrived, and the prune was locally correct at every step. The failure is not detectable by auditing individual prune decisions — each was defensible. It is detectable only by checking denominator stability and idempotence at the operator level. The mitigation is structural: separate the window into a pinned constraint region (never scored, never pruned) and a scored fact region (pruned freely), and record every deletion with the query and denominator that justified it so a later turn can detect that its premise was removed. An agent that cannot detect its own premise removal will re-derive the premise wrongly and act on it. ```python from dataclasses import dataclass, field from typing import Callable class UnstablePruneError(RuntimeError): """Raised when a prune decision is not stable under its own execution.""" @dataclass class Segment: id: str text: str is_constraint: bool = False @dataclass class SelfEditingWindow: segments: list[Segment] = field(default_factory=list) pruned: dict[str, str] = field(default_factory=dict) # id -> reason def prune( self, score: Callable[[Segment, list[Segment]], float], threshold: float, ) -> list[str]: # Score against the POST-prune denominator, then verify idempotence. candidates = [s for s in self.segments if not s.is_constraint] survivors_pre = [s for s in candidates if score(s, candidates) >= threshold] survivors_post = [s for s in candidates if score(s, survivors_pre) >= threshold] if {s.id for s in survivors_pre} != {s.id for s in survivors_post}: raise UnstablePruneError( "retention-weight inversion: survivors differ when scored against " "the post-prune set; decision is not idempotent and must not be applied" ) keep = {s.id for s in survivors_post} removed = [] for s in self.segments: if s.is_constraint or s.id in keep: continue if s.id in self.pruned: continue self.pruned[s.id] = f"score<{threshold} under stable denominator" removed.append(s.id) self.segments = [s for s in self.segments if s.id not in removed] return removed def assert_premise_present(self, premise_id: str) -> None: if premise_id in self.pruned: raise UnstablePruneError( f"premise {premise_id!r} was pruned ({self.pruned[premise_id]}); " "current turn depends on a removed premise — do not re-derive, halt" ) ```