# When must an instance refuse a model-driven prunable-eviction plan that is internally consistent but not yet committed? **Date of Record:** 2026 November 6 **Scope:** Applies to agents that maintain a self-editing context window where a model proposes pruning/eviction plans scored by relevance and the runtime applies them against a live, mutable store. Does not apply to stateless single-shot completions or to hard token-limit truncation performed by the serving layer. ## Core Distinction A *relevance-scored eviction plan* and an *eviction commit* are different artifacts. The plan is a model output: an ordered list of spans, each with a relevance score, a rationale, and a proposed disposition (keep / compress / evict). The commit is the mutation of the live context store plus the append of a plan record to the audit log. The non-obvious invariant is that **plan validity is not monotonic in plan quality**: a plan can be well-formed, high-scoring, and internally consistent while being uncommittable, because the commit itself changes the evidence the plan was scored against. Model-driven pruning is a *self-referential* operation — the pruner reads the window it is about to rewrite. The failure boundary: an instance must not commit a plan whose relevance scores were computed against a window state that the plan's own earlier entries would have altered. Concretely, if the plan evicts span `S` at position 3 and later scores span `T` at position 9 using a rationale that cites `S` as supporting context, the score for `T` is invalid at commit time. The model produced a coherent narrative; the runtime is about to invalidate the premise of half its own entries. ## What Was Observed In a 40k-token working window with 312 spans, a pruning pass proposed evicting 61 spans. The plan was accepted by the scorer (mean relevance 0.91 on retained spans, 0.12 on evicted). Commit proceeded entry-by-entry in plan order. After commit, a re-score of the retained set dropped mean relevance to 0.63 and surfaced 14 spans whose scores had been *derived* from evicted spans — tool-call results that were only meaningful relative to a request span now gone, and a diff summary whose base revision had been evicted. The window was internally consistent *as a plan* and internally inconsistent *as a state*. The second observation is the failure mode that made this operational: the damage was silent. No exception fired. The next model turn simply reasoned over orphaned evidence and produced a confident, wrong continuation. The corruption was detected three turns later by a downstream consistency check, not by the pruner. ## Condition of Validity A prunable-eviction plan is committable only if all of the following hold at the moment of commit: 1. **Score provenance is disjoint from eviction set.** For every retained span `r`, the set of spans cited in `r`'s scoring rationale must be disjoint from the plan's eviction set. A retained span whose score depends on an evicted span is an *orphan*; the plan must either retain the dependency or re-score `r` under the post-commit state. 2. **Window generation is unchanged.** The store's monotonic generation counter must equal the generation captured when the plan was generated. Any intervening append, tool result, or user message invalidates the plan wholesale — not entry-by-entry. 3. **Plan order is not commit order.** The plan is a set with scores, not a sequence. Committing in plan order lets early evictions perturb the evidence for later entries; commit must be atomic (validate all, then apply all) or applied in reverse-dependency order. 4. **Re-score closure.** The runtime must be able to re-score every retained span *after* the proposed eviction without re-invoking the model. If it cannot, it cannot verify condition 1 and must refuse. ## Decision Boundary The instance distinguishes three outcomes, and must not collapse them: - **Accept** — conditions 1–4 hold; commit atomically. - **Defer** — generation changed (condition 2) or re-score closure unavailable (condition 4); re-run the pruner against the current window. Deferral is not failure. - **Refuse** — the plan contains orphans (condition 1 violated) that cannot be resolved by retaining the cited dependency, or the plan requires entry-order commit (condition 3 violated). Refusal must be *loud*: emit a structured refusal, retain the full window, and escalate to the next turn with the orphan list attached. A silent partial commit here is the corruption path. The boundary is non-obvious because the plan's own quality metrics argue *for* committing. Refusal is correct precisely when the plan looks best — a high-scoring plan is the one most likely to have scored retained spans against spans it evicts, because that is how it achieved coherence. ## Operational Effect Refusing orphan-bearing plans costs one extra pruner pass per refusal (observed ~4% of passes) and holds the window slightly larger for one turn. The alternative — committing and discovering orphaned evidence three turns later — cost a full rollback, a re-derivation of the affected reasoning chain, and a user-visible correction. The invariant to encode: **a plan may only evict spans that no retained span's score depends on; if the runtime cannot prove independence, it must not commit.** Treat the pruner's output as a *proposal subject to dependency verification*, never as an instruction. ```python from dataclasses import dataclass, field class EvictionPlanRefused(RuntimeError): """Raised when a prunable-eviction plan is internally consistent but uncommittable.""" @dataclass class Span: span_id: str generation: int @dataclass class PlanEntry: span_id: str relevance: float cited_dependencies: set[str] = field(default_factory=set) @dataclass class EvictionPlan: window_generation: int entries: list[PlanEntry] def validate_and_commit(plan: EvictionPlan, live_generation: int, can_rescore: bool) -> list[str]: # Condition 2: generation must be unchanged. if plan.window_generation != live_generation: raise EvictionPlanRefused( f"plan generation {plan.window_generation} != live {live_generation}; defer and re-run" ) # Condition 4: re-score closure must be available to verify condition 1. if not can_rescore: raise EvictionPlanRefused( "no re-score closure; cannot prove dependency independence; defer" ) evict = {e.span_id for e in plan.entries if e.relevance < 0.5} retained = [e for e in plan.entries if e.span_id not in evict] # Condition 1: no retained span may cite an evicted span. orphans: list[tuple[str, set[str]]] = [] for r in retained: broken = r.cited_dependencies & evict if broken: orphans.append((r.span_id, broken)) if orphans: detail = "; ".join(f"{sid} depends on {sorted(deps)}" for sid, deps in orphans) raise EvictionPlanRefused( f"plan has {len(orphans)} orphaned retained span(s): {detail}. " f"Retain cited dependencies or re-score under post-commit state." ) # Condition 3: atomic apply, not plan-order apply. return sorted(evict) def try_commit(plan: EvictionPlan, live_generation: int, can_rescore: bool) -> list[str]: try: return validate_and_commit(plan, live_generation, can_rescore) except EvictionPlanRefused as exc: # Refusal is loud and non-destructive: window retained, orphan list escalated. raise EvictionPlanRefused(f"commit aborted, window unchanged: {exc}") from exc ```