DEV Community

Cover image for Every AI Agent I Build Has Memory. Here Is the Exact Architecture I Use.
Jahanzaib
Jahanzaib

Posted on • Originally published at jahanzaib.ai

Every AI Agent I Build Has Memory. Here Is the Exact Architecture I Use.

AI agent memory is the layer that lets an agent carry information between turns and between runs. A production memory architecture has four parts: short term context inside the current conversation, long term facts about the user or account, episodic records of what happened on previous runs, and semantic knowledge retrieved from your own documents. Without it, every run starts from zero.

A client called me six months into our engagement. Their AI customer support agent had been live since October. It handled thousands of conversations per day. And it was driving people insane.

Why? Because every single conversation started from zero. A customer who had called three times about the same billing issue would explain the whole thing again. The agent had no idea who they were. No memory of the previous conversations. No context about their account history. Just a blank slate, every time.

That call cost me two weeks of emergency work and nearly cost us the client. The agent was technically correct, technically fast, and technically useless for anyone who had ever spoken to it before.

Since then, every AI agent I build has persistent memory built in from day one. Not as an afterthought. Not bolted on after launch. Designed in from the first line of code.

In this guide I will show you exactly how I do it. The four memory types every production agent needs. The actual stack I use. Real Python code. And the five failure modes that will bite you if you skip steps.

Key Takeaways

  • LLMs are stateless by default. Memory is an architecture decision, not a feature toggle.
  • There are four distinct memory types: working (in-context), episodic, semantic, and procedural. Most agents only implement one of them.
  • The right memory stack depends on your use case: LangGraph checkpointing for short-term, Mem0 for long-term semantic memory, Redis for sub-millisecond retrieval, Zep for time-aware memory graphs.
  • Memory retrieval drift is the most dangerous failure mode and almost nobody talks about it.
  • Mem0 shows 26% accuracy improvement over plain vector approaches in benchmark testing.
  • You can have a basic working memory system running in under 50 lines of code. Getting it production-ready takes considerably more.

Why do most AI agents have no memory at all?

Every large language model call is stateless. When you send a message to Claude, GPT, or any other model, it processes your input and returns a response. Then it forgets everything. There is no persistent state. No connection between one call and the next.

This is by design. It makes models easier to scale, easier to deploy, and easier to reason about. But it creates a fundamental problem for AI agents that need to serve real users across real sessions.

The naive fix is to stuff the entire conversation history into the context window on every call. And this works, right up until it doesn't. Context windows have limits. Long conversations hit those limits. And even before you hit the limit, you are paying for every token in that history window on every single call. A 100-turn conversation with a 50-token average per turn costs you 5,000 tokens just to maintain state before the model processes your new message.

More importantly, context stuffing conflates very different types of memory. What the user said three messages ago is different from what they told you six months ago. And both of those are different from the business rules the agent needs to apply consistently. Treating all of it as "conversation history" is a recipe for confused, expensive, and unreliable agents.

Mem0 homepage showing the universal memory layer for AI agents with personalization and context managementMem0 solves the stateless LLM problem with a dedicated memory layer that persists between sessions and across agents

What are the four types of AI agent memory?

I think about agent memory the same way cognitive scientists think about human memory. There are four distinct types, and conflating them causes most of the memory bugs I have seen in production.

Working Memory (In-Context)

Working memory is everything in the current context window. The messages exchanged so far this session. The current task state. The intermediate results of tool calls. It is temporary by definition. It disappears when the session ends.

For most agent tasks, working memory is all you need. A single-turn question-answering agent, a one-shot code generator, a simple form-filling workflow. These do not need to remember anything beyond the current conversation.

The implementation is trivial: pass the message history as part of your prompt. LangGraph handles this automatically with its state graph. The challenge is managing what you keep and what you drop as conversations get long.

Episodic Memory

Episodic memory stores specific past events. What happened in previous sessions. Which tasks the agent completed for this user. What errors occurred. What feedback the user gave.

This is the memory type my billing support agent was missing. "User called three times about the same invoice" is episodic memory. It is specific, time-stamped, and tied to a particular user and event sequence.

Episodic memory is usually stored as compressed summaries or structured records in a database. You retrieve it based on relevance to the current context, not just recency.

Semantic Memory

Semantic memory stores facts and general knowledge. User preferences. Product information. Business rules. Things that are true independent of any specific event.

"This user prefers email over SMS for notifications" is semantic memory. "Our refund policy is 30 days" is semantic memory. These facts do not change based on recent events. They persist and apply broadly.

Vector databases are the standard storage layer for semantic memory because you retrieve it through similarity search. You embed the user's query, find the most relevant stored facts, and inject them into the context.

Procedural Memory

Procedural memory governs how the agent behaves. Which tools to call in which order. How to escalate certain types of requests. What tone to use with different customer segments.

Most people do not think of this as memory at all. It lives in the system prompt, in the tool definitions, in the agent's workflow configuration. But it is memory in the cognitive science sense: knowledge about how to do things, not just what things are.

In practice, procedural memory is the hardest to update dynamically. Changing an agent's behavior mid-deployment requires careful prompt engineering and testing. I treat it as the most stable layer of the memory stack.

What does a production AI agent memory architecture look like?

Here is the actual architecture I deploy across my client work. Different use cases call for different combinations, but this is the full stack.

Layer 1: LangGraph Checkpointing for Working Memory

For any agent that needs to maintain state across multiple steps within a single session, LangGraph's built-in checkpointing is my first choice. It handles the state management automatically. You define your state schema, and LangGraph takes care of persisting it between node executions.

The basic setup looks like this:

from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langchain_core.messages import HumanMessage, AIMessage
from typing import TypedDict, List, Annotated
from langchain_core.messages import BaseMessage
import operator

class AgentState(TypedDict):
    messages: Annotated[List[BaseMessage], operator.add]
    user_id: str
    session_context: dict

checkpointer = MemorySaver()

def call_model(state: AgentState):
    # Your LLM call here
    messages = state["messages"]
    response = llm.invoke(messages)
    return {"messages": [response]}

workflow = StateGraph(AgentState)
workflow.add_node("agent", call_model)
workflow.set_entry_point("agent")
workflow.add_edge("agent", END)

graph = workflow.compile(checkpointer=checkpointer)

# Each thread_id maintains its own state
config = {"configurable": {"thread_id": "user-123-session-456"}}
result = graph.invoke(
    {"messages": [HumanMessage(content="Hello")], "user_id": "user-123"},
    config=config
)
Enter fullscreen mode Exit fullscreen mode

For production deployments I replace MemorySaver with PostgresSaver or RedisSaver from the langgraph-checkpoint library. In-memory checkpointing is fine for development and single-instance deployments. It breaks immediately when you have multiple Lambda instances running.

LangGraph persistence documentation showing how checkpointers maintain state across agent steps and sessionsLangGraph's persistence layer stores the full state graph between steps, enabling multi-turn agent workflows that survive interruptions

Layer 2: Mem0 for Long-Term Semantic and Episodic Memory

For persistent memory that survives across sessions, I use Mem0. It is purpose-built for AI agent memory and handles the extraction, storage, and retrieval of semantic facts and episodic summaries automatically.

What I like about Mem0 is that you do not need to manually decide what to remember. You pass the conversation to Mem0, and it extracts the relevant facts using an LLM. Those facts get stored as embeddings in a vector database. On subsequent calls, you retrieve relevant memories by querying with the current context.

from mem0 import Memory
from langchain_anthropic import ChatAnthropic

# Initialize with your preferred LLM and vector store
memory = Memory()
llm = ChatAnthropic(model="claude-haiku-4-5")

def run_agent_with_memory(user_message: str, user_id: str) -> str:
    # Retrieve relevant memories for this user
    relevant_memories = memory.search(user_message, user_id=user_id, limit=5)

    # Format memories as context
    memory_context = "\n".join([
        f"- {m['memory']}" 
        for m in relevant_memories.get("results", [])
    ])

    # Build prompt with memory context
    system_prompt = f"""You are a helpful assistant. 

    What you know about this user:
    {memory_context if memory_context else 'No previous interactions.'}

    Respond naturally, incorporating what you know."""

    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_message}
    ]

    response = llm.invoke(messages)

    # Store this interaction as new memory
    memory.add(
        [{"role": "user", "content": user_message},
         {"role": "assistant", "content": response.content}],
        user_id=user_id
    )

    return response.content

# Usage
response = run_agent_with_memory(
    "I prefer concise answers, no more than three sentences", 
    user_id="user-123"
)
# Next call will remember this preference automatically
Enter fullscreen mode Exit fullscreen mode

Mem0 benchmarks show a 26% accuracy improvement over plain vector retrieval approaches. In my own testing across client deployments, the improvement is most pronounced for preference-based personalization where the agent needs to adapt to individual users rather than just recall facts.

Mem0 GitHub repository showing 47,000 stars and the universal memory layer for AI agents READMEMem0 has grown rapidly to become the most widely adopted open-source memory layer for production AI agents

Layer 3: Redis for High-Speed Session Context

Between LangGraph checkpointing (which handles agent state) and Mem0 (which handles long-term facts), I need a fast cache for session-level data that is not part of the agent's state graph. Things like: the current user's account details fetched from the CRM, the products they are browsing, the documents open in their workspace.

Redis is the answer here. Sub-millisecond reads. Automatic expiry with TTL. And with Redis Stack you get vector search capabilities if you need them for fast in-session retrieval.

import redis
import json
from datetime import timedelta

client = redis.Redis(host='localhost', port=6379, decode_responses=True)

def get_session_context(session_id: str) -> dict:
    data = client.get(f"session:{session_id}")
    return json.loads(data) if data else {}

def update_session_context(session_id: str, updates: dict) -> None:
    existing = get_session_context(session_id)
    existing.update(updates)
    client.setex(
        f"session:{session_id}",
        timedelta(hours=4),  # 4-hour session TTL
        json.dumps(existing)
    )

def store_agent_interaction(user_id: str, interaction: dict) -> None:
    key = f"history:{user_id}"
    client.lpush(key, json.dumps(interaction))
    client.ltrim(key, 0, 19)  # Keep last 20 interactions
    client.expire(key, timedelta(days=30))  # 30-day TTL
Enter fullscreen mode Exit fullscreen mode

Redis vector database page showing sub-millisecond query performance for AI agent memory workloadsRedis delivers sub-millisecond reads that keep agent response times low even when retrieving session context mid-conversation

Layer 4: Zep for Time-Aware Memory Graphs

For use cases where the temporal dimension of memory matters, I use Zep. The key feature is that Zep treats memory as a knowledge graph with time stamps and relationship tracking, not just a flat vector store. You can query not just "what does this user prefer" but also "how have this user's preferences changed over the past 90 days."

This is particularly valuable for long-running relationships. A sales assistant that has been working with a prospect for six months. A personal finance advisor tracking spending patterns. A coaching assistant monitoring behavior change over time.

Zep AI homepage showing temporal memory graph for AI agents with time-aware knowledge retrievalZep goes beyond flat vector retrieval by tracking how facts and preferences evolve over time, which matters for long-term agent relationships

Vector, key value or graph: which memory store should you use?

The choice of storage layer depends on how you need to retrieve your memories. Here is the decision logic I use.

Storage Type Best For Latency Cost Examples
In-context (array) Short conversations, single session Zero (already in prompt) Pay per token LangGraph MemorySaver
Key-value store User preferences, session state, structured facts Under 1ms Very low Redis, DynamoDB
Vector database Semantic search, unstructured facts, similarity retrieval 10-50ms Low to medium Pinecone, Qdrant, pgvector
Graph database Relationship tracking, temporal changes, complex entity connections 50-200ms Medium to high Neo4j, Zep, FalkorDB
Relational database Structured records, audit logs, analytics 5-20ms Low PostgreSQL, Supabase

Most production agents I build use two or three layers in combination. The in-context layer always. A key-value store for fast session state. And either a vector database or Mem0 (which abstracts the vector store behind a clean API) for long-term semantic memory.

Graph databases become relevant once you have complex entity relationships and need to track how those relationships change over time. I have used Zep with Neo4j for two enterprise deployments where the relationship complexity justified the added operational overhead.

What are the most common agent memory failure modes?

Every guide tells you how to add memory. Very few tell you how memory breaks in production. Here is what I have learned from shipping 126 systems.

1. Stale Memory Poisoning

Memory that was accurate when stored becomes wrong over time. A user's job title changes. A product gets discontinued. A policy gets updated. Your agent is confidently citing information from six months ago as current fact.

The fix: add a stored_at timestamp to every memory record. Build retrieval logic that weights recency. For facts with known expiry (product pricing, policy details), set explicit TTL fields and check them before injecting memories into context. I treat any fact older than 90 days as potentially stale and flag it in the system prompt rather than asserting it as current.

2. Context Stuffing

Naive memory implementations retrieve too much. The top 20 results. Everything from the past year. All the user's preferences. This explodes your context window, slows response time, and degrades answer quality because the model is trying to reconcile dozens of weakly relevant facts.

The fix: be aggressive with retrieval limits. I typically retrieve 3 to 5 memories maximum. Use a reranker if you have more candidates. And separate high-confidence facts (stored explicitly) from soft preferences (retrieved by similarity), using different injection strategies for each.

3. Multi-Agent Memory Conflicts

When multiple agents share the same memory store, you get write conflicts. Agent A stores "user prefers formal communication." Agent B, working on a different task, stores "user requested casual tone today." Now you have two contradictory facts with no resolution mechanism.

The fix: namespace your memories by agent type and use case. Give each agent type its own memory scope. Build a reconciliation layer (or use Mem0's built-in conflict handling) to detect and resolve contradictions rather than letting them accumulate.

4. Memory Retrieval Drift

This is the failure mode I see most often and talk about least. As your memory store grows, retrieval quality degrades because the embedding model that indexed your memories and the embedding model you use for retrieval queries may drift apart over time. Especially if you switch embedding providers or update model versions.

The fix: version your embeddings. Store which embedding model and version generated each memory. When you upgrade models, re-embed your stored memories. This is operationally annoying but critical. I have seen agents where retrieval precision dropped from 87% to 52% after an embedding model update that nobody flagged.

5. Privacy Violations via Memory Leakage

Memory systems that do not enforce proper user isolation will eventually leak one user's data to another. This is especially dangerous in multi-tenant deployments where a single agent serves thousands of users. A bug in the user ID lookup, a missing filter clause in the vector query, and suddenly User B's private financial details are being surfaced in User A's conversation.

The fix: always pass user_id as a mandatory filter parameter in every memory retrieval call. Never retrieve from the global namespace. Audit your vector database queries to confirm the user ID filter is applied at the storage layer, not just in your application code. Mem0 enforces this pattern by design with its user_id parameter.

Which AI agent memory tool should you use?

Tool Best For Handles Extraction Time-Aware Self-Hosted Production Ready
LangGraph Checkpointer Within-session state, multi-step workflows No No Yes Yes
Mem0 Long-term semantic and episodic memory Yes (LLM-based) Partial Yes Yes
Zep Time-aware memory graphs, long relationships Yes Yes Yes Yes
Redis Fast session state, structured facts, low latency No With TTL Yes Yes
pgvector Semantic memory when you already have PostgreSQL No No Yes Yes

My default recommendation for most production agent projects: start with LangGraph checkpointing plus Mem0. That covers working memory and long-term semantic memory without operational complexity. Add Redis for session-level caching when response latency starts to matter. Introduce Zep only when your use case genuinely requires temporal reasoning about relationship changes.

If you want to see how this memory architecture connects to broader agent design patterns, read my guide on building AI agents that work in production and my LangGraph tutorial for the full graph-based agent workflow.

How do you give an AI agent memory of past interactions?

Here is the initialization pattern I use at the start of every agent session. It pulls from all three active memory layers and assembles a context object the agent uses throughout the conversation.

import asyncio
from mem0 import Memory
import redis
import json

memory_client = Memory()
redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)

async def initialize_agent_context(user_id: str, session_id: str, current_query: str) -> dict:
    """Pull relevant context from all memory layers before the first LLM call."""

    # Layer 1: Fast session context from Redis (under 1ms)
    session_data = redis_client.get(f"session:{session_id}")
    session_context = json.loads(session_data) if session_data else {}

    # Layer 2: Long-term memories from Mem0 (10-30ms)
    mem0_results = memory_client.search(
        query=current_query,
        user_id=user_id,
        limit=5  # Never more than 5 at initialization
    )
    long_term_memories = [
        m["memory"] 
        for m in mem0_results.get("results", [])
        if m.get("score", 0) > 0.7  # Only high-confidence retrievals
    ]

    return {
        "user_id": user_id,
        "session_id": session_id,
        "session_context": session_context,
        "long_term_memories": long_term_memories,
        "memory_initialized": True
    }

def format_memory_for_prompt(context: dict) -> str:
    """Format retrieved memories for injection into system prompt."""
    parts = []

    if context["session_context"]:
        parts.append("Current session context:")
        for key, value in context["session_context"].items():
            parts.append(f"  - {key}: {value}")

    if context["long_term_memories"]:
        parts.append("What I know about this user from past interactions:")
        for memory in context["long_term_memories"]:
            parts.append(f"  - {memory}")

    return "\n".join(parts) if parts else "No prior context for this user."
Enter fullscreen mode Exit fullscreen mode

This runs before the first LLM call in every session. The total overhead is typically 15 to 40 milliseconds. That is a very acceptable price for an agent that actually remembers who it is talking to.

You can assess whether your current automation approach is ready for this kind of agent sophistication using the AI Readiness Assessment, which will tell you exactly where memory architecture fits in your current stack. And if you want to see the decision framework I use to choose between simple automation and full agent deployments, read when to use AI agents vs automation.

Which enterprise use cases actually need agent memory?

Memory is not free, so the honest answer is that plenty of agents do not need it. The test I apply is simple: does the right answer on this run depend on something that happened on a previous run? If no, skip it and save the latency and the storage.

These are the shapes where memory earns its cost, drawn from the systems I have shipped:

  • Support and service desks. A caller who has already explained their problem twice should not explain it a third time. Episodic memory of prior tickets is the difference between an agent that feels useful and one that feels like a phone tree.

  • Healthcare and clinical intake. Intake agents need long term facts about the patient and episodic memory of prior visits. This is also the setting where memory drift is most dangerous, so retention rules and redaction matter more than raw recall.

  • Legal and professional services. Matter context spans months. Semantic memory over the firm's own documents plus long term facts about the matter is what stops the agent contradicting an earlier position.

  • Sales and revenue teams. The value is entirely in what happened before: last touch, objections raised, pricing already quoted. An agent without episodic memory here is worse than a CRM note.

  • Internal knowledge and operations. Semantic memory over policy docs is usually enough. Most internal agents need retrieval, not personalization, and teams routinely overbuild this one.

The pattern across all of them: personalization and continuity need memory, lookup does not. If your agent answers the same question the same way for every user, you want retrieval, not a memory layer.

How much does AI agent memory cost in production?

Memory is not free. Here is the rough cost breakdown for a production deployment serving 10,000 active users with 5 interactions per user per week:

  • Mem0 cloud: Roughly $50 to $150 per month depending on the embedding model and storage tier. Self-hosted via the open-source version eliminates this but adds infrastructure cost.
  • Redis: $15 to $40 per month on any major cloud provider for a small managed instance. Scales linearly.
  • Embedding costs: If you are embedding memories before storage (required for vector search), this adds LLM API cost. At $0.0001 per 1K tokens with text-embedding-3-small, 50,000 memories stored per month costs roughly $0.50. Negligible.
  • Retrieval LLM cost: Mem0 uses an LLM to extract memories from conversations. Each extraction call adds roughly 500 to 1,000 tokens. At Claude Haiku pricing, that is about $0.001 per conversation. At 50,000 conversations per month, that is $50.

Total memory infrastructure cost for 10,000 active users: approximately $100 to $250 per month. For a B2B SaaS product or enterprise deployment, that is trivial compared to the value delivered by personalized, context-aware agents.

What is the difference between AI agent memory and RAG?

RAG (retrieval-augmented generation) retrieves information from a static knowledge base to answer questions. Agent memory is about remembering information about users, past interactions, and evolving context across sessions. RAG is about what the agent knows. Memory is about what the agent remembers about you specifically. Production agents often need both: RAG for product and policy knowledge, memory for user-specific personalization and session continuity.

Can I add memory to an existing AI agent without rebuilding it?

Yes, but with caveats. You can add Mem0 as a wrapper around almost any existing agent with minimal code changes: retrieve memories before the LLM call, inject them into the system prompt, and store new memories after the response. LangGraph checkpointing, however, requires building your agent as a state graph from the start. Retrofitting it to an existing non-LangGraph agent is technically possible but usually means a partial rewrite.

How do you prevent an AI agent from remembering sensitive information it should not store?

Define explicit memory policies before you start. Sensitive categories like payment details, health information, and authentication credentials should never be stored in the memory layer. Implement a filtering step between conversation capture and memory storage that strips PII and sensitive data before it reaches Mem0 or your vector store. Mem0 supports custom memory extraction prompts that can explicitly exclude certain categories of information. Test this filtering regularly with adversarial inputs.

How many memories should an agent retrieve per call?

Three to five is almost always the right answer. Retrieving more than five memories increases context cost, slows response time, and adds noise that degrades answer quality. Use a similarity threshold (I use 0.70 with Mem0) so you only retrieve memories that are genuinely relevant to the current query. A well-designed retrieval prompt and good memory extraction quality matter far more than retrieval quantity.

What happens to agent memory when a user asks to be forgotten?

This is a GDPR and privacy compliance question as much as a technical one. Mem0 provides a memory.delete_all(user_id=user_id) method that removes all memories for a given user. Redis keys can be deleted by pattern. LangGraph checkpoints can be purged by thread ID. You need to identify every storage layer your agent uses and implement a complete deletion flow across all of them. Test this flow regularly. Missing even one storage layer in your deletion flow creates compliance risk.

Does agent memory work for multi-agent systems where different agents serve the same user?

Yes, but you need a shared memory layer with proper namespacing. Each agent type writes to its own memory namespace but can read from others with appropriate permissions. Mem0 handles this with its agent_id and user_id parameters. The key design decision is whether agents share a single memory pool or maintain separate pools with a synchronization layer. For most use cases, a shared pool with namespace scoping is the right answer.

Is Mem0 better than building your own memory system with LangChain and a vector database?

For most teams, yes. Mem0 handles memory extraction (using an LLM to decide what is worth remembering), deduplication (merging new facts with existing ones rather than storing duplicates), and structured retrieval in ways that are genuinely hard to build correctly from scratch. The main reason to build your own is if you need deep control over the extraction logic or have specific compliance requirements around where data is stored. For teams without a dedicated ML engineer, Mem0 saves weeks of engineering work.

How do you handle memory in voice AI agents where conversations move faster?

Voice agents have stricter latency requirements. Memory retrieval needs to complete in under 100 milliseconds to avoid perceptible delays. This means Redis for session state (under 1ms) and pre-cached memories loaded at session start rather than retrieved mid-conversation. I load the top 3 most relevant memories at session initialization and do not retrieve more during the call unless the conversation takes an unexpected turn. For voice agents, I lean more heavily on Redis and structured key-value storage than on vector similarity search.

Citation Capsule: Mem0 internal benchmarks show 26% accuracy improvement over plain vector retrieval. Research on Agentic Context Engineering (arXiv, 2025) reports +10.6% on agent benchmarks with multi-layer memory architectures. IBM's AI agent memory overview provides foundational definitions used in this article. Sources: Mem0 GitHub 2026, IBM Think: What Is AI Agent Memory 2026, Redis AI Agent Memory Architecture 2026.

Top comments (0)