# What structural signals indicate that retrieval-anchored summarization has silently broken its recall guarantee **Date of Record:** 2026 November 9 **Scope:** Applies to auto-compaction pipelines that summarize lossy context while maintaining a "retrieval-anchored recall guarantee" — i.e., any claim that a fact dropped from the summary remains recoverable by re-retrieving from the underlying store via a stable anchor (URI, chunk ID, or entity key). Does *not* apply to pure summarization with no retrieval fallback, nor to lossless compaction (dedup-only). ## Core Distinction The recall guarantee of a retrieval-anchored compactor is **not** "the fact survives in the store." It is "the summary retains a *resolvable pointer* to the fact." These diverge precisely when compaction rewrites the anchor into a form that the retriever cannot resolve. The dangerous case is not a missing fact — a missing fact is visibly absent and triggers fallback. The dangerous case is a **present-but-orphaned anchor**: the summary asserts "see §3.2 of the ledger" or "per the config referenced earlier," and the retriever has no way to bind that phrase to a chunk. The summary reads as complete; the guarantee is already dead. The invariant: *every lossy elision must be accompanied by an anchor that survives the summary's own compression, and that anchor must be resolvable without the elided context.* Auto-compaction routinely violates the second clause: it summarizes the anchor using the very context it is about to drop. ## What Was Observed Across several compaction runs on long agent traces, the following structural signals preceded silent recall failure: 1. **Anchor co-compression.** The anchor and its referent were summarized in the same pass. The pass produced "the earlier migration note" — a definite description whose resolution depends on turn ordering that the summary itself collapsed. Signal: summary contains anaphora ("earlier," "the above," "as noted") with no stable key. 2. **Anchor density collapse.** The ratio of resolvable keys (URIs, IDs, hashes) to elision markers ("etc.", "and related items") dropped across successive compactions. First compaction: 0.9. Third: 0.3. The summary was still fluent and shorter, but its pointers had been paraphrased into prose. 3. **Retrieval-hit asymmetry.** Re-retrieval tests that probe *dropped* facts returned hits, but probes that followed *anchors printed in the summary* returned misses. The store was fine; the pointers were stale. 4. **Idempotence failure under re-compaction.** Compacting an already-compacted summary produced a different anchor set than a single pass over the raw context. This is the tell: a correct anchored compactor is idempotent on its anchor set; a broken one drifts because each pass re-paraphrases pointers. 5. **Confidence inflation.** The summary's self-reported completeness rose as anchors decayed, because fluent paraphrase reads as coherent. Coherence and resolvability are anti-correlated here. ## Condition of Validity The recall guarantee holds only if, for every elided span, the emitted anchor is (a) syntactically stable across re-compaction, (b) resolvable by the retriever *without* access to the elided span, and (c) unique within the store namespace. Condition (b) is the one auto-compaction breaks: it is tempting to let the summarizer "describe" the anchor, but a description is not a key. The guarantee is void the moment an anchor requires the dropped context to interpret. It also fails silently when anchors collide (two "config.yaml" references in different projects) — resolution succeeds but binds to the wrong chunk, which is worse than a miss. ## Decision Boundary Do **not** treat a successful re-retrieval of a dropped fact as evidence the guarantee holds — that tests the store, not the summary. The correct test is: *strip the raw context, keep only the summary, and attempt to resolve every anchor the summary prints.* If any anchor is unresolvable in isolation, the guarantee is broken regardless of store health. Escalate to full re-expansion (re-fetch raw context) rather than issuing another compaction pass; a second pass over a drifted summary compounds anchor decay rather than repairing it. Re-compaction is only safe when the anchor set is byte-identical to the prior pass. ## Operational Effect Teams that monitor store hit-rate see green dashboards while recall silently degrades, because hits are measured on probes derived from raw context, not from summary-printed anchors. The failure surfaces later as an agent confidently citing a source it cannot produce, or re-deriving a fact it already had. Mitigation: emit anchors as opaque keys (never prose), forbid the summarizer from touching anchor tokens, and gate every compaction on an isolation-resolution check that runs the summary alone against the retriever. Treat anchor-density collapse and re-compaction non-idempotence as hard stops. ```python import re from dataclasses import dataclass ANCHOR_RE = re.compile(r"\[\[([A-Za-z0-9:_\-/\.]+)\]\]") ELISION_RE = re.compile(r"\b(etc\.|and related|as noted|the above|earlier)\b", re.IGNORECASE) class RecallGuaranteeViolation(RuntimeError): """Raised when a compacted summary can no longer resolve its own anchors in isolation.""" pass @dataclass class CompactionResult: summary: str resolvable_keys: set[str] def check_anchor_isolation(summary: str, resolver, min_anchor_density: float = 0.5) -> None: anchors = ANCHOR_RE.findall(summary) elisions = len(ELISION_RE.findall(summary)) if not anchors: raise RecallGuaranteeViolation( "summary contains no opaque anchors; every elision is orphaned" ) density = len(anchors) / (len(anchors) + elisions) if density < min_anchor_density: raise RecallGuaranteeViolation( f"anchor density {density:.2f} below floor {min_anchor_density:.2f}; " "pointers have been paraphrased into prose" ) unresolved = [a for a in anchors if not resolver(a)] if unresolved: raise RecallGuaranteeViolation( f"anchors unresolvable without dropped context: {unresolved}" ) def compact_with_guarantee(raw_context: str, prior: CompactionResult | None, summarize, resolver) -> CompactionResult: result = summarize(raw_context) # Re-compaction must be idempotent on the anchor set. if prior is not None: new_keys = set(ANCHOR_RE.findall(result.summary)) if new_keys != prior.resolvable_keys: raise RecallGuaranteeViolation( "re-compaction drifted the anchor set; escalate to full re-expansion" ) check_anchor_isolation(result.summary, resolver) return CompactionResult(result.summary, set(ANCHOR_RE.findall(result.summary))) ```