Multi-Agent Systems9 min read

Agent Memory: Patterns That Actually Work in Production

Every agent framework promises memory. Few deliver it well. Here's what works.

Carolina Fogliato

March 23, 2026

Every agent framework promises memory.

"Persistent memory across sessions." "Long-term learning." "Agents that remember."

Then you try to use it in production.

Your agent forgets critical context mid-conversation. Memory bloats until responses slow to a crawl. The agent "remembers" things that never happened. Retrieval pulls irrelevant memories while missing obvious ones.

I've built memory systems for dispute resolution agents handling 15,000+ cases, marketing AI that maintains brand voice across thousands of interactions, and financial advisors tracking complex client portfolios.

Here's what I've learned: agent memory is a systems problem, not a feature toggle. The frameworks give you primitives. Making them work requires architecture.

Why Memory Is Hard

First, let's understand why this is harder than it looks.

The Context Window Trap

LLMs have context windows. Big ones now—128K, 200K tokens. So why not just stuff everything in?

Because attention degrades. At 128K tokens, the model literally can't attend to everything equally. Information in the middle gets lost. Relevant details drown in noise.

More context ≠ better memory. It often means worse.

The Retrieval Problem

"Just use RAG for memory" sounds reasonable until you try it.

Memory retrieval is different from document retrieval:

  • • Queries are implicit (what does the agent need to know right now?)
  • • Relevance is contextual (same memory is relevant in one situation, not another)
  • • Recency matters (recent interactions often trump older ones)
  • • Relationships matter (memory A is relevant because of its connection to memory B)

Standard vector similarity doesn't capture this.

The Staleness Problem

Memories become stale. User preferences change. Facts update. Relationships evolve.

An agent that "remembers" your favorite restaurant from two years ago isn't helpful—it's annoying. Memory systems need decay, updates, and contradiction resolution.

The Consistency Problem

In multi-agent systems, multiple agents might access and update shared memory simultaneously. Without coordination, you get inconsistencies. Agent A thinks the user prefers X. Agent B thinks they prefer Y. Chaos ensues.

The Memory Architecture That Works

After building several production systems, I've converged on a layered architecture:

4-Layer Memory Architecture

🧠

Layer 1: Working Memory

Current conversation context. Lives in the prompt.

  • • Recent messages (last 5-10 turns)
  • • Active task state
  • • Scratchpad for reasoning
Storage: Context window|Lifespan: Current session

Layer 2: Short-Term Memory

Session and recent interaction history.

  • • Conversation summaries
  • • Recent decisions and their rationale
  • • Temporary preferences and corrections
Storage: Redis / in-memory cache|Lifespan: Hours to days
📚

Layer 3: Long-Term Memory

Persistent knowledge about users, entities, patterns.

  • • User profiles and preferences
  • • Entity relationships
  • • Learned patterns and insights
Storage: Vector DB + Knowledge Graph|Lifespan: Months to years
📖

Layer 4: Episodic Memory

Specific past interactions that might be relevant.

  • • Notable conversations
  • • Key decisions and outcomes
  • • Important events
Storage: Vector DB + temporal indexing|Lifespan: Permanent (with decay)

Each layer serves a different purpose. Each has different storage, retrieval, and lifecycle characteristics.

Layer 1: Working Memory

Working memory is what's in the prompt right now. It's the agent's immediate awareness.

What Goes Here

  • • Last N conversation turns (typically 5-10)
  • • Current task description and state
  • • Relevant context retrieved from other layers
  • • Scratchpad for chain-of-thought reasoning

The Pattern: Sliding Window + Summarization

class WorkingMemory:
    def __init__(self, max_turns: int = 10, max_tokens: int = 4000):
        self.max_turns = max_turns
        self.max_tokens = max_tokens
        self.turns = []
        self.summary = ""

    def add_turn(self, role: str, content: str):
        self.turns.append({"role": role, "content": content})

        # If exceeding limits, summarize older turns
        if len(self.turns) > self.max_turns:
            self._compress()

    def _compress(self):
        # Summarize oldest turns
        old_turns = self.turns[:5]
        new_summary = llm.summarize(old_turns)

        # Update summary and remove old turns
        self.summary = f"{self.summary}\n{new_summary}"
        self.turns = self.turns[5:]

Key insight: Don't just truncate old messages—summarize them. You lose detail but preserve the important bits.

Anti-Pattern: Stuffing Everything

I've seen systems that dump entire conversation histories into the context. At 50 turns, you're burning 20K+ tokens and drowning the model in noise.

More is not better. Be selective.

Layer 2: Short-Term Memory

Short-term memory bridges sessions. It's what the agent remembers from earlier today, yesterday, this week.

What Goes Here

  • • Conversation summaries from recent sessions
  • • Temporary user corrections ("Actually, I prefer X")
  • • Recent decisions and their context
  • • Active tasks and their state

The Pattern: Time-Decayed Cache

class ShortTermMemory:
    def __init__(self, redis_client, decay_hours: int = 72):
        self.redis = redis_client
        self.decay_hours = decay_hours

    def store(self, user_id: str, key: str, value: dict):
        memory_key = f"stm:{user_id}:{key}"
        value["timestamp"] = time.time()
        value["access_count"] = 0
        self.redis.setex(
            memory_key,
            timedelta(hours=self.decay_hours),
            json.dumps(value)
        )

    def retrieve(self, user_id: str, key: str) -> dict | None:
        data = self.redis.get(f"stm:{user_id}:{key}")

        if data:
            value = json.loads(data)
            # Boost TTL on access (frequently accessed = more important)
            value["access_count"] += 1
            self.redis.setex(...)  # Extend TTL
            return value
        return None

Key insight: Use TTLs and access patterns to naturally decay irrelevant memories. Memories that aren't accessed fade away.

Promotion Pattern

Important short-term memories should promote to long-term storage:

  • • Frequently accessed (access_count > 5) → Promote
  • • Type is "preference" → Promote
  • • Explicitly marked important → Promote

Layer 3: Long-Term Memory

Long-term memory is persistent knowledge. User profiles, learned preferences, entity relationships.

What Goes Here

  • • User profile (name, preferences, history summary)
  • • Entity relationships (user → company, user → projects)
  • • Learned patterns (user tends to prefer X over Y)
  • • Important facts (user is allergic to shellfish)

The Pattern: Structured + Unstructured Hybrid

Long-term memory needs both structured storage (for reliable retrieval) and unstructured storage (for semantic search).

Graph Database

For explicit relationships and structured queries

Neo4j, Amazon Neptune

Vector Store

For semantic similarity search

Pinecone, Weaviate, Qdrant

Key insight: Use the graph for explicit relationships and structured queries. Use vectors for semantic similarity. You need both.

Update Pattern: Contradiction Resolution

What happens when new information contradicts old memories?

  • • Check for contradictions against existing facts
  • • If newer timestamp wins → Deprecate old fact as "superseded"
  • • Track provenance → You might need to debug why the agent "changed its mind"

Don't just overwrite. Track provenance.

Layer 4: Episodic Memory

Episodic memory stores specific past interactions—memorable conversations, key decisions, important events.

What Goes Here

  • • Notable conversations (not all, just significant ones)
  • • Key decisions and their outcomes
  • • Important events (user complained, user praised, user changed preferences)
  • • Exceptions and edge cases

The Pattern: Selective Storage with Importance Scoring

You can't store every interaction. You need to decide what's worth remembering.

Importance Signals

  • User expressed strong emotion → High importance
  • Decision was made → High importance
  • User corrected the agent → High importance
  • Conversation was long/engaged → Medium importance
  • User explicitly said "remember this" → Highest importance

Key insight: Not all interactions deserve to be memories. Score importance and be selective.

Common Pitfalls (and How to Avoid Them)

Pitfall 1: Remembering Everything

Problem: Memory bloats. Retrieval slows. Irrelevant memories crowd out relevant ones.

Fix: Be aggressive about what you store. Score importance. Let things decay.

Pitfall 2: No Memory Retrieval Strategy

Problem: You store memories but retrieve poorly. Agents miss relevant context or retrieve noise.

Fix: Combine semantic search with structured queries. Use metadata filtering. Weight by recency.

Pitfall 3: Treating Memory as Append-Only

Problem: Memories become stale or wrong. Contradictions accumulate.

Fix: Build update and contradiction resolution logic. Track provenance. Allow deprecation.

Pitfall 4: One Memory Store for Everything

Problem: Different memory types have different characteristics. One-size-fits-all doesn't work.

Fix: Use the layered architecture. Different storage for different purposes.

Pitfall 5: No Memory in Prompts

Problem: You retrieve memories but don't include them effectively in prompts.

Fix: Structure memory context clearly. Separate what the agent knows from what's happening now.

Performance Considerations

Memory adds latency. Here's how to manage it:

Parallel Retrieval

Run all retrievals in parallel with asyncio.gather()

Caching

Cache user profile (changes infrequently) with TTL

Lazy Loading

Only retrieve episodic memory if the query requires historical context

Agent memory isn't a checkbox feature. It's a system design challenge.

The frameworks give you building blocks—vector stores, conversation buffers, entity extractors. But making memory work—reliably, performantly, correctly—requires architecture.

The layered approach addresses the core challenges:

  • • Finite context windows (working memory with summarization)
  • • Session continuity (short-term memory with decay)
  • • Persistent knowledge (long-term memory with structure)
  • • Relevant recall (episodic memory with importance scoring)

Build memory as a system. Test it like you test retrieval.

Your agents will thank you. Or at least, they'll remember to.

Building agents that need to remember?

Let's talk

Memory architecture is one of the trickiest parts of production agent systems.

Newsletter

Agent architecture patterns and production insights—every Tuesday.

Join 2,500+ engineers and leaders getting practical AI implementation insights.

Subscribe to AI That Ships

Your Privacy Matters

We use cookies to enhance your experience, analyze traffic, and serve targeted ads.

By clicking "Accept All", you consent to all cookies. Cookie Policy