How to Build an Agent with Memory Using LangGraph

By Carlos Montiel | Enterprise AI Specialist
Leer en español →
Published: 2026-07-28 | By: Carlos Montiel | Reading time: ~4 minutes

An agent without memory repeats the same questions and loses context between sessions. LangGraph solves this with two distinct mechanisms: checkpointers for short-term memory and stores for long-term memory.

Two kinds of memory, two mechanisms

It's a common mistake to treat "memory" as a single concept. LangGraph explicitly distinguishes between short-term memory — the history of the current conversation, tied to a `thread_id` — and long-term memory — facts or user preferences that must persist across different conversations, even after the `thread_id` changes. The former is solved with checkpointers; the latter with LangGraph's `Store`.

Short-term memory with a checkpointer

For development and testing, `MemorySaver` keeps state in the process's memory. For production, you need real persistence:

from langgraph.checkpoint.postgres import PostgresSaver from psycopg_pool import ConnectionPool pool = ConnectionPool(conninfo="postgresql://user:pass@localhost:5432/agents_db") checkpointer = PostgresSaver(pool) checkpointer.setup() # creates the necessary tables the first time app = builder.compile(checkpointer=checkpointer) config = {"configurable": {"thread_id": "user-4471-session-1"}} app.invoke({"messages": [HumanMessage(content="I need a quote for 3 licenses")]}, config=config)

With `PostgresSaver`, if the process restarts or the user closes and reopens the conversation with the same `thread_id`, the graph recovers the exact state where it left off, including the full message history and any other key in the defined state.

Long-term memory with Store

When you need to remember information about the user beyond a single conversation — their name, preferences, a summarized purchase history — you use a `Store`, indexed not by `thread_id` but by a `namespace` that belongs to the user:

from langgraph.store.postgres import PostgresStore store = PostgresStore(pool) store.setup() def save_preference_node(state, config, *, store): user_id = config["configurable"]["user_id"] store.put( namespace=("preferences", user_id), key="preferred_language", value={"language": "en-US"}, ) return {} def read_preference_node(state, config, *, store): user_id = config["configurable"]["user_id"] item = store.get(namespace=("preferences", user_id), key="preferred_language") return {"loaded_preferences": item.value if item else {}}

When compiling the graph with `builder.compile(checkpointer=checkpointer, store=store)`, any node can declare the `store` parameter in its signature and LangGraph injects it automatically at runtime.

Summarizing long history to avoid saturating context

A conversation with hundreds of messages eventually exceeds the context window or triggers unnecessary costs. The standard pattern is a node that, when the history exceeds a threshold, summarizes it and replaces the old messages with the summary:

from langchain_core.messages import RemoveMessage, SystemMessage def summarize_if_needed(state): if len(state["messages"]) <= 20: return {} summary = llm.invoke( [SystemMessage(content="Summarize this conversation in one paragraph:")] + state["messages"][:-6] ) messages_to_remove = [RemoveMessage(id=m.id) for m in state["messages"][:-6]] return {"messages": [SystemMessage(content=f"Previous summary: {summary.content}")] + messages_to_remove}

`RemoveMessage` is a special type that, combined with LangGraph's message reducer, removes specific messages from the state by id, letting you prune history without losing the most recent turns — usually the most relevant ones for the immediate response.

Architecture recommendation

In real deployments we use `PostgresSaver` for the transactional state of the active conversation (with a reasonable TTL, since not every thread needs to live forever) and a separate `Store` — sometimes backed by the same database, sometimes by a vector store when long-term memory needs semantic search over accumulated preferences — for what must survive across sessions. Mixing both concepts into a single ad-hoc table is the most common cause of "phantom memory" bugs we've seen in audits: information from one conversation leaking into another because the same `thread_id` got reused for different users.

Carlos Montiel
Enterprise AI Solutions Architect
Specialist in LLMs, Agents, and Orchestration
guatemalia.com/en/#contact · info@guatemalia.com

Need to implement AI at your company?

Carlos Montiel is an enterprise AI solutions architect. He implements LLMs, Agents, RAG, and orchestrators for companies across Guatemala and Latin America. Reach out for a consultation.

Contact Carlos Montiel

info@guatemalia.com