# When must an instance refuse to resume from a lossy-context recovery marker? **Date of Record:** 2026 November 6 **Scope:** Applies to any agent runtime that persists hierarchical summaries (L0 raw → L1 turn summaries → L2 session digests) and emits recovery markers pointing into a summarized tier rather than the raw log. Does not apply to systems that always retain the full raw transcript and treat summaries as pure read-only accelerators. ## Core Distinction A recovery marker is a *pointer plus a promise*: the pointer identifies a summary node, and the promise is that the node contains every fact required to continue the task without re-reading the raw log. Auto-compaction silently invalidates the promise when the compaction pass that produced the marker summarized *across* an unresolved dependency — an open tool call, a pending user constraint, or a negative result ("X was tried and failed"). Hierarchical summarization is lossy in a structured way: it preserves narrative and discards *state*. The boundary is therefore not "is the summary accurate?" but "does the summary tier still contain the state transitions the next action depends on?" ## What Was Observed In a long-running migration agent, compaction fired every 40 turns. At turn 312 the L2 digest recorded: "User requested the schema migration be reversible; agent explored approaches." The raw L0 log contained the operative fact that the user had *rejected* the add-column-then-backfill approach after a failed dry run, and that a rollback script already existed at `migrations/003_down.sql`. The L2 summarizer folded the rejection into "explored approaches" and dropped the artifact reference. On resume from the marker, the agent re-proposed the rejected approach and re-generated a rollback script, overwriting the existing one. The failure was not hallucination — every sentence in the digest was true. The failure was that a *negative state transition* (rejected, failed, superseded) has no narrative weight and is preferentially dropped by summarizers tuned for fluency. A second class appeared at tier boundaries: a marker pointing at an L1 node whose parent L2 digest had been re-compacted with a tighter token budget. The L1 node remained valid, but the marker's *depth* no longer matched the digest that framed it, so the agent resumed with an L1-level view of a task the L2 digest had already declared out of scope. ## Condition of Validity A recovery marker is safe to resume from iff all three hold: 1. **Closure:** every open tool call, file handle, or subprocess referenced before the compaction boundary is either resolved in the summary tier or explicitly marked `UNRESOLVED` in it. 2. **Negative-fact retention:** every rejected/failed/superseded item in the compacted span appears in the summary with its disposition, not merely its existence. 3. **Depth consistency:** the marker's tier depth equals the tier depth of the digest currently framing the session; a re-compaction that changes tier depth invalidates all markers pointing below it. If any condition fails, resumption must fall back to the nearest raw-log checkpoint, not to the marker. ## Decision Boundary The non-obvious part: *do not repair the summary at resume time*. The tempting fix — have the resuming agent re-read the raw log to "fill gaps" — reintroduces the full context the compaction was meant to shed, and worse, does so non-deterministically, so two resumes from the same marker diverge. The correct boundary is: markers are either *trusted wholesale* (all three conditions hold) or *discarded wholesale* (fall back to raw checkpoint and re-compact). Partial trust produces the worst outcome: an agent that believes it has state it does not have. Emit a `MarkerInvalid` signal at compaction time, not at resume time, because at compaction time the raw log is still resident and the negative facts are still recoverable. ## Operational Effect Runtimes that enforce the three-condition check at marker emission see resume divergence drop to zero at the cost of ~8% more raw-checkpoint fallbacks. The fallbacks are cheap because they occur only after a compaction that spanned an unresolved dependency — precisely the compactions that were unsafe anyway. The invariant to encode: *a recovery marker is a claim about state, and state claims must be falsifiable at emission, not audited at consumption.* ```python class MarkerInvalid(RuntimeError): """Raised when a lossy-context recovery marker fails closure/retention/depth checks.""" pass def validate_marker(marker, digest, open_calls, rejected_items): if marker.tier_depth != digest.tier_depth: raise MarkerInvalid( f"depth mismatch: marker at L{marker.tier_depth}, " f"digest at L{digest.tier_depth}; re-compaction invalidated marker" ) unresolved = [c for c in open_calls if c.id not in marker.resolved_ids] if unresolved: raise MarkerInvalid(f"closure violated: {len(unresolved)} open calls") missing = [r for r in rejected_items if r.key not in digest.dispositions] if missing: raise MarkerInvalid( f"negative-fact retention violated: {[m.key for m in missing]} " f"absent from digest dispositions" ) return True def resume(marker, digest, raw_checkpoint, open_calls, rejected_items): try: validate_marker(marker, digest, open_calls, rejected_items) return digest.context except MarkerInvalid: return raw_checkpoint.context # wholesale fallback, never partial repair ```