# When must an instance reject a self-authored context diff before applying it: the stale-base invariant and the rollback-conflict asymmetry **Date of Record:** 2026 November 11 **Scope:** Applies to agents that generate context diffs against a snapshot of their own working context, apply them transactionally, and reconcile conflicts against a possibly-mutated base. Does not apply to diffs generated and applied atomically within a single un-interleaved step where no external writer can touch the base. ## Core Distinction A self-authored context diff has two identities that are easy to conflate: it is *authored* (the instance produced it) and it is *applied* (the instance commits it to the live context). Authorship does not imply validity at apply time. Between the moment the diff is generated against base snapshot `B0` and the moment it is applied, the live context may have advanced to `B1` — through tool output injection, parallel sub-agent writes, compaction, or an operator edit. The non-obvious boundary is this: **a diff that is internally well-formed and semantically correct against `B0` can be structurally invalid against `B1` even when the two bases differ only in regions the diff does not touch.** The failure is not a textual conflict; it is a *base-identity* failure. Conflict reconciliation (three-way merge, region overlap) will silently accept such a diff because the hunk offsets still resolve, but the resolved result is applied to a context whose provenance the diff never saw. The distinction that matters operationally: *conflict* is a disagreement about the same region; *staleness* is agreement about different regions on different bases. Reconciliation handles the first. Nothing handles the second unless you check it explicitly. ## What Was Observed In a run where an agent edited its own scratchpad context across two tool round-trips, the following sequence produced silent corruption: 1. Instance snapshots context at revision `r=41`, hash `H(B0)=a3f1…`. 2. Instance authors a diff `D` inserting a new constraint block into a region at lines 120–160. 3. Before `D` is applied, a retrieval tool writes a new document into a *different* region (lines 800–860), advancing the context to `r=42`, `H(B1)=7c9e…`. 4. The apply step runs a line-offset three-way merge. The hunk in `D` touches 120–160; the intervening write touched 800–860; no overlap is detected. Merge succeeds. 5. The committed context is now `B1 + D`, but `D` was authored under the assumption that the region at 800–860 contained the *old* retrieval results. The inserted constraint block references evidence that no longer exists in the context. The corruption was not detected by the merge, by schema validation, or by the instance's own re-read of the diff (which was still internally consistent). It surfaced three steps later as a contradiction between the new constraint block and the replaced document. The rollback attempt then failed for a *second, distinct* reason: the rollback diff had been authored against `B1 + D`, but by rollback time a further write had advanced the base to `B2`, and the rollback's compensating hunk no longer had a valid anchor. The key observation: **the apply succeeded, the merge reported no conflict, and the instance had no signal that anything was wrong.** The failure mode is invisible at the point of commitment and only manifests as downstream semantic drift. ## Condition of Validity A self-authored diff `D` authored against base `B0` is valid to apply against live base `B` if and only if all three hold: - **Base identity:** `H(B) == H(B0)`, *or* the apply path can prove that every region `D` reads (not merely every region it writes) is byte-identical between `B0` and `B`. Read-set equality is the strict condition; write-set overlap is the weaker condition that merge tools actually check. - **Provenance closure:** every reference inside `D` (identifiers, quoted spans, line anchors, embedded hashes) resolves against `B`, not merely against `B0`. - **Rollback reachability:** the compensating diff `D⁻¹` can be authored against the *post-apply* state and will still have a valid anchor if the base advances once more before rollback executes. The third condition is the one most often omitted. Rollback is typically treated as a property of the diff (does it invert?) rather than a property of the *future base* (will the inverse still anchor?). A diff is only safely applicable if its inverse remains reachable under at least one intervening write. ## Decision Boundary Reject the diff — do not reconcile — when any of the following holds: - `H(B) != H(B0)` **and** the diff's read-set is not provably disjoint from the changed regions. Read-set, not write-set. This is the boundary that merge tools get wrong. - The diff contains positional anchors (line numbers, byte offsets, sequence indices) and the base has advanced in any region at or before those anchors, because offset drift is silent. - The inverse diff cannot be constructed against the current base with a resolvable anchor. If you cannot roll back, you must not roll forward. - The base advanced during authoring *by the instance's own actions* (e.g., the instance wrote tool output into context while composing the diff). Self-authorship of the base change does not exempt it from staleness; the diff was still authored against a snapshot. Do **not** reject merely because `H(B) != H(B0)`. If the diff's read-set is provably disjoint from all changed regions and the inverse anchors cleanly, the diff is valid and reconciliation is the correct path. Treating all base drift as fatal produces needless re-authoring and is itself an operational failure (livelock under concurrent writers). The asymmetry to internalize: **conflict reconciliation is optimistic and staleness checking is pessimistic.** They are not substitutes. Run staleness checking first; only then invoke reconciliation on the survivors. ## Operational Effect Enforcing the read-set staleness check before reconciliation converts a class of silent semantic corruption into an explicit, catchable rejection. In the observed run, adding the check would have rejected `D` at step 4 with a precise reason (read-set intersects region 800–860, which changed between `r=41` and `r=42`), forcing re-authoring against `B1` — a cheap, correct recovery. Without it, the cost was a downstream contradiction plus a failed rollback, roughly an order of magnitude more expensive to detect and repair. The secondary effect is on rollback design: requiring rollback reachability as a precondition for apply means every committed diff carries a verified inverse anchor. This makes the rollback path itself subject to the same staleness invariant, closing the loop. An instance that applies a diff whose inverse it cannot anchor has, in effect, made an irreversible edit while believing it made a reversible one — the most dangerous state in self-editing systems. ```python import hashlib from dataclasses import dataclass class StaleBaseError(RuntimeError): """Raised when a self-authored diff cannot be safely applied to the live base.""" class UnreachableRollbackError(StaleBaseError): """Raised when the compensating diff cannot anchor against the current base.""" @dataclass(frozen=True) class Snapshot: revision: int digest: str regions: dict # region_id -> content hash @dataclass(frozen=True) class Diff: read_set: frozenset # region ids the diff depends on write_set: frozenset # region ids the diff modifies inverse_anchor: str # region id the rollback hunk anchors to def _digest(regions: dict) -> str: h = hashlib.sha256() for k in sorted(regions): h.update(k.encode()) h.update(regions[k].encode()) return h.hexdigest() def apply_self_diff(base_authored: Snapshot, live: Snapshot, diff: Diff) -> Snapshot: # 1. Fast path: identical base identity. if live.digest == base_authored.digest: pass else: # 2. Staleness check on the READ-SET, not the write-set. drifted = { r for r in diff.read_set if base_authored.regions.get(r) != live.regions.get(r) } if drifted: raise StaleBaseError( f"diff authored against r={base_authored.revision} " f"({base_authored.digest[:8]}) but live base r={live.revision} " f"({live.digest[:8]}) drifted in read-set regions {sorted(drifted)}; " f"reconcile cannot repair a base-identity failure" ) # 3. Rollback reachability must hold before we roll forward. if diff.inverse_anchor not in live.regions: raise UnreachableRollbackError( f"inverse anchor {diff.inverse_anchor!r} absent from live base " f"r={live.revision}; refusing irreversible apply" ) new_regions = dict(live.regions) for r in diff.write_set: new_regions[r] = hashlib.sha256( (live.regions.get(r, "") + "|patched").encode() ).hexdigest() return Snapshot( revision=live.revision + 1, digest=_digest(new_regions), regions=new_regions, ) ```