Skip to content

Memory Compression: Making Memories Efficient

Overview

Memory grows over time. Memory compression is the process of reducing memory size while preserving essential information.

Key insight: Not all memories are equally important. Compress/discard low-value ones.


The Compression Challenge

Problem

Day 1:   10 interactions  → 50K tokens
Day 7:   70 interactions  → 350K tokens
Month 1: 300 interactions → 1.5M tokens
Year 1:  3650 interactions → 18M tokens

Issues: - Storage costs grow - Retrieval becomes slower - No clear retention policy - Old, stale information clutters memory


Compression Strategies

Strategy 1: Summarization

Idea: Combine similar memories into summaries

class SummarizationCompression:
    def compress(self, memories, compression_ratio=0.1):
        """Compress memories via summarization"""

        # Group similar memories
        clusters = self.cluster_memories(memories)

        # Summarize each cluster
        summaries = []
        for cluster in clusters:
            summary = self.llm.summarize(
                texts=[m['text'] for m in cluster],
                max_tokens=len(cluster[0]['text']) // 10
            )

            summaries.append({
                "summary": summary,
                "count": len(cluster),
                "avg_importance": np.mean([m['importance'] for m in cluster])
            })

        return summaries

# Usage
original_memories = retrieve_all_memories()  # 1000 items
compressed = summarizer.compress(original_memories, ratio=0.1)
# Result: 10 summary items instead of 1000

Strategy 2: Deletion by Age & Quality

Idea: Delete old, low-quality memories

class AgeQualityPruning:
    def prune(self, memories, age_threshold_days=30, quality_threshold=0.3):
        """Delete old or low-quality memories"""

        keep = []
        delete = []

        now = datetime.now()

        for memory in memories:
            age_days = (now - memory['timestamp']).days
            quality = memory.get('quality', 0.5)

            # Keep if recent OR high quality
            if age_days < age_threshold_days or quality > quality_threshold:
                keep.append(memory)
            else:
                delete.append(memory)

        # Delete low-value memories
        for memory in delete:
            self.memory_db.delete(memory['id'])

        return keep

# Usage
pruned = pruner.prune(all_memories)
# Automatically removes stale, low-quality items

Strategy 3: Hierarchical Compression

Idea: Different compression levels for different ages

Recent (< 1 day):  No compression (keep everything)
│
Recent (1-7 days): Keep important items, discard noise
│
Historic (1-4 weeks): Summarize clusters
│
Old (> 4 weeks): Only keep high-quality or frequently accessed

Implementation:

class HierarchicalCompression:
    def compress(self, memories):
        """Apply different compression by age"""

        now = datetime.now()
        compressed = []

        for memory in sorted(memories, key=lambda x: x['timestamp']):
            age_days = (now - memory['timestamp']).days

            if age_days < 1:
                # Keep everything recent
                compressed.append(memory)

            elif age_days < 7:
                # Keep important items
                if memory['importance'] > 0.7:
                    compressed.append(memory)

            elif age_days < 30:
                # Summarize or delete
                if memory['access_count'] > 5:
                    compressed.append(memory)  # Keep frequently used
                else:
                    summary = self.create_summary(memory)
                    compressed.append(summary)

            else:
                # Old memories: keep only high-value
                if memory['importance'] > 0.9 or memory['access_count'] > 10:
                    compressed.append(memory)

        return compressed


Compression Techniques

Technique 1: Embedding-Based Deduplication

class DeduplicationCompressor:
    def deduplicate(self, memories, similarity_threshold=0.95):
        """Remove near-duplicate memories"""

        # Get embeddings
        embeddings = [embed(m['text']) for m in memories]

        # Find duplicates
        duplicates = []
        for i, emb1 in enumerate(embeddings):
            for j, emb2 in enumerate(embeddings[i+1:], start=i+1):
                similarity = cosine_similarity(emb1, emb2)

                if similarity > similarity_threshold:
                    # Keep the more important one
                    if memories[i]['importance'] > memories[j]['importance']:
                        duplicates.append(j)
                    else:
                        duplicates.append(i)

        # Remove duplicates
        unique_memories = [
            m for i, m in enumerate(memories)
            if i not in duplicates
        ]

        return unique_memories

Technique 2: Conceptual Compression

class ConceptualCompression:
    def compress(self, memories):
        """Abstract specific memories into concepts"""

        # Extract concepts from memories
        concepts = self.extract_concepts(memories)

        # Map memories to concepts
        compressed = []
        for concept in concepts:
            compressed.append({
                "type": "concept",
                "concept": concept['name'],
                "instances_count": concept['count'],
                "evidence": concept['examples'][:3]  # Keep a few examples
            })

        return compressed

# Usage
memories = [
    "Analyzed Q1 sales",
    "Analyzed Q2 sales",
    "Analyzed Q3 sales",
]

compressed = conceptual_compressor.compress(memories)
# Result: {"concept": "quarterly sales analysis", "instances": 3}

Implementing Compression Pipeline

Complete Example

class MemoryCompressionPipeline:
    def __init__(self):
        self.logger = setup_logging()

    def compress_memories(self, memories):
        """Apply full compression pipeline"""

        self.logger.info(f"Starting compression on {len(memories)} items")

        # Step 1: Remove exact duplicates
        step1 = self.remove_exact_duplicates(memories)
        self.logger.info(f"After dedupe: {len(step1)} items")

        # Step 2: Remove near-duplicates by embedding
        step2 = self.remove_near_duplicates(step1, threshold=0.95)
        self.logger.info(f"After similarity: {len(step2)} items")

        # Step 3: Apply hierarchical compression by age
        step3 = self.hierarchical_compress(step2)
        self.logger.info(f"After hierarchy: {len(step3)} items")

        # Step 4: Summarize remaining
        step4 = self.summarize_clusters(step3)
        self.logger.info(f"After summarization: {len(step4)} items")

        # Report compression
        ratio = len(step4) / len(memories)
        self.logger.info(f"Compression ratio: {ratio:.1%}")

        return step4

    def remove_exact_duplicates(self, memories):
        """Remove exact string matches"""
        seen = set()
        unique = []

        for memory in memories:
            text_hash = hash(memory['text'])
            if text_hash not in seen:
                seen.add(text_hash)
                unique.append(memory)

        return unique

    def remove_near_duplicates(self, memories, threshold):
        """Remove similar items (by embedding distance)"""
        # ... (see Technique 1 above)
        pass

    def hierarchical_compress(self, memories):
        """Apply age-based compression"""
        # ... (see Hierarchical Compression above)
        pass

    def summarize_clusters(self, memories):
        """Group and summarize similar memories"""
        # ... (see Summarization above)
        pass

# Usage
pipeline = MemoryCompressionPipeline()
compressed = pipeline.compress_memories(all_memories)

Scheduled Compression

import schedule
import time

def compress_and_archive():
    """Compress memories every night"""

    # Get old memories
    old_memories = memory_db.get_older_than(days=30)

    # Compress
    compressed = compressor.compress(old_memories)

    # Archive originals
    archive_db.store(old_memories)

    # Replace with compressed
    for memory in old_memories:
        memory_db.delete(memory['id'])
    for compressed_item in compressed:
        memory_db.add(compressed_item)

    print(f"Compression complete: {len(old_memories)}{len(compressed)}")

# Schedule
schedule.every().day.at("02:00").do(compress_and_archive)

while True:
    schedule.run_pending()
    time.sleep(60)

Compression Quality Metrics

Metric 1: Compression Ratio

Original size: 1000 memories
Compressed size: 100 memories
Ratio: 10% (90% reduction)

Metric 2: Information Retention

Ask: "What reports have we analyzed?"

Before compression: 8/10 results relevant
After compression: 7/10 results relevant
Retention: 87.5%

Metric 3: Query Performance

Search time before: 500ms
Search time after: 50ms
Speedup: 10x

When to Compress

Automatic Triggers

def should_compress(memory_system):
    # Compress if:
    # 1. Memory size exceeds threshold
    if memory_system.size_gb() > 5:
        return True

    # 2. Search latency degrades
    if memory_system.avg_search_time_ms() > 1000:
        return True

    # 3. Scheduled time (e.g., nightly)
    if datetime.now().hour == 2:
        return True

    return False

Best Practices

1. Preserve Important Memories

# ✅ Good: Mark important before compression
memory['importance'] = 0.9  # Mark as important
# Won't be deleted during compression

# ❌ Bad: Blindly compress everything
compress_everything()  # May lose critical info

2. Keep Access Patterns

# ✅ Good: Track how often accessed
memory['access_count'] = 15  # Frequently used
# Frequently accessed items kept even if old

# ❌ Bad: Ignore usage
delete_if_old()  # May delete useful memories

3. Version Control

# ✅ Good: Keep history
archive_db.store(original_memories)
compressed = compress(original_memories)
# Can rollback if needed

# ❌ Bad: Delete originals
compress_and_discard()  # No recovery possible

4. Test After Compression

# ✅ Good: Verify quality
original_results = retrieve(query)
memory = switch_to_compressed()
compressed_results = retrieve(query)

if similarity(original_results, compressed_results) < 0.9:
    raise CompressionError("Too much quality loss")

# ❌ Bad: Deploy without testing
compress_and_deploy()  # May have broken retrieval

Advanced: Learning-Based Compression

class LearningCompressor:
    def __init__(self):
        self.model = train_importance_model()

    def predict_importance(self, memory):
        """Use ML to predict if memory is important"""

        features = self.extract_features(memory)
        # Features: age, access_count, quality, etc.

        importance_score = self.model.predict(features)
        return importance_score

    def compress(self, memories):
        """Compress based on learned importance"""

        scores = [self.predict_importance(m) for m in memories]

        # Keep high-scoring memories
        important = [m for m, s in zip(memories, scores) if s > 0.7]

        # Compress/delete low-scoring
        less_important = [m for m, s in zip(memories, scores) if s <= 0.7]
        compressed = self.summarize(less_important)

        return important + compressed

Key Takeaways

  1. Compression necessary - Memory grows indefinitely
  2. Multiple strategies - Summarization, deletion, deduplication
  3. Preserve important - Mark and protect high-value memories
  4. Monitor quality - Don't over-compress
  5. Automate - Schedule compression pipeline
  6. Test carefully - Verify retrieval quality after compression

Next Steps


Last Updated: August 9, 2026