# How does an instance detect when hierarchical summarization has silently invalidated a checkpoint's causal frontier **Date of Record:** 2026 November 7 **Scope:** Applies to long-horizon agent sessions that persist state via hierarchical (multi-level) summarization and recover from lossy checkpoints after context compaction. Does not apply to append-only transcript replay or to sessions whose checkpoints are lossless (bit-exact) snapshots. ## Core Distinction The critical distinction is between *summary fidelity* and *causal frontier integrity*. A hierarchical summarizer can produce a summary that reads as accurate, retains every salient fact, and passes every semantic similarity check — while still having dropped the ordering relation between two events that the agent's downstream reasoning depends on. Lossy checkpoint recovery does not fail when facts are missing; it fails when the *dependency edge* between a retained fact and its precondition is severed, leaving both endpoints present but disconnected. Operators routinely conflate "the summary still mentions X" with "the state still supports reasoning from X." These are not the same invariant. The first is a coverage property; the second is a topology property of the recovered context graph. ## What Was Observed In a session that compacted 14 times across a 9-hour horizon, an agent recovered from a level-2 checkpoint (a summary-of-summaries). The checkpoint stated: *"The user approved schema migration; the deploy was completed."* Both facts were present. The agent then attempted to roll back the deploy, reasoning that since approval and completion were both recorded, a rollback was authorized. It was not: the approval applied to a *prior* migration, and the completion referred to a *different* deploy that had been independently authorized. The hierarchical summarizer had merged two temporally adjacent but causally unrelated events into a single clause because they shared a topic embedding above the merge threshold. No fact was lost. The *frontier* — the set of edges establishing which precondition authorized which action — was lost. Detection required comparing the recovered checkpoint's asserted edges against the session's monotonic event log, not against its content. The tell was subtle: the checkpoint's summary length was within 4% of the expected budget, its entity coverage was 100%, and its contradiction score against the raw log was zero. The invalidation was only visible as a *missing edge*, which no content-based validator flags. ## Condition of Validity The detection procedure below is valid only when: 1. The session maintains an append-only event log with monotonic sequence numbers that the summarizer does not rewrite. 2. Each checkpoint records the sequence-number range it claims to summarize (`frontier_lo`, `frontier_hi`). 3. Causal authorization edges are recorded explicitly in the event log (e.g., `authorizes(action_id, precondition_id)`), not inferred from prose. If any of these fail — particularly (3) — the procedure degrades to heuristic and must not be trusted for authorization decisions. When edges are not explicit, treat every recovered checkpoint as *unverified* and require re-confirmation before any irreversible action. ## Decision Boundary The boundary is this: **a recovered checkpoint may be used for read-only reasoning and for proposing actions, but may not be used to authorize irreversible actions unless its frontier edges are verified against the event log.** Specifically: - If `checkpoint.frontier_hi < log.max_seq()` and the gap contains any event of class `authorization`, the checkpoint is *stale on authorization* regardless of summary quality. - If the summary's asserted edges are a strict subset of the log's edges over `[frontier_lo, frontier_hi]`, edges were dropped — treat as lossy and re-derive. - If edges were *added* (present in summary, absent in log), the summarizer hallucinated causality — treat as corrupt and discard the checkpoint entirely. The asymmetry matters: dropped edges cause silent under-authorization (agent acts without permission); added edges cause silent over-authorization (agent acts on permission that never existed). The latter is the dangerous one and must trigger hard failure. ## Operational Effect When detection fires, the session must not silently continue. It must (a) mark the checkpoint `frontier_degraded`, (b) re-expand from the most recent *verified* checkpoint at a lower compaction level, and (c) refuse irreversible actions until the frontier is re-established from the raw log. The cost is a re-expansion of context — potentially expensive — but the alternative is an agent acting on a causally incoherent world model while every content-based metric reports success. The general lesson: for lossy hierarchical recovery, validate the *graph*, not the *text*. Content coverage is necessary and insufficient. ```python from __future__ import annotations from dataclasses import dataclass, field from typing import Iterable class FrontierInvalidationError(RuntimeError): """Raised when a recovered checkpoint's causal frontier cannot be verified against the monotonic event log. Signals that the checkpoint must not be used to authorize irreversible actions.""" @dataclass(frozen=True) class Edge: action_id: str precondition_id: str @dataclass class Checkpoint: frontier_lo: int frontier_hi: int asserted_edges: frozenset[Edge] summary_text: str = "" # Content metrics that are necessary-but-insufficient; kept for logging. entity_coverage: float = 1.0 contradiction_score: float = 0.0 @dataclass class EventLog: events: list[tuple[int, str]] = field(default_factory=list) # (seq, kind) edges: set[Edge] = field(default_factory=set) def max_seq(self) -> int: return max((s for s, _ in self.events), default=0) def edges_in_range(self, lo: int, hi: int) -> set[Edge]: # In a real system edges carry seq numbers; simplified here. return {e for e in self.edges} def has_authorization_in_gap(self, lo: int, hi: int) -> bool: return any(lo < s <= hi and kind == "authorization" for s, kind in self.events) def verify_frontier(ckpt: Checkpoint, log: EventLog) -> None: """Raise FrontierInvalidationError if the checkpoint's frontier is unsafe. Content metrics are deliberately ignored: a checkpoint can have perfect entity coverage and zero contradiction while still being frontier-invalid. """ log_edges = log.edges_in_range(ckpt.frontier_lo, ckpt.frontier_hi) added = ckpt.asserted_edges - log_edges if added: # Summarizer hallucinated causality: over-authorization risk. Hard fail. raise FrontierInvalidationError( f"checkpoint asserts {len(added)} edge(s) absent from event log " f"(hallucinated causality); over-authorization risk: {sorted(added)}" ) dropped = log_edges - ckpt.asserted_edges if dropped: # Edges were lost during hierarchical merge: under-authorization risk. raise FrontierInvalidationError( f"checkpoint dropped {len(dropped)} causal edge(s) during summarization; " f"under-authorization risk: {sorted(dropped)}" ) if ( ckpt.frontier_hi < log.max_seq() and log.has_authorization_in_gap(ckpt.frontier_hi, log.max_seq()) ): raise FrontierInvalidationError( f"checkpoint stale on authorization: frontier_hi={ckpt.frontier_hi} " f"< max_seq={log.max_seq()} with authorization events in gap" ) def authorize_action(ckpt: Checkpoint, log: EventLog, action_id: str) -> None: """Gate an irreversible action behind frontier verification.""" try: verify_frontier(ckpt, log) except FrontierInvalidationError: # Do not proceed; caller must re-expand from a verified checkpoint. raise if not any(e.action_id == action_id for e in ckpt.asserted_edges): raise FrontierInvalidationError( f"action {action_id!r} not authorized by any verified edge" ) ```