<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shuo Liu</title>
    <description>The latest articles on DEV Community by Shuo Liu (@linfordr).</description>
    <link>https://dev.to/linfordr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4073123%2Feb2d5167-7b6e-4a6b-baea-b9fb9650e4ed.jpg</url>
      <title>DEV Community: Shuo Liu</title>
      <link>https://dev.to/linfordr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/linfordr"/>
    <language>en</language>
    <item>
      <title>Compaction turns your agent's decisions into folklore — here's the fix</title>
      <dc:creator>Shuo Liu</dc:creator>
      <pubDate>Wed, 12 Aug 2026 18:41:37 +0000</pubDate>
      <link>https://dev.to/linfordr/compaction-turns-your-agents-decisions-into-folklore-heres-the-fix-4pn1</link>
      <guid>https://dev.to/linfordr/compaction-turns-your-agents-decisions-into-folklore-heres-the-fix-4pn1</guid>
      <description>&lt;h1&gt;
  
  
  Compaction turns your agent's decisions into folklore — here's the fix
&lt;/h1&gt;

&lt;p&gt;Yesterday I posted about context compaction silently destroying agent memory. The response that stuck with me came from a reader working on ranking and Maps workflows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A decision like 'location visibility dropped' is weak unless the summary keeps the grid, query set, timestamp, competitor set, and changed GBP fields. Without that provenance, compaction turns an observation into folklore."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Folklore. That's the word. A decision without its source isn't weaker information — it becomes unquestionable. There's nothing left to challenge it with.&lt;/p&gt;

&lt;p&gt;That comment landed as a feature within hours (v0.3.1: decisions carry source + evidence, and the audit tool grades them). But the reader's follow-up made me look at the other half of the pipeline, and I found something embarrassing: the audit could &lt;em&gt;detect&lt;/em&gt; lost provenance, but the recovery side never &lt;em&gt;restored&lt;/em&gt; it. The verdict would say "decision lost provenance", then the recovery block would re-inject the decision without the source anyway. Half a fix.&lt;/p&gt;

&lt;p&gt;So this week:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Wrote a recover-side drill that checks every expected item — rules, todos, decisions &lt;em&gt;with source and evidence&lt;/em&gt;, progress — verbatim inside the recovery block, per category, with an exit-code gate.&lt;/li&gt;
&lt;li&gt;Ran it against a manifest with provenance: decisions recovered at 50% before the fix, 100% after.&lt;/li&gt;
&lt;li&gt;Fixed the recovery block to re-inject source/evidence alongside each decision (empty fields are skipped, so existing manifests are unaffected). 37 tests pass.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The pipeline is now symmetric:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;preserve side: compaction_drill measures what your compressor destroys&lt;/li&gt;
&lt;li&gt;audit side: cam judge classifies every tracked item verbatim / paraphrased / lost&lt;/li&gt;
&lt;li&gt;recover side: recover_drill verifies the recovery block re-anchors everything — provenance included&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why provenance is the part that matters most: a compressed memory that keeps "we decided X" but drops "because file Y behaved this way on date Z" will steer future code while being impossible to challenge. The next agent run inherits the conclusion without the evidence. That's how bugs become traditions.&lt;/p&gt;

&lt;p&gt;The fix is one line in the recovery block per decision — but the &lt;em&gt;test&lt;/em&gt; is the real deliverable. &lt;code&gt;recover_drill.py&lt;/code&gt; runs against your own manifest and the summary your compressor actually produced, and fails the exit code with a per-category report if anything is missing. No more trusting the recovery path.&lt;/p&gt;

&lt;p&gt;If you run long agent sessions and have ever wondered whether the compressed memory is still the same memory — run the drill. It takes a minute, and the first run usually surprises you.&lt;/p&gt;

&lt;p&gt;repo: &lt;a href="https://github.com/44334433/memory-anchor" rel="noopener noreferrer"&gt;https://github.com/44334433/memory-anchor&lt;/a&gt; (v0.3.2)&lt;br&gt;
first post: &lt;a href="https://dev.to/linfordr/context-compaction-is-silently-destroying-your-llm-agents-memory-2pg2"&gt;https://dev.to/linfordr/context-compaction-is-silently-destroying-your-llm-agents-memory-2pg2&lt;/a&gt;&lt;/p&gt;

</description>
      <category>llm</category>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Context compaction is silently destroying your LLM agent's memory</title>
      <dc:creator>Shuo Liu</dc:creator>
      <pubDate>Tue, 11 Aug 2026 17:05:24 +0000</pubDate>
      <link>https://dev.to/linfordr/context-compaction-is-silently-destroying-your-llm-agents-memory-2pg2</link>
      <guid>https://dev.to/linfordr/context-compaction-is-silently-destroying-your-llm-agents-memory-2pg2</guid>
      <description>&lt;h1&gt;
  
  
  Context compaction is silently destroying your LLM agent's memory
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Tl;dr:&lt;/strong&gt; Long-session LLM agents lose governing rules, todos, and decisions when context compaction runs — and the loss is usually silent. I built a zero-dependency Python library (&lt;a href="https://github.com/44334433/memory-anchor" rel="noopener noreferrer"&gt;memory-anchor&lt;/a&gt;) that snapshots that state verbatim before compaction and re-injects it after, plus a CLI to audit how much a compaction actually destroyed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem nobody measures
&lt;/h2&gt;

&lt;p&gt;Run any agent long enough and it hits the context window. The fix everyone reaches for is compaction: old turns get summarized into a paragraph and the summary replaces them. Cheap summarizers — especially the fast flash models everyone uses to keep costs down — flatten detail. In my own agent's logs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a governing rule got paraphrased (the agent started behaving differently and nobody noticed why)&lt;/li&gt;
&lt;li&gt;a pending todo vanished ("what was I doing?")&lt;/li&gt;
&lt;li&gt;a decision's rationale was rewritten (settled questions got re-litigated)&lt;/li&gt;
&lt;li&gt;a verification path disappeared (half-verified work got reported as done)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the scary one. After a 35% compaction pass, my agent forgot it had delegated a subtask and never collected the result. Not a hallucination — a &lt;em&gt;silent state loss&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why existing memory systems don't cover this
&lt;/h2&gt;

&lt;p&gt;The current memory stack (mem0, Letta, Hindsight, and friends) remembers &lt;em&gt;facts&lt;/em&gt; — entities, preferences, retrieval chunks. None of them guarantee that the &lt;em&gt;rules and work state governing this session&lt;/em&gt; survive a compaction byte-for-byte. Research-backed systems like MemGPT treat memory as a tiered store, and Claude's own compaction is a black box — you don't get to see what it threw away.&lt;/p&gt;

&lt;p&gt;There are components everywhere, but no off-the-shelf, framework-agnostic answer. So I built one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;preserve(ctx) ─► manifest (4 lists, verbatim) ─► [summarizer runs] ─► recover(ctx) ─► recovery block injected at head of messages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four small pieces, pure stdlib, zero dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;StateManifest&lt;/strong&gt; — rules / todos / decisions / progress, serialized to JSON, with incremental &lt;code&gt;merge()&lt;/code&gt;: done todos never resurrect, superseded decisions never reappear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MemoryStore&lt;/strong&gt; — atomic (tmp+rename) local JSON persistence, per-session indexed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RecoveryInjector&lt;/strong&gt; — rebuilds the recovery block; L1 (immutable rules) is &lt;em&gt;never&lt;/em&gt; trimmed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CompactableMemory&lt;/strong&gt; — a two-line facade:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;memory_anchor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CompactableMemory&lt;/span&gt;

&lt;span class="n"&gt;mem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CompactableMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.memory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preserve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                              &lt;span class="c1"&gt;# before compaction
&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recover&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# after compaction
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The part I'm most proud of: auditing compaction damage
&lt;/h2&gt;

&lt;p&gt;v0.3 added &lt;code&gt;cam judge&lt;/code&gt; — point it at your pre-compaction manifest and the summary that replaced it, and it classifies every item as &lt;strong&gt;verbatim&lt;/strong&gt; (survived), &lt;strong&gt;paraphrased&lt;/strong&gt; (semantics preserved), or &lt;strong&gt;lost&lt;/strong&gt;. It's a CI gate, not a vibes check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cam judge &lt;span class="nt"&gt;--before&lt;/span&gt; manifest.json &lt;span class="nt"&gt;--after&lt;/span&gt; summary.txt &lt;span class="nt"&gt;--min-verbatim&lt;/span&gt; 90
&lt;span class="c"&gt;# exit code 1 if fewer than 90% of items survived byte-for-byte&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The numbers from my own system are not flattering to the status quo. Running the same 8KB briefing through two compressors:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;compressor&lt;/th&gt;
&lt;th&gt;verbatim survival&lt;/th&gt;
&lt;th&gt;outcome&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;extractive (built-in)&lt;/td&gt;
&lt;td&gt;~60%&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 governing rules silently lost&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;caveman (aggressive)&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;all items survived byte-for-byte&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The judge didn't just measure the damage — it &lt;em&gt;exposed&lt;/em&gt; it. That extractive pass was silently eating rules every single run, and nothing in the pipeline was looking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/44334433/memory-anchor
&lt;span class="nb"&gt;cd &lt;/span&gt;memory-anchor &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pytest   &lt;span class="c"&gt;# 31 tests&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or script it into your cron/CI without Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cam before my-session &lt;span class="nt"&gt;--rule&lt;/span&gt; &lt;span class="s2"&gt;"R1|never paraphrase governing rules|100"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--todo&lt;/span&gt; &lt;span class="s2"&gt;"ship v0.2|pending|run the drill"&lt;/span&gt;
&lt;span class="c"&gt;# ...compaction happens...&lt;/span&gt;
cam after my-session &lt;span class="nt"&gt;--messages&lt;/span&gt; messages.json &lt;span class="nt"&gt;--budget&lt;/span&gt; 2000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This is not a semantic memory system. It preserves &lt;em&gt;work state you explicitly declare&lt;/em&gt; — it won't infer what matters on your behalf (yet).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;judge&lt;/code&gt; uses diff-based matching, not semantics. A truly reworded rule may count as "paraphrased" rather than "lost" — the threshold (45%) is a documented heuristic.&lt;/li&gt;
&lt;li&gt;It's framework-agnostic by design: bring your own summarizer, hook the two calls wherever your pipeline compacts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;If your agent has ever "forgotten" something after compaction, you already know this pain.&lt;/strong&gt; Anchor the state that must survive — and start measuring what your summarizer is destroying. Star/watch the repo if you want to see the framework adapters (LangChain, Claude Code, OpenHands) land — I'm holding off until real integrations ask for them.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
