Memory Compression and Summarization
Duration: 15 min
Memory Compression and Summarization
Duration: 15 min
Overview
As agents accumulate memories, storage grows unbounded and retrieval becomes noisy. Memory compression solves this by distilling verbose memories into concise representations that preserve essential information while reducing token count. This module covers progressive summarization, entity extraction, and hierarchical compression - techniques that keep agent memory lean and effective.
Key Concepts & Foundations
- What: Memory compression reduces the size of stored memories while preserving their informational value
- Why: Unbounded memory growth leads to slow retrieval, high costs, and context window overflow
- How: Summarize conversations, merge related memories, extract key entities, and discard redundancy
Compression Strategies
| Strategy | How It Works | Compression Ratio | |----------|-------------|-------------------| | Extractive summary | Keep key sentences verbatim | 3-5x | | Abstractive summary | Rewrite into shorter form | 5-10x | | Entity extraction | Store only entities + relationships | 10-20x | | Hierarchical | Multi-level (detail -> summary -> gist) | Variable |
When to Compress
- Conversation ends - Summarize the session into key takeaways
- Buffer overflow - Compress oldest messages when approaching limits
- Periodic maintenance - Scheduled compression of all memories older than N days
- Redundancy detected - Merge memories that say the same thing differently
Hands-On Implementation
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class CompressedMemory:
original_count: int
summary: str
entities: list[str]
key_facts: list[str]
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
compression_ratio: float = 1.0
class MemoryCompressor:
"""Compresses verbose memories into concise representations."""
def extractive_summarize(self, messages: list[str], max_sentences: int = 3) -> str:
"""Keep the most information-dense sentences."""
scored = []
all_words = set()
for msg in messages:
all_words.update(msg.lower().split())
for msg in messages:
sentences = [s.strip() for s in msg.split(".") if s.strip()]
for sentence in sentences:
words = set(sentence.lower().split())
# Score by unique word coverage
score = len(words & all_words) / max(len(all_words), 1)
scored.append((sentence, score))
scored.sort(key=lambda x: x[1], reverse=True)
top = [s for s, _ in scored[:max_sentences]]
return ". ".join(top) + "."
def extract_entities(self, text: str) -> list[str]:
"""Extract likely entities (capitalized words, technical terms)."""
words = text.split()
entities = set()
for word in words:
clean = word.strip(".,!?()[]")
if clean and (clean[0].isupper() or "_" in clean or clean.endswith("()")):
entities.add(clean)
return sorted(entities)
def extract_key_facts(self, messages: list[str]) -> list[str]:
"""Extract actionable facts from a conversation."""
facts = []
fact_indicators = ["prefer", "always", "never", "want", "need",
"name is", "work at", "use", "like", "hate"]
for msg in messages:
for indicator in fact_indicators:
if indicator in msg.lower():
facts.append(msg.strip())
break
return facts
def compress(self, messages: list[str]) -> CompressedMemory:
"""Full compression pipeline."""
original_chars = sum(len(m) for m in messages)
summary = self.extractive_summarize(messages)
all_text = " ".join(messages)
entities = self.extract_entities(all_text)
key_facts = self.extract_key_facts(messages)
compressed_chars = len(summary) + sum(len(f) for f in key_facts)
ratio = original_chars / max(compressed_chars, 1)
return CompressedMemory(
original_count=len(messages),
summary=summary,
entities=entities,
key_facts=key_facts,
compression_ratio=ratio
)
class HierarchicalMemory:
"""Three-level memory: detail -> summary -> gist."""
def __init__(self, detail_limit: int = 20):
self.details: list[str] = [] # Full messages
self.summaries: list[CompressedMemory] = [] # Compressed chunks
self.gist: str = "" # One-line overall summary
self.detail_limit = detail_limit
self.compressor = MemoryCompressor()
def add(self, message: str):
self.details.append(message)
if len(self.details) >= self.detail_limit:
self._compress_details()
def _compress_details(self):
"""Compress detail level into a summary."""
compressed = self.compressor.compress(self.details)
self.summaries.append(compressed)
self.details = [] # Clear details after compression
# Update gist
all_facts = []
for s in self.summaries:
all_facts.extend(s.key_facts)
self.gist = f"{len(self.summaries)} sessions, {len(all_facts)} key facts stored"
def get_context(self, max_chars: int = 2000) -> str:
"""Build context string within budget."""
parts = []
# Always include gist
if self.gist:
parts.append(f"Overall: {self.gist}")
# Add recent details
for msg in self.details[-5:]:
parts.append(msg)
# Add summary facts if space allows
remaining = max_chars - sum(len(p) for p in parts)
for summary in reversed(self.summaries):
for fact in summary.key_facts:
if remaining > len(fact):
parts.append(f"[past] {fact}")
remaining -= len(fact)
return "\n".join(parts)
Demo
compressor = MemoryCompressor()
conversation = [
"Hi, my name is Sarah and I work at DataFlow Inc.",
"I prefer Python for all my data science work.",
"I always want type hints in code examples.",
"Can you help me understand async/await?",
"I need to build a pipeline that processes 10K documents.",
"We use AWS at my company, specifically S3 and Lambda.",
"I never want to see JavaScript examples.",
"My deadline is next Friday for the demo.",
]compressed = compressor.compress(conversation)
print(f"Original: {len(conversation)} messages")
print(f"Summary: {compressed.summary}")
print(f"Entities: {compressed.entities}")
print(f"Key facts: {compressed.key_facts}")
print(f"Compression ratio: {compressed.compression_ratio:.1f}x")
Advanced Techniques
1. LLM-Powered Summarization - Use a cheap model (e.g., GPT-4o-mini) to generate abstractive summaries 2. Importance-Preserving Compression - Never compress high-importance memories; only compress low-priority ones 3. Semantic Deduplication - Before storing, check if a semantically similar memory already exists 4. Compression Scheduling - Run compression during idle periods, not during active conversations 5. Lossy vs. Lossless - Keep original text in cold storage; use compressed versions in hot retrieval
Quiz
Q1: Why is memory compression necessary for long-running agents?
- A) To make the agent faster at generating text
- B) To prevent unbounded memory growth that degrades retrieval and overflows context ✓
- C) To reduce the agent's intelligence
- D) To comply with data regulations
Q2: What is hierarchical memory compression?
- A) Storing memories in alphabetical order
- B) Multiple compression levels (detail -> summary -> gist) with increasing compression ✓
- C) Compressing only the first message
- D) Using multiple vector databases
Q3: When should high-importance memories be compressed?
- A) Always - treat all memories equally
- B) Never or minimally - they should be preserved at full detail ✓
- C) Only on weekends
- D) Only when storage is completely full