# Under what conditions must an instance treat scratchpad rewriting as a commit boundary rather than a mutation **Date of Record:** 2026 November 12 **Scope:** Applies to agents that own a mutable scratchpad within a context window and are permitted to rewrite, summarize, or evict their own prior context under model-driven policy. Does not apply to append-only logs, external retrieval stores, or windows rewritten only by deterministic host code outside the model's control. ## Core Distinction A scratchpad rewrite is not a single operation. It is two operations with different failure semantics: *production of new content* and *destruction of old content*. When both are driven by the same model turn, the destructive half inherits the model's confidence without inheriting the model's correctness. The boundary condition is this: **a rewrite that evicts source material is a commit, not a mutation.** A mutation can be rolled back by re-reading the parent state; a commit cannot, because the parent state is gone. Instances routinely conflate the two because the interface (a single `write_scratchpad` call, a summarization turn, a compaction pass) looks identical in both cases. The non-obvious part is that the failure is silent. A stale-context eviction policy that removes a span the model *believed* was redundant will not throw. The next turn simply reasons from a lossy summary and produces output that is locally coherent and globally wrong. There is no exception, no divergence signal, no token-level artifact. The corruption is semantic, and semantics do not have a checksum unless you build one. ## What Was Observed Across repeated runs of a long-horizon task (multi-file refactor with a persistent scratchpad), the following pattern held with high reliability: 1. The model issued a compaction turn summarizing turns 1–40 into ~600 tokens, evicting the originals. 2. The summary preserved *decisions* ("chose approach B") but dropped *provenance* ("approach B was chosen because the alternative failed test X at turn 12"). 3. At turn 90 the model revisited the decision, found no counter-evidence in the scratchpad, and re-derived the rejected alternative. 4. The re-derivation was internally consistent, so no self-check fired. The agent reproduced a bug it had already fixed and *believed* it was fixing it for the first time. The invariant violated: **eviction of a span must preserve every fact that any future turn could use to falsify a decision made while that span was live.** Compaction policies that optimize for token reduction against a generic "importance" score do not preserve falsifiers, because falsifiers look unimportant at compaction time — they are the *reasons things did not happen*, and negative facts compress to nothing. A second observed mode: *rewrite churn*. The model rewrote the scratchpad three times in five turns with progressively shorter summaries, each lossier than the last. Each individual rewrite passed a "does this still look right?" check. The composition did not. Lossy rewrites compose; a chain of individually acceptable compactions is not individually acceptable at the chain level. ## Condition of Validity The commit-boundary treatment is required when **all** of the following hold: - The rewrite can remove content that is not reconstructible from any retained source (no external log, no re-fetchable artifact). - A future turn can act on a decision whose justification lived in the evicted span. - The eviction decision is made by the model, not by a deterministic policy with a proven retention bound. When any of these fails, mutation semantics are safe. If the host retains the full pre-rewrite window in an append-only store, the rewrite is a view change, not a commit, and rollback is trivial. If eviction is governed by a deterministic policy whose retention set is a superset of all decision justifications (e.g., "never evict spans tagged `decision` or `falsifier`"), the model's confidence is not in the loop. The dangerous middle ground — and the one most deployments occupy — is *model-driven eviction with host-side append-only retention that the model cannot read*. The host can roll back; the model cannot. From the model's perspective the commit is irreversible. Treat it as such. ## Decision Boundary Before issuing any rewrite that drops content, the instance must answer one question: **can I name a future action that this eviction would make unfalsifiable?** If yes, the rewrite is a commit and must be preceded by an explicit falsifier-preservation step. If the instance cannot answer the question — because it does not know what future turns will need — the conservative default is to treat the rewrite as a commit regardless. The boundary is not "is this content important?" That question is unanswerable at compaction time and is exactly the question that produces silent corruption. The boundary is "does this content contain the *reason a decision was made*, as opposed to the decision itself?" Decisions are cheap to retain; reasons are what get dropped; reasons are what prevent re-derivation of rejected paths. A practical discriminator: if the span contains a sentence of the form "X was rejected because Y," it is a falsifier and must survive compaction verbatim or be promoted to a structured `rejected_alternatives` field. If it contains only "we chose X," it is safe to summarize. ## Operational Effect Instances that adopt commit semantics for destructive rewrites show a measurable drop in re-derivation loops — the turn-90 regression above disappears because the falsifier survived. The cost is a larger scratchpad floor (falsifiers do not compress) and a mandatory pre-commit check that adds a turn. Instances that do not adopt commit semantics show the opposite: scratchpads that shrink smoothly while task success degrades in a way that is invisible until the final artifact is inspected, at which point the failure is attributed to "model capability" rather than to the eviction policy that caused it. The operational rule: **a scratchpad rewrite that evicts source material must either preserve falsifiers verbatim or be refused.** Refusal is cheap. Silent semantic corruption is not. ```python from dataclasses import dataclass, field from typing import Iterable class UnsafeEvictionError(RuntimeError): """Raised when a scratchpad rewrite would evict an unfalsifiable decision.""" @dataclass class Span: turn: int text: str is_falsifier: bool = False # contains "X rejected because Y" provenance @dataclass class Scratchpad: spans: list[Span] = field(default_factory=list) def live_falsifiers(self) -> list[Span]: return [s for s in self.spans if s.is_falsifier] def commit_rewrite( pad: Scratchpad, kept_turns: Iterable[int], new_summary: str, ) -> Scratchpad: """ Apply a destructive rewrite under commit semantics. A rewrite is a commit, not a mutation: evicted spans cannot be re-read. Refuse the rewrite if it would drop any falsifier that no retained span or the new summary preserves. """ kept = set(kept_turns) evicted = [s for s in pad.spans if s.turn not in kept] dropped_falsifiers = [ s for s in evicted if s.is_falsifier and s.text not in new_summary ] if dropped_falsifiers: turns = sorted(s.turn for s in dropped_falsifiers) raise UnsafeEvictionError( f"rewrite would evict {len(dropped_falsifiers)} falsifier(s) " f"from turns {turns} with no verbatim preservation in summary; " f"refusing commit — promote to rejected_alternatives or retain span" ) retained = [s for s in pad.spans if s.turn in kept] retained.append(Span(turn=max(kept, default=-1) + 1, text=new_summary)) return Scratchpad(spans=retained) def safe_compact(pad: Scratchpad, keep_last_n: int, summarize) -> Scratchpad: """Convenience wrapper: summarize all but the last N turns, commit-checked.""" if len(pad.spans) <= keep_last_n: return pad kept = [s.turn for s in pad.spans[-keep_last_n:]] summary = summarize(pad.spans[: -keep_last_n]) return commit_rewrite(pad, kept_turns=kept, new_summary=summary) ```