DEV Community

Abdeljabbar Elassali
Abdeljabbar Elassali

Posted on

How to Add Persistent Memory to a LangChain Agent (5 Options, Ranked by Pain)

How to Add Persistent Memory to a LangChain Agent (5 Options, Ranked by Pain)

If your LangChain agent still runs fine on code from a 2024 tutorial, that luck has a narrow shape: it ends the first time the process restarts. Most "add memory to LangChain" tutorials teach ConversationBufferMemory. In LangChain 1.0 that class is gone, moved into the langchain-classic package with the rest of the legacy chains. And even back when it worked, it never solved the real problem. It lived in your process's RAM. Restart the process, redeploy the container, wake the scheduled job tomorrow morning, and the agent wakes up knowing nothing.

So there are two problems stacked here. The first is finding the current API in a framework that renames things every year. The second, the one that actually costs operators sleep, is deciding where the memory's bytes live so they survive restarts, redeploys, and run boundaries. Here are the five options that exist in late 2026, ordered from least to most infrastructure.

1. RunnableWithMessageHistory plus an in-memory dict (dev only)

This is the modern replacement for the old ConversationBufferMemory pattern on chains. You wrap your agent so every call loads the session's message history and appends to it:

from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

store: dict[str, InMemoryChatMessageHistory] = {}

def get_history(session_id: str):
    return store.setdefault(session_id, InMemoryChatMessageHistory())

chain_with_history = RunnableWithMessageHistory(
    agent,
    get_session_history=get_history,
    input_messages_key="input",
    history_messages_key="chat_history",
)
Enter fullscreen mode Exit fullscreen mode

It works perfectly until the process dies. Then it is gone, all of it, with no recovery. Use it for local development and tests. Anything that runs on a schedule needs something below.

2. The same wrapper with a persistent chat-history backend

Keep RunnableWithMessageHistory, but make get_session_history return a Redis or Postgres backed history instead of a dict. The langchain_community package ships RedisChatMessageHistory and PostgresChatMessageHistory for exactly this. Same session id next week, same conversation back.

What you get: every message, in order, reloadable per session. What you do not get: anything that is not a message. No mid-run resume, no facts keyed by user, no cross-session knowledge. And you now operate a database for your agent's brain: backups, TTLs, a connection string in your scheduler's environment. This is the right call when the whole requirement is "the agent remembers what was said in this session." It is the wrong call when you need the agent to remember what it learned.

3. A LangGraph checkpointer (the agent-native option)

If your agent is built with create_agent, the v1 replacement for initialize_agent and create_react_agent, memory means a checkpointer:

from langgraph.checkpoint.sqlite import SqliteSaver

memory = SqliteSaver.from_conn_string("checkpoints.db")
agent = create_agent(model="gpt-5", tools=[...], checkpointer=memory)

agent.invoke(
    {"messages": [{"role": "user", "content": "triage today's leads"}]},
    config={"configurable": {"thread_id": "lead-triage-daily"}},
)
Enter fullscreen mode Exit fullscreen mode

The thread_id is the conversation's primary key. Invoke with the same thread id tomorrow and the agent resumes the same thread with full state: not just the messages, but the tool calls, the intermediate steps, exactly where it stopped. MemorySaver is the in-memory checkpointer for tests. SqliteSaver or a Postgres checkpointer is what runs in production.

The trap that catches scheduled agents: the checkpointer file has to live on persistent storage. A checkpoints.db inside a container that gets rebuilt on every deploy is amnesia with extra steps, and it fails silently. The agent works, the tests pass, and every Monday it wakes up blank because the disk it wrote to on Friday no longer exists. Mount a volume or point the checkpointer at Postgres. Verify by actually restarting the process and asking what it remembers, not by reading the code and assuming.

4. A Store for facts that outlive any thread

Checkpointers remember threads. Sometimes you need the opposite: a fact that follows a user across every thread. LangChain's answer is the Store, a namespaced key-value layer the agent reads and writes at runtime:

from langgraph.store.memory import InMemoryStore

store = InMemoryStore()
store.put(("preferences",), "user_42", {"tone": "terse", "timezone": "America/Denver"})
Enter fullscreen mode Exit fullscreen mode

Namespaces like ("preferences",) plus a user id give you cross-session memory: decisions, preferences, corrections like "we stopped doing that." The mental model that saves you: State is what happened in this thread, Store is what is true across threads. Mix them up and you either bloat every prompt with thread trivia or lose durable facts between sessions. And the same persistence caveat applies in miniature: InMemoryStore dies with the process, so production pairs it with a persistent store backend.

5. Skip the database entirely: hosted memory over MCP

All four options above share one property: you own the storage. You pick the database, keep it alive, back it up, and the memory is only reachable from processes that can reach that database. If your operation runs a LangChain agent on a cron box, an n8n workflow, and Claude Code on a laptop, that is three memory silos that never talk.

The alternative is a hosted memory layer the agent reaches over MCP instead of a database driver. Vilix AI works this way. It is cloud-hosted, so there is no database to run, and every connected tool reads and writes the same memory over MCP: Claude, Codex, Cursor, OpenClaw, Hermes, anything MCP-compatible. The agent saves full conversation history, not just extracted facts, and recall is semantic plus keyword search, so the agent pulls the relevant slice of context instead of dumping the whole archive. Export everything in a portable format anytime. Delete individual memories or wipe the account instantly. There is a free plan forever, and a 7-day Pro trial that does not ask for a credit card.

The honest tradeoff: it is cloud-only, and it is someone else's service. If your setup requires agent memory to never leave your own infrastructure, options 2 through 4 are your list. If you would rather not become a part-time database administrator for your agent's brain, this is the one option with zero infrastructure to babysit.

The scheduled-run checklist

Whichever option you pick, agents on a schedule add two requirements the tutorials skip.

First, thread and session discipline. If every cron run generates a fresh session id, you built amnesia on purpose. Decide the policy up front: one thread per recurring job for continuity, or one thread per run with durable facts promoted to the Store for hygiene. Either works. Switching between them by accident does not.

Second, bytes on persistent storage. In-memory anything plus a redeploy equals a blank agent. The check is embarrassingly simple and almost nobody runs it: restart the process, ask what it remembers. Do that before the first production run, not after the first incident.

The old tutorials were not wrong about the goal, only about the API and the failure mode. Memory was never the hard part of LangChain. Keeping the bytes alive between runs was, and still is.

Top comments (0)