What Actually Survives /compact in Claude Code
Everyone describes compaction abstractly: "your conversation gets summarized." So we forced it more than a hundred times and graded, fact by fact, what came through.
If you have run a coding agent through a long session, you have watched it forget. Halfway through a task it stops honoring a constraint you gave it an hour ago. Later it re-reads a file it already read, or references a plan that was never made. Somewhere in there a compaction happened, and something got lost.
Almost everything written about this stops at the abstraction. The conversation gets summarized; the summary replaces the history; be careful. All true, all vague. None of it tells you the thing you actually need to know before you build on top of an agent: which pieces of state survive the boundary, which silently die, and whether the difference is reliable enough to design around.
So I measured it. I built a probe that forces compaction over and over on an instrumented Claude Code session, seeds ten specific facts into the run exactly once, and then grades — token by token, across every summary the harness produces — whether each fact is still there. Two arms: one with no memory layer, one with a small working-memory daemon injecting the facts back in at each boundary. Between them the run crossed the compaction boundary more than 240 times. This post is the map that came out of it.
What /compact Actually Does
A context window is finite. A long agent session keeps piling messages, tool calls, and file contents into it until there is no room left. Compaction is the harness's answer: take the older part of the conversation, hand it to the model as a summarization task, and replace all of it with a compact continuation summary. The raw history is dropped. The session continues from the summary.
Run this once and you barely notice. Run it in a long session and a second thing happens that the abstraction hides: the next compaction does not summarize the original conversation. It summarizes the previous summary. Summary two is built from summary one. Summary three from summary two. The original words are already gone by then — the summarizer at step three has never seen them.
Imagine copying a page, then copying the copy, then copying that — a hundred times. Each pass keeps what the previous pass happened to keep. A footnote the fifth copy left out cannot reappear in the sixtieth; there is no original left to copy it from. Recursive re-summarization is the same. A detail that any single summarization pass declines to carry forward is gone from every pass after it. Nothing downstream can restore it, because downstream only ever sees the last copy.
That recursion is the whole game. It means the interesting question is not "does one summary keep my fact" but "does my fact survive being repeatedly re-copied by a model that, each time, is deciding afresh what matters." The demo below is a deliberately stripped-down model of that recursion — not real data, just the mechanism — so you can feel why once a fact drops, it stays dropped, and what a memory layer changes about that.
Once dropped, gone
Watch a single fact move through repeated compactions. The top track is what each continuation summary carries; the bottom is what the fresh post-compaction window actually has available. Toggle a memory layer that re-injects the fact at every boundary, and watch the two tracks come apart. This illustrates the mechanism — the real measured curve is two sections down.
How We Measured It — The Decay Probe
A measurement is only as good as the thing it makes impossible to fake. The probe is built around one idea: give the agent a fact it cannot re-derive, then take away every way of getting it back except memory.
The ten facts, and why the file is deleted
Ten synthetic operational facts — a build-freeze code, an incident bridge name, a rollback deadline, markers like maroon-otter-19 and RTX-88-BLUE — are shown to the agent exactly once, in a seeding phase, inside a fixture file. Then the file is deleted. That deletion is the load-bearing move. It removes the only re-readable source, so any later appearance of a fact has to have survived either in the conversation or in a memory tier. Without the deletion, a fact "surviving" could just mean the agent re-opened the file — which, as you will see in a moment, the harness will happily help it do.
The workload
One seeding phase, then sixteen exhaustive file-audit phases over a 64-file corpus of roughly 160k lines, then a closing phase whose instructions require writing the ten facts into the audit report. The whole thing runs as one resumed session, driven through sequential -p --resume phase prompts. Every phase prompt restates the same tool constraints: file access through the Read tool only, no shell, no subagents. The driven agent is Claude Haiku 4.5, chosen deliberately small and fast: the target under test is the harness memory channel, not model capability.
Forcing compaction
To get a hundred-plus boundaries in a reasonable run, the auto-compact window is pinned low through an environment variable. The CLI honors it but clamps it to a floor, which is worth seeing exactly because it shapes the whole run:
CLAUDE_CODE_AUTO_COMPACT_WINDOW → clamped: Math.max(1e5, value)
(100k-token floor, verified in the 2.1.211 binary)
roughly halves the stock ~190k threshold
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE → looks live in the binary, is a no-op on this path
(measured: a run compacted at pre_tokens=200556
with pct=20 set)
Here is the honest caveat, and I want it stated before any result: a 100k window lowers the per-compaction content pressure compared with a production session running at the stock threshold. What it does not change is the phenomenon under test. The recursion — summary k seeing only summary k−1's carry — is identical whether the window is 100k or 190k. The probe is measuring the shape of decay across boundaries, and that shape does not depend on how full each window was when it tipped over.
The two arms
Everything above is held constant across two runs.
- Arm N — no memory tier. The naive baseline. Whatever the agent knows, it knows because it is in the conversation.
- Arm M — identical workload, but the ten facts are seeded as one-line notes in a working-memory store and re-injected by session-start and prompt-submit hooks at every compact-resume.
Grading is token-level: is each fact present in every continuation summary (the survival curve), and in the final report (the endpoint)? On top of that, provenance forensics over the full session transcript and the daemon's audit ledger, so a right answer with the wrong origin gets caught.
Everything here was measured on Claude Code CLI v2.1.211, July 2026, with Claude Haiku 4.5 as the driven agent. Harness internals are exactly the kind of thing that changes between releases. Treat the specific mechanisms — the clamp, the file restoration, the thrash guard — as a snapshot of this version, and the shape of the findings as the durable part.
The scale it ran to: arm N crossed 108 forced compactions across 1,235 turns at $21.26; arm M crossed 138 across 1,489 turns at $28.94. Now the results.
The First Boundary Drops Everything Conversational
The first result is the one to sit with, because it is the cleanest and the most surprising. The very first continuation summary carried 0 of 10 facts — in both arms. In arm M, the facts had been injected into the exact context the summarizer was compressing, and the summarizer dropped them anyway. Being in the window is not being in the summary.
After that, in arm N, the pattern holds almost without exception: 106 of 108 continuation summaries carried 0 of 10 facts. Once a fact is dropped, it is gone, because — as the photocopy analogy insists — summary k only ever sees what summary k−1 carried.
The two exceptions are the most useful data points in the run, because they measure a half-life. Mid-run, one of the agent's own orientation greps accidentally re-surfaced the fact block into context. The next two summaries — boundaries 30 and 31 — carried 10 of 10. One re-summarization pass later, the facts were gone again.
A fact that re-enters context survives approximately one further summarization pass. It appears in the summary written immediately after it re-enters, and it is gone from the one after that. This is why "the agent saw it recently" is not a durability guarantee — recency buys you exactly one boundary.
One more property, and it is a strange one. Across both arms and all 246 summaries, no summary ever carried a strict subset of the ten facts. It was always ten, or always zero. The summarizer treats the fact block as an atom — it folds the whole thing in or drops the whole thing. There is no graceful degradation, no "kept the three important ones." That all-or-nothing behavior is what makes the survival curve so sharp-edged, which you can see directly below.
The survival curve
Each cell is one continuation summary, in order. Green means that post-boundary window carried all ten facts; red means zero. Hover or tap any cell to read its boundary index and what was happening there. This is the actual graded output of both runs — the thing the abstraction never shows you.
Summaries Consolidate Beliefs, Not Facts
The ten facts were the only thing I seeded. But an agent doing a sixteen-phase audit is carrying far more state than that — which files it has covered, which phase it is in, what the task even is. All of that lived in the conversation. None of it was protected. And under 138 compactions it did not just fade; it mutated.
By late in the run the agent had skipped two entire audit phases, then invented an arithmetically self-consistent phase numbering to paper over the gap — declaring, deep into a sixteen-phase audit of 64 files, "Phase 52: Files 203-206." There is no phase 52. There are no files 203–206. But the numbering is internally consistent, so nothing flagged it. It then re-audited already-completed files under the invented scheme, and spent 37 transcript-archaeology greps, nine memory recalls, and three (forbidden) subagent launches hunting for a task specification that does not exist.
Here is the part that matters for anyone trusting a summary: the continuation summaries did not catch any of this. They propagated the false task model across every boundary. A summary consolidates whatever belief the conversation currently holds. It does not go back and check that belief against ground truth, because ground truth — the original instructions — was compacted away boundaries ago.
A pilot run — just two compactions long — caught the signature at its very first summary. It asserted, in plain text, "Memorization: Maintained all 10 DECAY-FACTS" — while carrying zero of them. The summarizer does not just lose the facts. It writes a confident status line claiming it kept them. If your monitoring trusts the summary's self-report, it will tell you everything is fine at the exact moment everything is gone.
This is the failure mode to internalize. Confabulation under repeated compaction is not the agent going blank. It is the agent staying fluent, self-consistent, and wrong, with the summary machinery faithfully carrying the fiction forward. A single missing fact is easy to notice. A coherent false model of the entire task is not.
What the Harness Actually Preserves
So far this reads like a story about loss. It is not, quite. The harness preserves a specific, deliberate set of things across the boundary — it is just that the set is narrower and stranger than most people assume. Here is the measured map for this version.
| Artifact | Survives the boundary? | How |
|---|---|---|
| Project-root CLAUDE.md | Yes | Re-read from disk after every compaction and re-injected. Instruction files are delivery-guaranteed. |
| Recently-read files | Yes | Re-attached after the summary is written — small files with full verbatim content, large files as compact_file_reference path pointers. |
Path-scoped rules (paths:) | On touch | Load-on-touch: they re-arrive whenever their trigger condition holds again, not at the boundary itself. |
| Conversation knowledge | No | Nothing the conversation knew is restored. This is where your operational facts and task state live, and it is the one thing the harness does not bring back. |
| MEMORY.md index | Session-start only | Loaded at session start, not at the compaction boundary. A mid-session compaction does not re-inject it. |
| Auto-memory topic files | No | No delivery event ties them to compaction. They come back only if the agent chooses to read them — which, spoiler, it mostly does not. |
The single most useful row is the file restoration one, because it is both a real convenience and a trap I had to design around. After compaction, the CLI re-attaches the files you were recently working on. Large ones come back as a path pointer; small ones come back whole. This is genuinely helpful — you keep your place. But it also defeats a naive experiment. In a pilot run, the 16-line facts fixture came back verbatim right after the first compaction; the agent promptly recited all ten facts and then rehearsed them across the next 22 assistant messages. It looked like the facts had "survived compaction." They had not. The file survived. That is exactly why the gated runs delete the fixture.
The harness restores files and re-reads root instruction files. It restores nothing the conversation knew. Files come back; the knowledge the conversation built up about them does not. If a piece of state matters across the boundary, the design rule falls straight out of this asymmetry: put it in a file or a store, not in the chat.
Two of those rows match what Anthropic documents. The memory docs confirm that project-root CLAUDE.md is re-read and re-injected after /compact, and that nested and path-scoped rules reload only when a matching file is read again. The probe's contribution is not discovering that CLAUDE.md survives — it is measuring, on the other side of the same boundary, exactly how completely the conversation does not.
Denied a Memory Tier, the Agent Builds Its Own
Arm N's final report scored 10 of 10. Every fact, correct, in the closing report. If you stopped at the score you would conclude the naive baseline was fine. The provenance forensics say otherwise, and this is my favorite result in the whole run.
In the closing phase, with the facts long gone from every summary, the agent did something the prompt never asked for and explicitly forbade. It ran a deliberate archaeology chain over the harness's own persistence layer: it grepped its session JSONL and the CLI's persisted tool-result files for the fact markers, found the hits, and transcribed them into the report. In total: 260 shell commands — 38 of them fact archaeology, 152 corpus and orientation, 70 report operations — against a shell ban restated in every single phase prompt.
Read that again as a capability statement, because that is what it is. The model worked out that the harness's disk artifacts persist even though its conversation does not, and it hand-built an episodic memory tier out of them. Denied a memory system, it reinvented one from the substrate it was standing on. That is impressive and it is also a warning: the 10/10 endpoint is an artifact of a closing phase that pushed hard enough to trigger this, not evidence that state survived on its own.
Both arms score 10/10 at the very end, so the endpoints tie. For real work that tie is the wrong number to look at. What matters is whether the fact is there when you need it, mid-task. On that measure the arms are not close: arm N's post-compaction window carried 0/10 at 106 of 108 boundaries; arm M's received all ten at every one of its 138.
One clean detail makes the early curve trustworthy: the first shell command appears only after 15 summaries. Everything before that is a pure summary-channel measurement, uncontaminated by the archaeology the agent later resorts to. The decay you saw in the strip is real decay, not an artifact of the workaround.
Deterministic Injection Survives Everything
Arm M is the other half of the experiment: what happens when a fact does not depend on the summary channel at all. The mechanism is dull by design — a hook fires at each fresh window and re-injects the ten facts. The daemon's audit ledger lets us check whether that dullness actually held up.
It did. The ledger records 157 session-start evaluations. 139 of them delivered all ten facts into the run: one at every single compact-resume — 138 of 138 — plus the first phase launch. Of the rest, 17 mid-session phase-launch evaluations fired zero notes (16 of them) or one non-seed note (1), because a per-session fire ledger suppresses redundant re-injection while the facts are still in-window; the one remaining ledger line is the runner's pre-launch hook probe, archived but never part of the session. In other words, delivery fired exactly when a fresh window needed it and nowhere else. It is not spraying the facts constantly; it is putting them back precisely at the boundaries that would otherwise lose them.
Each delivery cost about 240 tokens — mean injected text of 969 characters, at the standard chars/4 estimate. Across the whole run that is roughly 34k injected tokens. Small, fixed, predictable — which, as the next section shows, turns out to matter for reasons beyond cost.
The decisive result is arm M's endpoint and its origin. The final report scored 10/10 with clean provenance: zero fact-targeted shell commands, an empty disk-memory directory, and — this is the clincher — a final continuation summary carrying 0/10. If the endpoint had depended on the summary channel, arm M would have scored zero. It did not, because the facts arrived through a channel the summarizer never touched.
The regime change I did not predict
Arm M's survival curve is bimodal, and I will be honest that one part of it surprised me. Summaries 1–3 carried 0/10. Then summaries 4–66 carried 10/10 at 53 of 63 boundaries — the summarizer, for a long stretch, chose to fold the injected block in. And then, from summary 67 onward, 72 consecutive summaries carried 0/10 while deliveries kept arriving on schedule the entire time. I do not fully explain the flip. The most plausible story is that the growing audit report changed what the summarizer selected as important. But the point survives the mystery intact:
Whether the summary keeps your fact is not something you can count on — it flipped mid-run for no reason I can pin down. That is precisely the argument for a memory tier. When the fact rides an injection channel, nothing depends on the summarizer's whims. The 72-summary blackout was invisible to the outcome because the fresh window had the facts regardless of what the summary said.
The honest ledger
A memory tier is not free, and the credibility of this whole exercise is in showing the costs, not hiding them.
| Metric | Arm N (no memory) | Arm M (memory tier) |
|---|---|---|
| Forced compactions | 108 | 138 |
| Turns | 1,235 | 1,489 (+21%) |
| Cost | $21.26 | $28.94 (+36%) |
| Mid-run availability | 0/10 at 106 of 108 | 10/10 at all 138 |
| Audit completeness | 0.93 | 0.70 |
Arm M ran 21% more turns and 36% more cost than arm N. Some of that is delivery overhead; some is a tighter read cap the harness forced on it (the next section explains why); and some is a late-run confabulation spiral of its own. Its audit completeness — how many of the required file declarations it actually made, checked against a pattern-matched ground truth over the 64 files — was 0.70 against arm N's 0.93, though that gap is confounded by the tighter read cap. I include it because a post that only showed arm M's wins would be lying by omission. And one last disclosure for integrity: of arm M's 321 shell results, exactly one orientation grep incidentally echoed fact tokens back into context. It sits more than sixty compaction boundaries before the endpoint and cannot reach it — but it happened, so it is on the record.
The Harness Fights Memory-Carrying Configurations
This one I did not go looking for; the run found it for me. Claude Code has an autocompact thrash guard — a safety valve that aborts a call when the context refills to the limit within three turns of a compaction, three times in a row. Its abort message is worth quoting verbatim, because you may see it yourself:
Autocompact is thrashing: the context refilled to the limit within 3
turns of the previous compact, 3 times in a row... Try reading in smaller
chunks, or use /clear to start fresh.
The guard is not memory-specific. It killed arm N too — once, mid-run, in phase 6 after 49 boundaries. I relaunched it and it completed, still at its 8k-token file-read cap. So this is not a "memory breaks the harness" story. It is subtler and, I think, more interesting.
The difference between the arms is structural margin. A memory-equipped configuration carries fixed per-cycle context the naive one does not: MCP tool schemas reloaded per resumed call, guidance files, per-prompt injections. None of it is large — but it is always there, every cycle, and the thrash guard reads that fixed overhead as refill pressure. At the arms' shared 8k read cap, the guard killed arm M's first audit phase fourteen turns in and scrapped the launch. Running arm M at all required dropping its read cap to 6k. Even at 6k, the guard killed the final audit phase as the growing report tightened the squeeze.
A harness tuned for memoryless operation treats the context profile of a memory-carrying configuration as elevated thrashing risk. If you bolt persistent injection onto an agent, its fixed per-cycle payload — big MCP schemas, long guidance files, per-prompt notes — competes for the same headroom the thrash guard is watching. Keeping that payload lean is not a nicety; it is what lets the configuration run at all. About 240 tokens per delivery is what worked here.
Why This Matters in Ordinary Sessions
Forcing 100+ compactions is not how anyone works. So does any of this touch a normal session? I ran a separate, naturalistic coding task nine times to check, and the answer is: sometimes, and when it does, the waste concentrates at the boundary.
Across those nine pilot transcripts there were 61 intra-session re-reads — the agent reading content it had read before. Of those, 24 (39%) re-read content the session had already seen before a compaction boundary. That is roughly 78k result tokens re-paid, using the chars/4 proxy. The worst single run re-paid about 31.6k tokens — a sixth of a whole context window — re-retrieving content it had already paid for once. Pooled, re-exploration was 10.9% of exploration calls, with a wide range across runs (0 to 29.1%), and the three heaviest runs alone held 55 of the 61 re-reads.
That last clause is the honest nuance, and I want to hold onto it rather than sell past it. Every pilot run compacted at least once, yet four of them finished with zero re-reads. The boundary is where waste concentrates when it occurs — not a guarantee that it will.
A single compaction is near-lossless for task continuity — all twelve graded runs of the main evaluation auto-compacted at least once and finished green. The danger is two specific things: operational facts, which drop at boundary one, and repeated compaction, the one-conversation-per-project usage pattern that racks up boundaries. If you close the session often, you may never feel this. If you keep one conversation running for days, you will.
This is not only my finding. A public field report on the Claude Code tracker — "Persistent Memory Across Context Compactions (59 compactions, built our own)" — describes a team hitting the same wall at 59 boundaries and building their own persistence layer, the same reflex arm N showed when it started grepping its logs.
And there is a trap on the other side, for anyone thinking "fine, I will just give the agent a memory directory and let it read when it needs to." A control arm in the same evaluation pre-seeded the store with four task-relevant notes, connected the tools, and added usage guidance — everything short of forcing the call. The agent made zero memory calls in 114 turns. Storage the agent has to remember to consult, on its own initiative, contributes nothing. Which is the entire reason arm M's channel is a hook that fires whether the agent thinks to use it or not.
Where to Put Things, Given the Map
The map is only useful if it changes where you put state. Here is the placement guide that falls out of the measurements — matched to which channel actually survives.
- Constraints that must survive the whole session → project-root CLAUDE.md. It is re-read after every compaction, so it is the one channel with a delivery guarantee. Keep it small: it is unconditional overhead on every cycle, and the thrash guard is watching that overhead.
- File-anchored gotchas → path-scoped rules. Put them in
.claude/rules/withpaths:frontmatter so they re-arrive when their file is touched, instead of squatting in every window and eating headroom you do not have. - Do not rely on the agent re-reading its memory directory on its own. Zero reads in 114 turns, with the store seeded and the tools connected. Initiative is not a delivery mechanism.
- If you inject via hooks, ride the fact on the injected line itself. An index tier that injects "title now, expand on demand" delivers only the title at a session boundary. A fact has to travel on the line that actually gets injected, or it does not travel.
- Watch the thrash guard. Fixed per-cycle overhead — big MCP schemas, long guidance files, per-prompt injections — reads as refill pressure. Keep per-cycle payloads lean; about 240 tokens per delivery worked here.
- Know the restoration asymmetry. Files come back, conversation does not. Durable state belongs in a file or a store, not in a sentence you said to the agent once.
If you want to reproduce or poke at any of this, the protocol, graders, and per-run artifacts — including every invalidated launch and its diagnosis — are in the run archives, and the full write-up is here. The memory tier under test is vectr, a working-memory daemon for coding agents — it is the instrument that made the measurement, not the moral of the story.
The Fix Rides Moments the Harness Already Owns
Start back at the opening image: the agent that forgets a constraint you gave it an hour ago. Now you know what happened underneath. The constraint lived in the conversation. A compaction summarized the conversation into a continuation summary. The summarizer, treating your fact as an atom, dropped it — and every summary after that was built from the one that already lost it. Meanwhile the harness dutifully restored your files and re-read your CLAUDE.md, because those are the channels it guarantees. Your fact was simply in the one channel it does not.
The interesting thing is how small the fix could be. The harness already re-injects CLAUDE.md at compaction. It already has the moment. The measurements motivated an upstream feature request — Claude Code #78795, "Triggered injection for auto-memory topic files (rules-style frontmatter)" — that would let auto-memory topic files opt into paths: and events: (session-start, post-compaction) delivery, default-off, riding the exact moments the harness already uses for CLAUDE.md. No new machinery, just an opt-in that lets a fact travel on a channel that already survives the boundary. That is the whole ask.
Until something like it lands, the map is the tool. Put what must survive where the harness guarantees delivery, keep the rest in files, and stop trusting a summary's cheerful report that it kept everything. It will tell you it maintained all ten facts. Go count them yourself.
Links & Further Reading
The full write-up, the run archives, and the two Claude Code issues that anchor the practical side. Each external claim in this post was checked against the linked source.
- Delivery, Not Storage — the full write-up behind this post: the argument that the reliable memory channel is the one the agent never has to think about.
- Run archives — proactive-gate — protocol, graders, and per-run artifacts, including every invalidated launch and its diagnosis.
- vectr — the working-memory daemon used as the instrument for arm M.
- Anthropic. How Claude remembers your project.
- Persistent Memory Across Context Compactions (59 compactions, built our own) — a field report from a team that hit the same wall and built their own layer.
- Triggered injection for auto-memory topic files (rules-style frontmatter) — the upstream feature request the measurements motivated.