MemorySync
Getting Started

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

PieceWhat it does
MemorySyncStoreA LangGraph BaseStore: durable cross-thread key-value memory with semantic search, compiled straight into any graph via store=.
Memory middlewareContext injection for create_agent (langchain 1.x) — the modern middleware pattern, with optional auto-persistence.
Pre-model hookThe same injection for create_react_agent(pre_model_hook=...), for the installed base.
Persist nodeA graph node that writes the latest exchange verbatim — safe under LangGraph’s node retries.
Search toolA graph-callable semantic memory search tool that never raises.
ConcernUse
Thread state (checkpoints, resume, time travel)A checkpointer — langgraph-checkpoint-postgres, -sqlite, -redis.
Long-term memory across threads, sessions and surfacesMemorySync, 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 MemorySyncStore
store = MemorySyncStore() # MEMORYSYNC_API_KEY from the env
store.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_agent
from langgraph.config import get_store
from langchain_core.tools import tool
from langchain_memorysync.langgraph import MemorySyncStore
@tool
def 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}."
@tool
def 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 pattern
from langchain.agents import create_agent
from langchain_memorysync.langgraph import MemorySyncMemoryMiddleware
agent = create_agent(
model,
tools=tools,
middleware=[MemorySyncMemoryMiddleware(user_id="customer-7")],
)
# create_react_agent — the pre-model hook
from langgraph.prebuilt import create_react_agent
from langchain_memorysync.langgraph import create_memorysync_pre_model_hook
agent = 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, END
from langchain_memorysync.langgraph import create_persist_turn_node
remember = 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_tool
search = 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_agent
from langchain_openai import ChatOpenAI
from 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 — Monday
agent.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

ExportKindPythonNode.js
MemorySyncStoreLangGraph BaseStorelangchain_memorysync.langgraphmemorysync-langchain/langgraph
MemorySyncMemoryMiddlewarecreate_agent middleware✓ (needs langchain>=1)— (use the hook)
create_memorysync_pre_model_hook / createMemorySyncPreModelHookPre-model hook factory
create_persist_turn_node / createPersistTurnNodeGraph node factory
create_memory_search_tool / createMemorySearchToolStructured 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

PackageRegistryRequiresRuntime
langchain-memorysync[langgraph] 1.1.0PyPIlanggraph >=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.

Where to go next

Was this page helpful?