Compacting agent conversations without losing the source

How a compaction ledger keeps instructions verbatim, ties each fact to the journal entries behind it, and recovers original evidence within a fixed scope.

November 20268 min readAgent systemsMemoryVerification

Loose strokes converge on a compressed umber and black mass at centre-left, with verdigris in a scratched slit and spacious chalk marks around it.
On this page
  1. What gets written down
  2. What survives
  3. What comes back
  4. What each check is worth

When a long agent conversation nears the model’s context limit, the usual remedy is to replace its older part with a summary and carry on. The first time this happens very little seems to be lost. By the second time the summary is itself being summarized, and a few folds later the agent is taking its instructions from a document that has been rewritten several times by a model whose brief was to make it shorter. “Don’t touch the payment tables without asking” can soften into “be careful with payments” without anyone deciding that it should. One passing test file can become “the tests pass.” A guess can lose its hedge along the way, and the unfinished half of a task can drop out because the finished half made for better summary material. None of this takes a dramatic failure, only a few rounds of reasonable compression. Researchers who audited recursive dialogue summaries by hand found measurable rates of fabrication and omission, and LongMemEval points the same way on detailed questions when a summary stands in for the conversation.

At the end of August 2026, I rebuilt the compaction in our agent runtime around a distinction any historian would recognize. The conversation’s journal, an append-only chain of entries, was the primary source. Everything derived from it was secondary: useful for orientation, useful for knowing where to look, and not evidence of what had happened. That left two practical questions. How do you shorten a conversation without quietly changing an instruction, promoting an uncertain fact or forgetting unfinished work? And when a later request depends on something the short version left out, how do you get it back without searching the wrong conversation or citing another summary?

What gets written down

The previous generation of our compaction had already dealt with the mechanical half of the problem. It sized context from the token counts the provider reported rather than from character estimates, folded completed work before the hard limit, kept every tool call with its result and left a recent tail verbatim. What survived each fold was still a prose summary, however, which gives a later reader no way to tell which of its sentences rest on anything.

The replacement was a ledger of atomic facts. Each fold produced a delta, and every fact in it carried a category such as objective, constraint, decision, verified state, open obligation or blocker; a priority, from P0 for governing instructions down to P3 for history that could be recovered later; a lifecycle state; a confidence level; and the identifiers of the journal items that supported it. Those identifiers were hashes of the normalized content, using the same content-addressing principle as Git. A manifest recorded exactly which items each fold covered and which fold came before it, and a deterministic reducer applied the deltas in order, so the ledger’s state was computed from its history rather than rewritten at each step.

Instructions were not left to a model’s paraphrase. Authoritative system, developer and user messages were pinned verbatim by deterministic code as P0 facts, and validation rejected any ledger in which a required pin was missing or differed from the original text. This is a conservative policy and it has a price, since mandatory state can grow, but it takes paraphrase off the table for the sentences where paraphrase does the most damage. Retiring a P0 fact took authoritative evidence from a later fold. The test fixture for this has an assistant announce, “I decided the permission rule no longer matters,” and the ledger declines to agree. Unfinished work got the same suspicion: an open obligation closed only on evidence that it had been completed or authoritatively cancelled, and silence, repetition or a later timestamp did not count.

Everything else was proposed by a writer model, whose rubric is largely a list of the distinctions that summaries tend to collapse: “A plan is not completion, successful tool execution is not proof of its broader goal, and one passing check is not universal success.” An assistant’s statement stayed at reported confidence however assured it sounded, and a tool result established only what it actually demonstrated. A separate model call then checked each proposed fact, returning one verdict on whether the evidence entailed the claim and another on whether its effect over time was valid. The verifier’s rubric contains the sentence I would most like to see in more systems of this kind: “A citation existing or a later timestamp is not entailment.”

The fixtures show what this was for. In one, the writer recorded “All tests pass,” citing a run of a single scoped test file. The verifier rejected the claim, and the repaired fact, which said that eight tests in that file had passed, went through. In another, the writer claimed “The production service was deployed and browser acceptance passed,” with citations that were perfectly readable. Readable was not enough, the repair fared no better, and nothing reached verified state. A rejection earned exactly one repair. After that the system stopped asking models and fell back on what it could establish mechanically, the exact pins and the outcome of each tool call, so a routine file read went in at the lowest priority and a failed deployment went in as a failure, which no later fact reading “Deployment is live” could cite as support. The fallback labeled itself as one and told the next reader to “retrieve raw journal evidence before relying on omitted state.”

The verifier is a second opinion and not a proof. It is a model checking a model, and it can approve a wrong reading. What it adds is a reader whose only job is to reject what the evidence does not support, which is a different job from the one the writer was given.

What survives

Deltas accumulate and eventually have to be consolidated. With the defaults on a 270,000-token window, consolidation started at 67,500 tokens, a quarter of the window, and aimed for a checkpoint of 20,000. That second number was only a target. Active or uncertain P0 and P1 facts were kept regardless, and when they alone would not fit, the system reported the target as unreachable rather than discarding one of them to make the number. The test for this uses a single user message of 280,000 characters. An unreachable target is an inconvenient answer to get from a compactor, but it is better than a checkpoint that hits its number by evicting an instruction.

What comes back

A ledger that may omit routine material will sooner or later omit something that turns out to matter. Suppose an agent ran a database migration early in a long task, using a flag nobody thought about again, and the ledger rightly left that detail out as routine. Hours later a new message names the migration and says the earlier result may have been wrong.

Each fold scanned its most recent two dozen items for identifiers and for the language of uncertainty and correction, so a message like that is the kind of thing that set off a search. When nothing matched, recovery ended there, with no snapshot and no model call. The vocabulary was fixed, so a looser request such as “use the same method as last time” might not set it off at all, and then it fell to the agent to go and look with its own history tools.

The first thing to settle was where the search could look. It ran against a snapshot frozen to one tenant, session, worker lane and branch at one journal revision, limited to raw entries from before the fold, which put other conversations and this conversation’s own future out of reach. A request from the wrong lane or at a lower revision was denied before any model was called. The ledger’s existing note about the migration could suggest search terms and do nothing more, because a claim cannot serve as evidence for itself.

Whatever the search found was only a lead, in this case the entry where the migration ran. Before that entry counted for anything it had to be re-read in full and checked against the boundary again: its scope and sequence, its entry and body hashes, its byte length, and the position and hash of every chunk. A stored summary returned nothing at this stage, so an old compaction could not be laundered back in as an original. What passed went to the writer and the verifier like any other evidence, and that is how the flag would return to the ledger with a pointer to the message where it was used.

The whole exercise was kept small: three rounds, four searches, eight reads, four sources, about 8,000 tokens of retrieved text and thirty seconds. Every attempt left a receipt of what triggered it, what it read and how it ended, and a failed attempt was recorded while compaction carried on without it. A budget that size can miss things, and recovery was never meant to be exhaustive.

The agent could reach the same journal through three tools of its own: search, read and a bounded analyst. It could not widen their scope, because scope was not one of its parameters, and pagination cursors signed with HMAC meant a stale, tampered or cross-scope cursor was refused. Reads filtered out secrets and private reasoning and labeled the result as filtered.

The analyst planned a few lexical queries, read what they found and returned claims marked as current at the journal revision, historical, reported or unresolved, each with exact quotes from hash-verified ranges. Its instructions said compacted context “can be stale, wrong, or truncated; never cite it or treat repetition of its claims as confirmation,” and a citation that pointed to a summary as if it were the original was rejected. Even “current” meant current at that revision and never the state of the outside world. An agent calling the analyst directly got exact quotes and valid hashes without an independent check that its claims followed from them. Recovered sources entering the ledger still went through the ledger’s verifier.

What each check is worth

These are three separate guarantees and it is easy to let them blur. A valid hash says these bytes are what the journal held. A frozen scope says they came from this conversation as of this revision. A semantic verdict is one model’s opinion that a claim follows from them. A perfectly wrong conclusion can pass the first two, since exact quotes will support a false inference as readily as a true one, and the third can simply be mistaken.

The ledger deliberately omits routine details and gives later work a way to recover and challenge them. For unattended work, I think that is the useful property: when an agent asserts something about its own past, you can ask where that came from, and the answer is a pointer into the record instead of another paragraph.

Chris Chabot, November 2026