LangGraph Memory
Give LangGraph agents durable, cross-thread memory: an official BaseStore implementation, pre-model context injection for both agent generations, a retry-safe persistence node, and a graph-callable search tool — in Python and Node.js.
What the integration provides
| Piece | What it does |
|---|---|
MemorySyncStore | A LangGraph BaseStore: durable cross-thread key-value memory with semantic search, compiled straight into any graph via store=. |
| Memory middleware | Context injection for create_agent (langchain 1.x) — the modern middleware pattern, with optional auto-persistence. |
| Pre-model hook | The same injection for create_react_agent(pre_model_hook=...), for the installed base. |
| Persist node | A graph node that writes the latest exchange verbatim — safe under LangGraph’s node retries. |
| Search tool | A graph-callable semantic memory search tool that never raises. |
| Concern | Use |
|---|---|
| Thread state (checkpoints, resume, time travel) | A checkpointer — langgraph-checkpoint-postgres, -sqlite, -redis. |
| Long-term memory across threads, sessions and surfaces | MemorySync, through this integration. |
Install
pip install "langchain-memorysync[langgraph]"
Set MEMORYSYNC_API_KEY in the environment, or pass the key explicitly. Requires langgraph 1.x (Python) or @langchain/langgraph 1.x (Node.js); the plain LangChain surface of this package keeps working on langchain-core 0.3.x without langgraph installed.
Durable cross-thread state with MemorySyncStore
Namespaces are string tuples, exactly as LangGraph defines them. Each namespace maps to its own server-side scope, so ("memories", "user-a") and ("memories", "user-b") are isolated by the same enforcement that separates customers — never by a client-side filter.
from langchain_memorysync.langgraph import MemorySyncStorestore = MemorySyncStore() # MEMORYSYNC_API_KEY from the envstore.put(("memories", "user-1"), "diet", {"text": "vegetarian"})item = store.get(("memories", "user-1"), "diet")print(item.value) # {'text': 'vegetarian'}hits = store.search(("memories", "user-1"), query="what do they eat?")print(hits[0].value, hits[0].score)# Filters use the same JSONB-style operators as LangGraph's own stores.store.search(("memories", "user-1"), filter={"confidence": {"$gte": 0.8}})
Compile it into an agent
from langchain.agents import create_agentfrom langgraph.config import get_storefrom langchain_core.tools import toolfrom langchain_memorysync.langgraph import MemorySyncStore@tooldef save_preference(key: str, value: str) -> str:"""Save a user preference to long-term memory."""get_store().put(("memories", "user-1"), key, {"text": value})return f"Saved {key}."@tooldef recall_preferences(query: str) -> str:"""Search the user's long-term memory."""hits = get_store().search(("memories", "user-1"), query=query)return "\n".join(h.value["text"] for h in hits) or "Nothing stored yet."agent = create_agent(model,tools=[save_preference, recall_preferences],store=MemorySyncStore(),)
Inject context before the model call
Both agent generations are covered. The middleware serves create_agent (langchain 1.x); the pre-model hook serves create_react_agent, which most existing LangGraph code still uses. Both shape only what the model sees for that one call — checkpointed graph state is never mutated, so your stored transcript stays exactly what was said.
# create_agent — the middleware patternfrom langchain.agents import create_agentfrom langchain_memorysync.langgraph import MemorySyncMemoryMiddlewareagent = create_agent(model,tools=tools,middleware=[MemorySyncMemoryMiddleware(user_id="customer-7")],)# create_react_agent — the pre-model hookfrom langgraph.prebuilt import create_react_agentfrom langchain_memorysync.langgraph import create_memorysync_pre_model_hookagent = create_react_agent(model,tools,pre_model_hook=create_memorysync_pre_model_hook(user_id="customer-7"),)
Persist turns from the graph
from langgraph.graph import StateGraph, MessagesState, START, ENDfrom langchain_memorysync.langgraph import create_persist_turn_noderemember = create_persist_turn_node(user_id="customer-7")graph = StateGraph(MessagesState)graph.add_node("agent", call_model)graph.add_node("remember", remember)graph.add_edge(START, "agent")graph.add_edge("agent", "remember")graph.add_edge("remember", END)app = graph.compile()# The session defaults to the graph's thread_id, so turns group by# conversation with no extra wiring.app.invoke({"messages": [("user", "I always fly aisle.")]},config={"configurable": {"thread_id": "trip-1"}})
Search memory as a graph tool
from langchain_memorysync.langgraph import create_memory_search_toolsearch = create_memory_search_tool(user_id="customer-7")agent = create_react_agent(model, tools=[search, *other_tools])
It is the same search_memory structured tool the LangChain integration ships: read-only by construction, scoped to one end user, and it returns readable strings instead of raising — a tool exception would abort the whole graph run.
Complete example: a support agent that remembers
Everything wired together: context injected before each model call, the exchange persisted after it, and durable facts in the store — across two separate conversations. Stop the process between them; the second still knows what the first learned.
from langchain.agents import create_agentfrom langchain_openai import ChatOpenAIfrom langchain_memorysync.langgraph import (MemorySyncMemoryMiddleware,MemorySyncStore,create_memory_search_tool,)USER = "customer-7"agent = create_agent(ChatOpenAI(model="gpt-4o-mini"),tools=[create_memory_search_tool(user_id=USER)],middleware=[MemorySyncMemoryMiddleware(user_id=USER, auto_persist=True)],store=MemorySyncStore(),)# Conversation 1 — Mondayagent.invoke({"messages": [{"role": "user", "content": "I'm vegetarian and I fly aisle."}]},config={"configurable": {"thread_id": "monday"}},)# Conversation 2 — Friday, a fresh thread (and maybe a fresh process)out = agent.invoke({"messages": [{"role": "user", "content": "Book my trip: flight plus a dinner spot."}]},config={"configurable": {"thread_id": "friday"}},)# The model already sees: vegetarian, aisle seat — injected from memory.print(out["messages"][-1].content)
Public API
| Export | Kind | Python | Node.js |
|---|---|---|---|
MemorySyncStore | LangGraph BaseStore | langchain_memorysync.langgraph | memorysync-langchain/langgraph |
MemorySyncMemoryMiddleware | create_agent middleware | ✓ (needs langchain>=1) | — (use the hook) |
create_memorysync_pre_model_hook / createMemorySyncPreModelHook | Pre-model hook factory | ✓ | ✓ |
create_persist_turn_node / createPersistTurnNode | Graph node factory | ✓ | ✓ |
create_memory_search_tool / createMemorySearchTool | Structured tool factory | ✓ | ✓ |
Both languages share wire formats and idempotency seeds, pinned by a cross-language parity test — a store written from Python is read from Node.js and vice versa.
Supported versions
| Package | Registry | Requires | Runtime |
|---|---|---|---|
langchain-memorysync[langgraph] 1.1.0 | PyPI | langgraph >=1.2 <2 (langchain-core 1.x) | Python 3.10+ |
memorysync-langchain 1.1.0 (/langgraph subpath) | npm | @langchain/langgraph >=0.2 <2 (optional peer) | Node 18+ |
The base LangChain surface of both packages keeps its langchain-core 0.3.x support — importing the langgraph subpath is what requires the 1.x line, and an incompatible install fails at import with a sentence that names the fix, never a crash mid-run.