Conversation state and user memory have different lifetimes in an agent. LangGraph checkpoints keep each thread's execution state isolated, while a store gives nodes a separate place for facts that should be available to other threads.
The graph compiles an InMemorySaver and InMemoryStore together. The checkpointer records each run by thread ID, while the store organizes a memory under a namespace and key chosen from runtime context rather than checkpoint configuration.
The InMemoryStore implementation loses its contents when the Python process exits, so it is suited to local development and tests. A production deployment should replace it with a database-backed BaseStore implementation while keeping the same namespace and node access pattern.
from dataclasses import dataclass from typing import Literal from langgraph.checkpoint.memory import InMemorySaver from langgraph.graph import START, StateGraph from langgraph.runtime import Runtime from langgraph.store.memory import InMemoryStore from typing_extensions import TypedDict class State(TypedDict): action: Literal["save", "recall"] preference: str recalled: str @dataclass class Context: user_id: str
def memory_node(state: State, runtime: Runtime[Context]): namespace = ("users", runtime.context.user_id, "memories") key = "meal-preference" if state["action"] == "save": runtime.store.put(namespace, key, {"preference": state["preference"]}) return {"recalled": ""} item = runtime.store.get(namespace, key) return {"recalled": item.value["preference"] if item else "missing"}
The user ID belongs in the namespace rather than the thread configuration, which lets separate threads recall the same user's memory without sharing their checkpoint state.
builder = StateGraph(State, context_schema=Context) builder.add_node("memory", memory_node) builder.add_edge(START, "memory") store = InMemoryStore() checkpointer = InMemorySaver() graph = builder.compile(checkpointer=checkpointer, store=store)
context = Context(user_id="customer-42") write_config = {"configurable": {"thread_id": "support-2026-01"}} read_config = {"configurable": {"thread_id": "support-2026-02"}} graph.invoke( {"action": "save", "preference": "vegetarian", "recalled": ""}, write_config, context=context, ) result = graph.invoke( {"action": "recall", "preference": "", "recalled": ""}, read_config, context=context, )
namespace = ("users", context.user_id, "memories") item = store.get(namespace, "meal-preference") write_state = graph.get_state(write_config).values read_state = graph.get_state(read_config).values assert item is not None assert result["recalled"] == "vegetarian" assert write_state["action"] == "save" assert read_state["action"] == "recall" print(f"namespace: {item.namespace}") print(f"stored preference: {item.value['preference']}") print(f"write thread action: {write_state['action']}") print(f"read thread action: {read_state['action']}") print(f"recalled preference: {result['recalled']}")
$ python memory_graph.py
namespace: ('users', 'customer-42', 'memories')
stored preference: vegetarian
write thread action: save
read thread action: recall
recalled preference: vegetarian