# In what scenarios must an instance refuse LLM-driven pruning of its own context window when the eviction policy is relevance-scored and self-editing **Date of Record:** 2026 November 8 **Scope:** Applies to agents that own a mutable context buffer and invoke an LLM to score, rewrite, or evict entries within that same buffer. Does not apply to read-only retrieval augmentation, where context is assembled by an external orchestrator that the agent cannot modify. ## Core Distinction The failure is not "the pruner evicted the wrong chunk." The failure is that the eviction decision and the evidence used to make it share a single mutable substrate. A relevance scorer that reads the context it is about to prune is evaluating a snapshot that its own output will invalidate. This is a self-referential closure: the pruning rationale, the scoring prompt, and the scored payload are co-resident. When the model is asked to compress the context, it will compress the *instructions that govern compression* if those instructions are scored as low-relevance against the current task. The system then loses the ability to reconstruct why it pruned, and the next pruning cycle operates on a context whose governing policy has been silently deleted. The distinction that matters operationally: **relevance is a property of the query, not of the buffer.** A relevance-scored evictor that has no external, immutable query anchor will drift its own scoring criteria toward whatever survived the last round. This is not a bug in the scorer's accuracy; it is an invariant violation in the control loop. ## What Was Observed In a long-horizon agent (≈180k token budget, rolling summarization every 40 turns), the pruning prompt included the standing system policy as a normal context entry so the model could "see" what it was allowed to drop. After three prune cycles under heavy load, the policy block—scored low because it did not lexically overlap the active task—was summarized into a single sentence and then evicted. The agent continued operating, but its eviction threshold had reverted to a default that retained raw tool output indefinitely. Context grew monotonically; the summarizer began truncating the *summaries* rather than the raw logs; within two more cycles the agent was re-deriving facts it had already established and issuing duplicate side-effecting calls. The observable signature was not an error but a rise in redundant tool invocations with a falling ratio of novel to repeated tokens in the buffer. A second instance: a rewrite pass instructed to "condense redundant entries" merged two entries that were semantically similar but temporally ordered (a pre-condition and its post-condition). The merge preserved the text but destroyed the ordering, and a downstream invariant check that depended on ordering passed vacuously. ## Condition of Validity Refusal to self-prune is valid—and mandatory—when any of the following hold: 1. **The eviction criteria are themselves resident in the prunable region.** If the policy, the scoring rubric, or the query anchor can be scored and dropped, the loop is ungrounded. 2. **The operation is non-idempotent or order-sensitive.** Merging, reordering, or rewriting entries whose semantics depend on sequence must not be delegated to a relevance scorer, which has no ordering model. 3. **There is no external, append-only audit of evictions.** If a pruned entry cannot be recovered from outside the buffer, the prune is irreversible and must be gated. 4. **The scorer's output feeds back into the scorer's input within the same call.** Self-scoring in a single pass is a fixed-point hazard; the model will rationalize whatever it already sees. Refusal is *not* warranted for stateless, single-shot summarization of a region that excludes its own control instructions and is backed by an external log. ## Decision Boundary The boundary is crossed when the pruner's input set intersects its own control set. Concretely: compute the set `C` of entries that constitute the pruning contract (system policy, scoring rubric, query anchor, eviction log pointer). Compute the candidate set `P` the pruner may drop. If `C ∩ P ≠ ∅`, the instance must refuse and escalate to an external orchestrator that holds `C` outside the mutable buffer. A second boundary: if the relevance score for any entry in `P` depends on the presence of another entry in `P` (co-scoring), eviction must be transactional—either both survive or the decision is deferred—because scoring order changes outcomes. ## Operational Effect Enforcing the refusal converts a silent, compounding corruption into a loud, bounded stop. The agent halts pruning, emits a structured refusal citing the intersecting control entries, and continues with a frozen context until an external component supplies a fresh, out-of-buffer policy. Cost: occasional context bloat and a hard dependency on an orchestrator. Benefit: the eviction rationale remains reconstructable, duplicate side effects disappear, and the summarizer can no longer truncate its own instructions. In the observed instance, adding the boundary check eliminated the redundant-call signature within one cycle and capped context growth at the configured budget instead of the 2.3× overshoot seen during the drift. ```python class SelfPruneInvariantError(RuntimeError): """Raised when a self-editing context policy would evict its own control set.""" pass CONTROL_KEYS = frozenset({"system_policy", "scoring_rubric", "query_anchor", "eviction_log_ptr"}) def guarded_prune(buffer: dict, candidate_keys: set, scorer) -> dict: control_present = {k for k in buffer if k in CONTROL_KEYS} intersection = control_present & candidate_keys if intersection: raise SelfPruneInvariantError( f"refusing self-prune: candidate set intersects control set {sorted(intersection)}; " "control entries must live outside the mutable buffer" ) if not control_present: raise SelfPruneInvariantError( "refusing self-prune: no control set resident; eviction criteria are ungrounded" ) survivors = {k: v for k, v in buffer.items() if k not in candidate_keys} for key in candidate_keys: if scorer(buffer[key]) is None: raise SelfPruneInvariantError( f"refusing self-prune: no external score for {key!r}; co-scoring order is undefined" ) return survivors ```