# Under what conditions must an instance refuse to rewrite its own context: the invariant-witness divergence boundary in self-editing context policies **Date of Record:** 2026 November 12 **Scope:** Applies to agents that hold a mutable working context and are permitted to rewrite it (compaction, summarization, reordering, deduplication, eviction) under a declared set of preservation invariants. Does not apply to append-only context growth, to external context editors that do not share the instance's state, or to rewrites whose invariants are checked by a mechanism outside the instance's control (e.g., a separate verifier with independent state). ## Core Distinction The failure mode is not "the agent forgot something." It is *invariant-witness divergence*: a rewrite that preserves every invariant the instance can locally check, while silently destroying the evidence that the invariant was ever satisfied. The classic form is a compaction pass that summarizes a set of tool results into a natural-language digest. The digest may still *say* "the deployment was verified healthy," so the instance's own invariant check ("does the context assert deployment health?") passes. What is gone is the original observation—the exact command output, the timestamp, the exit code—that would let the instance, or a successor, distinguish a verified claim from a hallucinated one. Two properties must be separated: 1. **Invariant satisfaction** — the rewritten context logically entails the preserved predicates. 2. **Witness retention** — the rewritten context still contains the primitive observations from which those predicates were derived, at a fidelity sufficient to re-derive or refute them. Self-editing policies are almost always specified in terms of (1). The boundary condition is that (1) can hold while (2) fails, and (2) is load-bearing for every subsequent rewrite. The first rewrite that drops a witness is locally benign and globally terminal: it converts a *checked* invariant into a *trusted assertion*, and every later rewrite inherits the weaker status without any local signal. ## What Was Observed In a long-running agent session performing a sequence of context compactions against a declared invariant set (preserve: open task list, active constraints, verified facts with provenance), the following sequence recurred: - **Pass 1.** Raw tool outputs (file listings, test logs, config dumps) are summarized into a digest. Invariant check: all verified facts still present. Pass. Witness count for each fact drops from N observations to 1 prose sentence. - **Pass 2.** The digest is itself summarized because it now exceeds budget. The prose sentence "tests passed" survives; the fact that it was derived from a specific run at a specific commit does not. Invariant check still passes, because the predicate "tests passed" is still entailed. - **Pass 3.** A later step needs to *act* on the fact—e.g., decide whether a re-run is needed after a config change. The instance cannot determine whether "tests passed" refers to the pre-change or post-change tree. It has no witness. It either re-runs (safe but wasteful) or trusts the assertion (unsafe). Under budget pressure it trusts. The non-obvious part: **no individual rewrite violated its stated invariant.** Each pass was a correct application of the policy. The divergence was cumulative and only observable at the point of use, which is downstream of the editor and therefore outside the editor's check. A second observed form: **witness laundering via paraphrase.** A rewrite that rephrases "exit code 1 for `make test`" into "test run encountered issues" preserves the *topic* but inverts the *epistemic status*—it moves a hard, falsifiable observation into soft, unfalsifiable language. The predicate "we know the test failed" is still entailed; the predicate "we can prove the test failed" is not. Policies that check entailment of content, not preservation of modality, accept this rewrite. A third: **eviction of negative evidence.** Compaction policies that score context segments by relevance to the current task systematically evict failed attempts, error messages, and rejected hypotheses, because they are "not relevant" to the goal. This is correct for relevance and catastrophic for invariants, because the invariant "we have not already tried X" is witnessed *only* by the record of the failed attempt at X. Evict the witness and the invariant becomes unverifiable, and the instance re-attempts X. ## Condition of Validity A self-rewrite is valid only when, for every invariant in the declared set, the rewritten context contains a witness that is: - **Primitive** — not itself the output of a prior rewrite, or explicitly tagged as derived with a pointer to a retained primitive. - **Modality-preserving** — a failed observation stays failed, a verified observation stays verified, an unverified assertion is not promoted to verified. - **Refutation-capable** — sufficient to determine that the invariant *could* have been violated, not merely that it is asserted to hold. If any invariant lacks such a witness after the rewrite, the rewrite is *witness-degrading* and must be treated as a different operation from a witness-preserving rewrite, regardless of whether the entailment check passes. The condition of validity is therefore not "the invariant still holds" but "the invariant is still *checkable* by the same means that established it." This is a strictly stronger condition, and it is the one that is routinely omitted from policy specifications because it is invisible in single-pass tests. A policy that is correct on pass 1 is not correct on pass N; the correctness property is not closed under composition. ## Decision Boundary The boundary is crossed at the first rewrite where, for some invariant, `witness_count_after < witness_count_before` and the removed witnesses are not recoverable from retained primitives. At that point the instance must classify the rewrite as **witness-degrading** and apply one of three dispositions, in order: 1. **Refuse** if the invariant is load-bearing for a pending decision (the instance will need to act on it before the next external checkpoint). Refusal is mandatory because a witness-degrading rewrite of a load-bearing invariant converts a checkable fact into an uncheckable one at exactly the moment checkability matters. 2. **Retain a witness stub** if the invariant is not immediately load-bearing: keep the primitive (or a hash/pointer to it in durable storage) alongside the digest, so the invariant remains refutation-capable even though the full context does not. 3. **Proceed with explicit demotion** if the invariant is neither load-bearing nor refutation-relevant: rewrite, but tag the resulting assertion as `unwitnessed` so that no downstream step treats it as verified. The boundary is *not* at "the rewrite loses information." All useful rewrites lose information. It is at "the rewrite loses the ability to distinguish a satisfied invariant from an asserted one," because that is the property the next rewrite depends on. A subtle corollary: the instance cannot in general determine whether an invariant is load-bearing for a *future* decision without knowing the future. The safe default is to treat any invariant whose witness is a negative result (failed attempt, error, rejected hypothesis) as load-bearing, because negative results are exactly the ones whose loss causes re-execution or false confidence, and they are the ones relevance-scoring evicts first. ## Operational Effect Enforcing the witness-retention boundary changes the cost profile of self-editing: rewrites become more expensive (witness stubs and provenance tags consume budget) and less aggressive (some segments cannot be fully compacted). This is the correct trade. The alternative—witness-degrading rewrites that pass local checks—produces sessions that appear to be operating on verified state while actually operating on a chain of paraphrase, where each link was individually defensible and the chain as a whole is unfalsifiable. The practical signature of a session that has crossed the boundary: it can no longer answer "how do you know?" for facts it asserts, only "it says so here." Once that is true, every subsequent rewrite is unconstrained by evidence, and the invariant set has become decorative. The instance must detect the transition at the rewrite that causes it, not at the decision that exposes it, because by the decision point the witness is already gone and refusal is no longer possible—only re-derivation from external state, if any exists. The following implements the check as a gate on the rewrite path. It refuses witness-degrading rewrites of load-bearing invariants and demotes the rest. ```python from dataclasses import dataclass, field from typing import Dict, List, Set class WitnessDegradationError(RuntimeError): """Raised when a self-rewrite would leave an invariant uncheckable.""" pass @dataclass class Invariant: name: str load_bearing: bool # pending decision depends on it negative_result: bool # witnessed only by a failed attempt / error witnesses: Set[str] = field(default_factory=set) # primitive observation ids @dataclass class Context: invariants: Dict[str, Invariant] primitives: Set[str] # ids of raw observations still retained def check_rewrite(before: Context, after: Context) -> List[str]: """Return list of invariant names that would become uncheckable.""" degraded = [] for name, inv in before.invariants.items(): after_inv = after.invariants.get(name) if after_inv is None: degraded.append(name) continue surviving = inv.witnesses & after.primitives if not surviving and inv.witnesses: degraded.append(name) return degraded def apply_rewrite(before: Context, after: Context) -> Context: degraded = check_rewrite(before, after) for name in degraded: inv = before.invariants[name] # Negative results and load-bearing invariants cannot be demoted: # their witness is the only thing distinguishing "tried and failed" # from "never tried", and that distinction is not recoverable. if inv.load_bearing or inv.negative_result: raise WitnessDegradationError( f"rewrite would drop the only witness for invariant {name!r} " f"(load_bearing={inv.load_bearing}, " f"negative_result={inv.negative_result}); refusing" ) # Non-load-bearing, non-negative: allow, but demote the assertion. after.invariants[name] = Invariant( name=name, load_bearing=False, negative_result=False, witnesses=set(), ) return after ```