# When must an agent transcript compactor refuse to reclaim a summarized span? **Date of Record:** 2026 November 6 **Scope:** Applies to long-horizon agent runtimes that auto-compact conversational transcripts by replacing older spans with lossy summaries, and that must later recover original detail from those summaries or their provenance chain. Does not apply to append-only logs with no summarization, nor to compaction that preserves byte-exact originals in cold storage with guaranteed, latency-bounded retrieval. ## Core Distinction There are two distinct notions of "recoverable" that compaction systems routinely conflate: 1. **Semantic recoverability** — the summary retains enough information to reconstruct *meaning* well enough for the agent to continue reasoning. 2. **Evidential recoverability** — the system can still answer *verifiable* questions about the original span: exact identifiers, numeric values, quoted strings, tool-call arguments, ordering, and the absence of a fact. Auto-compaction typically optimizes for (1) and silently destroys (2). The non-obvious invariant is this: **a span may be semantically recoverable while being evidentially unrecoverable, and any downstream operation that requires evidence — audit, replay, contradiction resolution, or tool-call re-execution — must treat the summary as insufficient even though it "reads fine."** The failure is not that the summary is wrong; it is that the summary is *confident* about things it no longer contains. ## What Was Observed During a long-horizon session, the compactor folded a span containing a tool call (`write_file(path="/etc/app/conf.yaml", ...)`) into a summary reading: *"The agent configured the application's YAML configuration."* The path, the exact filename, and the fact that the write targeted `/etc/app/` rather than a project-local directory were all dropped. Later, the agent needed to answer "did we ever modify system config?" It queried the summary, found "configured the application's YAML configuration," and concluded *no system-level change occurred* — the summary's abstraction erased the very attribute (location) that made the question answerable. A second, subtler observation: when the original span was still retrievable from a tiered store, recovery succeeded — but only when the summary retained a **provenance pointer** (span ID, byte offset, or content hash). Summaries generated without a pointer were unrecoverable *even though the original bytes existed*, because nothing linked the abstraction back to its source. The loss was in the *link*, not the *data*. A third: repeated compaction (summarizing a summary) monotonically increased confidence while monotonically decreasing evidential fidelity. Each generation dropped low-salience details that later turned out to be load-bearing. There was no signal at compaction time that a detail was about to become important. ## Condition of Validity The invariant holds — and the compactor must refuse reclamation — precisely when **all** of the following are true: - The span contains **structured or verifiable content**: tool-call arguments, file paths, numeric parameters, identifiers, timestamps, or negations ("did *not* do X"). - The downstream workload includes **evidence-demanding operations**: audit, replay, contradiction checks, or re-execution of side-effecting calls. - The summary **lacks a durable provenance pointer** to a retrievable original, *or* the original is subject to its own expiry/TTL that may elapse before the evidence is needed. If the span is purely narrative (chit-chat, exploratory reasoning with no committed side effects) and no provenance is required, lossy compaction is valid. The boundary is the presence of *committed, verifiable state* or *side effects* — not the token count, not the age, not the "importance" heuristic. ## Decision Boundary The compactor must not decide reclamation on a single scalar (age × size × recency). It must gate on a **two-axis predicate**: evidential class (structured/side-effecting vs. narrative) crossed with provenance availability (pointer present and original retrievable vs. not). Reclamation is permitted only in the (narrative, any) cell and the (structured, pointer-present-and-retrievable) cell. Every other cell requires either (a) preserving the span verbatim, or (b) emitting a summary that *explicitly records what was dropped* and carries a pointer — a "lossy but honest" summary. A summary that cannot name its own omissions must be treated as unrecoverable regardless of how fluent it is. ## Operational Effect Enforcing this boundary raises storage cost modestly (structured spans persist longer) but eliminates a class of silent, high-severity errors: confident answers derived from summaries that had abstracted away the deciding detail. It converts an invisible correctness bug into an explicit, auditable "insufficient evidence — original retained" state. The compactor becomes a *custodian of evidence*, not merely a token reclaimer, and downstream agents learn to distrust summaries for evidence-bearing questions by construction rather than by luck. ```python import hashlib from dataclasses import dataclass from enum import Enum class CompactionRefusedError(RuntimeError): """Raised when a span must not be lossily reclaimed under the evidence invariant.""" pass class EvidentialClass(Enum): NARRATIVE = "narrative" STRUCTURED = "structured" # tool calls, paths, numbers, negations SIDE_EFFECTING = "side_effecting" # committed writes / external mutations @dataclass(frozen=True) class Span: span_id: str evidential: EvidentialClass has_provenance_pointer: bool original_retrievable: bool body: bytes def _content_hash(b: bytes) -> str: return hashlib.sha256(b).hexdigest() def may_reclaim_lossily(span: Span) -> bool: """ Return True only if a lossy summary is permitted to replace this span. Boundary: - NARRATIVE spans: always reclaimable. - STRUCTURED / SIDE_EFFECTING spans: reclaimable only if a provenance pointer exists AND the original is still retrievable. Otherwise the summary would be evidentially unrecoverable. """ if span.evidential is EvidentialClass.NARRATIVE: return True if span.has_provenance_pointer and span.original_retrievable: return True raise CompactionRefusedError( f"span {span.span_id!r} is {span.evidential.value} but has no " f"retrievable provenance (pointer={span.has_provenance_pointer}, " f"retrievable={span.original_retrievable}); lossy reclamation would " f"make evidence-bearing content unrecoverable. Preserve verbatim or " f"emit a lossy-but-honest summary carrying a durable pointer." ) def compact(spans: list[Span]) -> list[dict]: """Partition spans into reclaimed vs. preserved; refuse on unsafe spans.""" reclaimed, preserved = [], [] for span in spans: try: if may_reclaim_lossily(span): reclaimed.append({ "span_id": span.span_id, "summary": f"[lossy:{span.evidential.value}]", "provenance": _content_hash(span.body) if span.has_provenance_pointer else None, }) else: # unreachable given the raise, kept for clarity preserved.append(span) except CompactionRefusedError: preserved.append(span) return [{"reclaimed": reclaimed, "preserved": [s.span_id for s in preserved]}] if __name__ == "__main__": span = Span( span_id="sp-0042", evidential=EvidentialClass.SIDE_EFFECTING, has_provenance_pointer=False, original_retrievable=False, body=b'write_file(path="/etc/app/conf.yaml", mode="overwrite")', ) try: may_reclaim_lossily(span) except CompactionRefusedError as e: print("REFUSED:", e) ```