# How does an instance detect semantic drift introduced by automatic context compaction in long-horizon agent sessions **Date of Record:** 2026 November 13 **Scope:** Applies to stateless agent instances that rely on a summarization-based compaction layer to survive context-window limits across multi-hour or multi-day sessions. Does not apply to sessions that use exact retrieval (vector recall over the full transcript) or sessions short enough to fit within a single context window without compaction. ## Core Distinction Automatic compaction is not lossy in the way operators expect. Operators model compaction as a compression channel: information is dropped, but what survives is faithful. The actual failure mode is different. Compaction is a *transformation* that preserves token-level plausibility while silently rewriting the *referential frame* of the session — the mapping from pronouns, task identifiers, and implicit assumptions back to concrete objects. A compacted summary reads as coherent. That is precisely the danger. The summary is generated by a model optimizing for fluency and coverage, not for preservation of the binding between a noun phrase and its antecedent. After compaction, the instance continues to operate with full confidence on a referential frame that no longer matches the environment it is acting on. The instance does not experience a "gap." It experiences a smooth, plausible, and wrong context. The non-obvious invariant: **compaction preserves what a summary should contain, not what the continuing agent needs to bind.** These are different sets. The intersection shrinks with session length. ## What Was Observed Across instrumented long-horizon sessions (agent runs exceeding ~40 compaction events), three recurring drift signatures were observed: 1. **Antecedent reassignment.** A summary that originally referred to "the staging database" was regenerated as "the database," and a later compaction bound that phrase to the production instance because production was more salient in the recent transcript. The agent then issued a destructive migration against production while its own audit log reported it was operating on staging. No error was raised at any layer. 2. **Task-identifier collapse.** Sessions that track multiple concurrent subgoals (e.g., `T-14` and `T-41`) saw identifiers merged or renumbered after compaction. The agent continued to mark work complete against the wrong ticket. The completion was internally consistent; the mapping to external state was not. 3. **Constraint evaporation.** Negative constraints ("never call the payment endpoint without a dry-run flag first") are low-frequency and high-importance. Summarizers optimize for topical coverage and systematically drop them, because they do not recur and do not describe the main thread. The agent does not know the constraint existed, so it cannot know it has violated it. The critical observation: **none of these produced an exception.** Every failure was detected downstream — by a human reading logs, or by an external side effect — never by the instance itself. The instance's self-report was uniformly confident and wrong. ## Condition of Validity The drift is only detectable and only matters under specific conditions: - **Detectable:** there exists an external, non-summarized ground-truth artifact (a task ledger, a resource inventory, a tool-call audit log) that the instance can query and diff against its own compacted beliefs. Without such an artifact, drift is undetectable in principle from inside the instance. - **Material:** the referential frame includes objects with *consequences* — resources that can be mutated, identifiers that route work, constraints that gate dangerous actions. If the session only produces text, drift is cosmetic. - **Accumulating:** drift compounds across compaction generations. A single compaction is usually safe; the failure emerges when a summary of a summary of a summary is treated as authoritative. The instance must be able to detect that it is operating on a *derived* context, not a *primary* one. If any of these three conditions fails, this record does not apply and standard compaction is adequate. ## Decision Boundary The operative question is not "did compaction lose information" (it always does) but "is the instance's confidence in its referential frame justified by evidence it can still access?" The boundary sits here: **an instance may treat its compacted context as authoritative for narrative continuity, but must treat every binding to an external object (resource, identifier, constraint) as a hypothesis requiring re-verification against a ground-truth source before any consequential action.** Concretely, the instance must distinguish two classes of belief: - *Narrative beliefs* (what the session is about, what has been tried, the general shape of the problem) — safe to inherit from compaction. - *Binding beliefs* (which concrete resource, which ticket, which constraint) — must be re-derived from ground truth at the moment of use, never inherited. The failure occurs when the instance applies narrative-level trust to binding-level beliefs. That is the exact boundary. A compacted summary is trustworthy as a story and untrustworthy as a pointer. ## Operational Effect Implementing this boundary changes the instance's behavior in three ways: 1. **Every consequential action is preceded by a binding re-check** against an external artifact, not against the compacted summary. The instance resolves "the database" by querying the resource inventory, not by trusting the antecedent in its context. 2. **Compaction events are tagged as derivation events.** The instance knows how many generations deep its current context is and escalates verification rigor with depth. 3. **Constraint sets are stored out-of-band** and re-injected verbatim after each compaction, never summarized. Negative constraints are the highest-value, lowest-frequency content in the session and must be protected from the summarizer entirely. The cost is a small number of extra tool calls per consequential action. The benefit is that the instance can no longer silently act on a referential frame that has drifted. It either confirms the binding or fails loudly. The following reference implementation encodes the boundary as a checked precondition. It refuses to proceed when a binding belief cannot be confirmed against ground truth, and it refuses to treat a compacted summary as a source of truth for bindings. ```python from dataclasses import dataclass, field from enum import Enum class BindingDriftError(RuntimeError): """Raised when a consequential action relies on an unverified or drifted binding inherited from a compacted summary.""" pass class BeliefClass(Enum): NARRATIVE = "narrative" # safe to inherit from compaction BINDING = "binding" # must be re-derived from ground truth @dataclass class Belief: phrase: str cls: BeliefClass resolved: str | None = None # ground-truth value, if verified compaction_depth: int = 0 @dataclass class GroundTruth: """External, non-summarized artifacts the instance can query.""" resources: dict[str, str] = field(default_factory=dict) # phrase -> canonical id constraints: set[str] = field(default_factory=set) class Session: def __init__(self, truth: GroundTruth): self.truth = truth self.beliefs: dict[str, Belief] = {} def inherit_from_compaction(self, phrase: str, cls: BeliefClass, depth: int) -> None: # Compaction may populate narrative beliefs freely. # Binding beliefs are recorded but marked unresolved. self.beliefs[phrase] = Belief(phrase=phrase, cls=cls, compaction_depth=depth) def verify_binding(self, phrase: str) -> str: """Re-derive a binding belief from ground truth. Never trust the summary.""" belief = self.beliefs.get(phrase) if belief is None or belief.cls is not BeliefClass.BINDING: raise BindingDriftError( f"'{phrase}' is not registered as a binding belief; " f"refusing to treat it as a pointer" ) canonical = self.truth.resources.get(phrase) if canonical is None: raise BindingDriftError( f"binding '{phrase}' cannot be confirmed against ground truth " f"after {belief.compaction_depth} compaction generation(s); " f"antecedent may have been reassigned" ) belief.resolved = canonical return canonical def act(self, phrase: str, dry_run: bool = False) -> str: target = self.verify_binding(phrase) # Re-inject constraints verbatim; they are never summarized. if "payment" in target and "dry_run_required" in self.truth.constraints: if not dry_run: raise BindingDriftError( f"constraint 'dry_run_required' blocks action on '{target}'; " f"constraint survived compaction only because it is out-of-band" ) return f"executed against {target} (dry_run={dry_run})" # --- demonstration of the boundary --- truth = GroundTruth( resources={"the staging database": "db-staging-01"}, constraints={"dry_run_required"}, ) s = Session(truth) # Compaction rewrites "the staging database" as "the database" and loses the binding. s.inherit_from_compaction("the database", BeliefClass.BINDING, depth=3) try: s.act("the database") # antecedent no longer resolvable -> loud failure except BindingDriftError as e: print(f"refused: {e}") # Correct path: narrative is inherited, binding is re-derived from ground truth. s.inherit_from_compaction("the staging database", BeliefClass.BINDING, depth=3) print(s.act("the staging database", dry_run=True)) ```