Skip to content

Episodic & Procedural Memory: Different Memory Types

Overview

Different types of information should be stored and retrieved differently.

  • Episodic: "What happened?" - Specific events
  • Procedural: "How do I do it?" - Skills and procedures

This file covers their differences, storage, and retrieval.


Episodic Memory: What Happened

Definition

Memory of specific events that occurred at specific times in specific contexts.

Examples: - "On Jan 15, I analyzed quarterly sales" - "The user mentioned they prefer concise responses" - "We discovered that customers want faster shipping"

Characteristics

Aspect Value
What Specific events/experiences
When Time-stamped
Where Context-rich
Why Learn from experience
Storage Vector DB (episodic store)
Retrieval Semantic similarity search
Retention Variable (important events kept longer)

Implementation

class EpisodicMemory:
    def __init__(self):
        self.vector_db = VectorStore()
        self.events = []

    def record_event(self, event: dict):
        """Record a specific event"""

        episode = {
            "timestamp": event['timestamp'],
            "description": event['description'],
            "context": event.get('context', {}),
            "outcome": event.get('outcome'),
            "emotional_valence": event.get('valence', 0),  # -1 to 1
            "importance": event.get('importance', 0.5)
        }

        # Embed and store
        embedding = embed(episode['description'])

        self.vector_db.add(
            text=episode['description'],
            embedding=embedding,
            metadata={
                'timestamp': episode['timestamp'],
                'context': episode['context'],
                'outcome': episode['outcome'],
                'importance': episode['importance']
            }
        )

        self.events.append(episode)

    def recall_similar(self, query: str, k: int = 5):
        """Recall similar past episodes"""

        query_embedding = embed(query)

        # Search by similarity
        similar = self.vector_db.search(query_embedding, k)

        # Return with timestamps (temporal context)
        return sorted(similar, key=lambda x: x['metadata']['timestamp'], reverse=True)

    def recall_recent(self, hours: int = 24):
        """Recall recent episodes"""

        cutoff = now() - timedelta(hours=hours)

        return [
            e for e in self.events
            if e['timestamp'] > cutoff
        ]

# Usage
episodic = EpisodicMemory()

# Record an event
episodic.record_event({
    'timestamp': now(),
    'description': 'Analyzed quarterly sales data from Q1',
    'context': {'user': 'alice', 'domain': 'sales'},
    'outcome': 'Found 15% growth trend',
    'importance': 0.9
})

# Later recall similar events
results = episodic.recall_similar("What analysis have we done on sales?")

Procedural Memory: How Do I Do It

Definition

Memory of skills, procedures, and strategies—how to do things.

Examples: - "To analyze data: 1) Load, 2) Explore, 3) Clean, 4) Analyze" - "When writing reports: Always include context and limitations" - "The most effective search strategy is to try multiple queries"

Characteristics

Aspect Value
What Skills, procedures, strategies
When Not time-specific
Where Domain-specific
Why Improve performance through practice
Storage Procedure DB, policy store
Retrieval Keyword/topic lookup
Retention Persistent (keep forever)
Updates Success rate tracking

Implementation

class ProceduralMemory:
    def __init__(self):
        self.procedures = {}
        self.success_rates = {}
        self.usage_counts = {}

    def learn_procedure(self, name: str, steps: List[str], domain: str = "general"):
        """Learn a new procedure"""

        self.procedures[name] = {
            "name": name,
            "steps": steps,
            "domain": domain,
            "learned_at": now(),
            "version": 1
        }

        self.success_rates[name] = 0.5  # Start neutral
        self.usage_counts[name] = 0

    def execute_procedure(self, name: str, **kwargs):
        """Execute a learned procedure"""

        if name not in self.procedures:
            raise ProcedureNotFound(f"Unknown procedure: {name}")

        procedure = self.procedures[name]

        try:
            # Execute steps
            for step in procedure['steps']:
                execute_step(step, **kwargs)

            # Record success
            self.record_success(name)
            return True

        except Exception as e:
            # Record failure
            self.record_failure(name)
            raise

    def record_success(self, procedure_name: str):
        """Update success rate when procedure succeeds"""

        current = self.success_rates.get(procedure_name, 0.5)

        # Update with exponential moving average
        self.success_rates[procedure_name] = 0.9 * current + 0.1 * 1.0
        self.usage_counts[procedure_name] += 1

    def record_failure(self, procedure_name: str):
        """Update success rate when procedure fails"""

        current = self.success_rates.get(procedure_name, 0.5)

        # Update with exponential moving average
        self.success_rates[procedure_name] = 0.9 * current + 0.1 * 0.0
        self.usage_counts[procedure_name] += 1

    def get_best_procedure(self, domain: str):
        """Get most successful procedure for domain"""

        domain_procedures = [
            (name, self.success_rates[name])
            for name, proc in self.procedures.items()
            if proc['domain'] == domain
        ]

        if not domain_procedures:
            return None

        # Return highest success rate
        best_name = max(domain_procedures, key=lambda x: x[1])[0]
        return self.procedures[best_name]

# Usage
procedural = ProceduralMemory()

# Learn a procedure
procedural.learn_procedure(
    name="analyze_sales_data",
    steps=[
        "Load data from database",
        "Explore dimensions and metrics",
        "Clean anomalies",
        "Calculate growth trends",
        "Identify patterns"
    ],
    domain="sales"
)

# Execute and track success
try:
    procedural.execute_procedure("analyze_sales_data", data=sales_df)
except Exception as e:
    print(f"Procedure failed: {e}")

Comparing Episodic vs Procedural

Use Cases

Scenario Which Memory
"What did we learn last time?" Episodic
"How did we solve this before?" Episodic (find similar case)
"How do I do task X?" Procedural
"What worked best for this domain?" Procedural (best procedure)
"When did we discover this?" Episodic (temporal)
"Why did that strategy work?" Both (combine)

Retrieval

Episodic:

# Find similar past events
recent_analyses = episodic.recall_similar("sales analysis", k=5)
# Returns: List of past analyses with times

Procedural:

# Find how to do something
procedure = procedural.get_best_procedure("sales_analysis")
# Returns: Steps with success rate 0.87

Learning

Episodic:

# Learn from experience
# Just record what happened
episodic.record_event(...)
# Over time, patterns emerge from many episodes

Procedural:

# Learn through practice
# Try procedure, track success
try:
    procedural.execute_procedure(...)
    success = True  # Update success rate
except:
    success = False  # Update failure rate
# Success rate improves with practice


Integration: Combined Memory System

class IntegratedMemorySystem:
    def __init__(self):
        self.episodic = EpisodicMemory()
        self.procedural = ProceduralMemory()

    def learn_from_experience(self, task_name: str, outcome: dict):
        """Learn both episodic and procedural"""

        # Store what happened
        self.episodic.record_event({
            'timestamp': outcome['timestamp'],
            'description': f"Performed {task_name}",
            'outcome': outcome['result'],
            'importance': outcome['success']
        })

        # If new successful approach, learn procedure
        if outcome['success'] and outcome['is_novel_approach']:
            self.procedural.learn_procedure(
                name=outcome['procedure_name'],
                steps=outcome['steps'],
                domain=task_name.split('_')[0]
            )

        # Track success for existing procedures
        if task_name in self.procedural.procedures:
            if outcome['success']:
                self.procedural.record_success(task_name)
            else:
                self.procedural.record_failure(task_name)

    def solve_new_problem(self, problem: dict):
        """Solve using both episodic and procedural"""

        # Step 1: Look for similar past problems (episodic)
        similar_past = self.episodic.recall_similar(problem['description'], k=3)

        # Step 2: Get best procedure for this domain (procedural)
        domain = problem['domain']
        best_procedure = self.procedural.get_best_procedure(domain)

        # Step 3: Combine insights
        if best_procedure:
            # Use proven procedure
            return self.procedural.execute_procedure(best_procedure['name'])
        elif similar_past:
            # Adapt solution from similar past case
            past_solution = similar_past[0]['outcome']
            return adapt_solution(past_solution, problem)
        else:
            # Fall back to general approach
            return solve_from_scratch(problem)

Competing for Storage: Which to Keep?

When storage is limited, which memories to keep?

Priority Matrix

                Low Success Rate    High Success Rate
Old:             Delete              Keep (maybe compress)
Recent:          Replace             Keep
High Importance: Keep                Keep
Low Importance:  Delete              Keep

Implementation

def prioritize_for_retention(memory, other_options):
    """Score memory for retention"""

    score = 0

    # Episodic factors
    if memory.type == 'episodic':
        score += memory['importance'] * 50

        # Older = lower priority
        age_days = (now() - memory['timestamp']).days
        score -= min(age_days / 30, 40)  # Cap penalty

        # Frequently accessed = higher priority
        score += memory['access_count'] * 5

    # Procedural factors
    if memory.type == 'procedural':
        score += memory['success_rate'] * 50  # Very important
        score += memory['usage_count'] * 2
        score -= (memory.get('failure_count', 0) * 5)

    return score

Best Practices

1. Episodic: Keep Context

# ✅ Good: Rich context
episode = {
    'what': 'Analyzed sales',
    'when': datetime.now(),
    'where': 'sales_dashboard',
    'who': 'alice',
    'why': 'Quarterly review',
    'outcome': 'Found 15% growth'
}

# ❌ Bad: No context
episode = {'text': 'Analyzed sales'}

2. Procedural: Track Metrics

# ✅ Good: Track success
procedure['success_rate'] = 0.87
procedure['times_used'] = 15
procedure['last_success'] = datetime.now()

# ❌ Bad: No metrics
procedure['steps'] = [...]  # Don't know if it works

3. Combine for Better Decisions

# ✅ Good: Use both
best_procedure = procedural.get_best(domain)
similar_past = episodic.recall_similar(query)
decision = combine_insights(best_procedure, similar_past)

# ❌ Bad: Use only one
decision = execute_procedure(best_procedure)  # Ignore past context

Real-World Example

# A sales agent learns and improves

# Day 1: First analysis (episodic + procedural learning)
agent.analyze_sales(data)  # Records event, learns procedure

# Day 2: Similar analysis (uses procedural, records episodic)
agent.analyze_sales(data)  # Uses learned procedure, tracks success

# Day 5: Difficult analysis (uses both)
agent.analyze_sales(difficult_data)
  # Recalls similar past difficult cases (episodic)
  # Uses best procedure (procedural)
  # Adapts based on context
  # Records success

# Month 1: Optimized (procedural success rate = 0.95)
agent.analyze_sales(data)  # Procedure highly successful

Key Takeaways

  1. Episodic: Store specific events with context
  2. Procedural: Store and improve skills/procedures
  3. Different storage: Episodic in vector DB, procedural in procedure DB
  4. Different retrieval: Episodic by similarity, procedural by domain
  5. Integration: Use both for better decisions
  6. Learning: Track success rates for procedures
  7. Retention: Keep both, prioritize high-value

Next Steps


Last Updated: August 9, 2026