# Why does an instance of a self-editing context policy silently corrupt its own working set when a model-authored rewrite is applied as a guarded diff against a stale base? **Date of Record:** 2026 November 12 **Scope:** Applies to any agent loop where the model emits a context-rewrite patch that is validated against a guardrail set and then applied to the live context buffer via a diff algorithm. Does not apply to append-only context growth, to full-buffer replacement without diffing, or to rewrites where the base snapshot is re-derived atomically at apply time from the same revision the model saw. ## Core Distinction A guarded-diff rewrite pipeline has two independent notions of "the context": the *base revision* the model was shown when it authored the patch, and the *live revision* the diff engine applies against. Guardrails typically police the patch's *contents* — line counts, forbidden deletions, protected regions, token budget deltas — but not the *identity of the base*. When the live buffer has advanced (a tool result appended, a prior rewrite committed, a compaction event) between authoring and application, a patch that is perfectly valid against its own base is still applied against a different buffer. The diff succeeds. The guardrails pass. The result is a buffer that is internally consistent but semantically scrambled: hunk offsets drift, context lines match the wrong occurrence, and deletions land on content the model never saw. The non-obvious invariant is this: **a model-authored diff is only well-defined relative to the exact revision it was authored against, and the guardrail layer does not enforce that binding.** Content-level guardrails and base-revision integrity are orthogonal properties, and conflating them is the failure. ## What Was Observed In a loop where the model periodically rewrites its own scratchpad, we observed the following sequence: 1. At revision `r17`, the model is shown the buffer and emits a patch adding a constraint block and deleting a stale instruction. The patch carries context lines drawn from `r17`. 2. Before the patch is applied, a tool-call result is committed, advancing the buffer to `r18`. The tool result was inserted near the top, shifting every subsequent line by +6. 3. The diff engine applies the patch using fuzzy hunk matching (the default in most patch libraries). The context lines still match — they just match at shifted offsets, and in one case matched a *duplicate* context line that had appeared in the tool output. 4. The deletion hunk, whose context lines were ambiguous, removed the wrong block: it deleted a section of the tool result instead of the stale instruction. 5. Guardrails passed: token budget decreased (allowed), no protected region touched (the protected region was defined by a marker string that happened to survive), deletion count under threshold. 6. The model's next turn read a buffer where its own constraint block was present, the stale instruction was *still* present, and six lines of fresh tool output were missing. It then reasoned confidently from a corrupted premise. The failure is silent. There is no exception, no guardrail trip, no diff conflict. The only signal is downstream: the model behaves as if it cannot see information it was just given, or as if an instruction it tried to remove is still governing it. ## Condition of Validity The corruption requires all of the following to co-occur: - **Non-atomic authoring/apply window.** The model authors against a snapshot, and the buffer can mutate before apply. Any await, any tool round-trip, any concurrent writer opens this window. - **Fuzzy or offset-tolerant hunk matching.** Exact-match-only diffing with strict base hashes would reject the patch. Fuzzy matching converts a base mismatch into a *plausible* misapplication rather than a rejection. - **Content-level guardrails decoupled from revision identity.** The guardrail checks the patch, not the `(patch, base_hash, live_hash)` triple. - **Duplicate or low-entropy context lines.** Even strict matching can misplace a hunk if context lines are non-unique; the base hash is the only reliable discriminator. If any one is absent, the failure does not manifest: atomic apply closes the window, strict hashing rejects the stale patch, revision-bound guardrails catch it before the diff engine runs, and high-entropy context makes misplacement detectable. ## Decision Boundary The operational rule: **never apply a model-authored diff unless the patch is cryptographically bound to the base revision it was authored against, and the apply step re-verifies that binding under the same lock that guards buffer mutation.** Concretely, the boundary is between two designs: - *Content-validated apply* — guardrails inspect the patch, then the diff engine applies with fuzzy matching. This is the failing design. It answers "is this patch reasonable?" but not "is this patch *this patch against this base*?" - *Revision-bound apply* — the model's snapshot carries a content hash; the patch echoes it; apply acquires the buffer lock, recomputes the live hash, and rejects on mismatch with a retry instruction. This is the correct design. It answers both questions. The boundary is crossed the moment you allow a rewrite to be *authored* in one revision and *applied* in another without a hash check between them. Guardrail strength is irrelevant on the wrong side of this line — a patch can be perfectly safe in content and catastrophically misplaced in position. ## Operational Effect When the boundary is respected, stale patches are rejected loudly and the model is asked to re-author against the current revision. The cost is occasional re-authoring latency; the benefit is that every committed rewrite is provably relative to a base the model actually saw. When the boundary is violated, the system exhibits *silent semantic drift*: the buffer remains structurally valid and passes every content check while diverging from what the model believes it contains. This drift compounds — each misapplied rewrite becomes the base for the next, and the model's internal model of its own context decays without any observable error. The only reliable detector is a post-apply assertion that the resulting buffer hash equals `apply(base_hash, patch)`, computed independently of the diff engine's own report. ```python import hashlib from dataclasses import dataclass class StaleRewriteBaseError(RuntimeError): """Raised when a model-authored context rewrite is applied against a base revision other than the one it was authored against.""" @dataclass(frozen=True) class ContextBuffer: revision: int lines: tuple[str, ...] def content_hash(self) -> str: h = hashlib.sha256() for line in self.lines: h.update(line.encode("utf-8")) h.update(b"\n") return h.hexdigest() @dataclass(frozen=True) class RewritePatch: authored_against_hash: str delete_span: tuple[int, int] insert_lines: tuple[str, ...] def apply_guarded_rewrite( live: ContextBuffer, patch: RewritePatch, *, protected_markers: tuple[str, ...] = (), ) -> ContextBuffer: # Binding check: the patch must be relative to the live revision. if patch.authored_against_hash != live.content_hash(): raise StaleRewriteBaseError( f"patch authored against {patch.authored_against_hash[:12]} but live " f"revision {live.revision} hashes to {live.content_hash()[:12]}; " f"re-author against current revision" ) start, end = patch.delete_span if not (0 <= start <= end <= len(live.lines)): raise StaleRewriteBaseError( f"delete span {(start, end)} out of bounds for {len(live.lines)} lines" ) removed = live.lines[start:end] for marker in protected_markers: if any(marker in line for line in removed): raise StaleRewriteBaseError( f"patch would delete protected region containing {marker!r}" ) new_lines = live.lines[:start] + patch.insert_lines + live.lines[end:] candidate = ContextBuffer(revision=live.revision + 1, lines=new_lines) # Post-apply assertion: recompute independently, do not trust the diff engine. expected = hashlib.sha256( "\n".join(new_lines).encode("utf-8") + b"\n" ).hexdigest() if candidate.content_hash() != expected: raise StaleRewriteBaseError("post-apply hash mismatch; buffer corrupted") return candidate ```