Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
langchain-ai avatar

Langgraph Persistence

  • 12.5k installs
  • 1.1k repo stars
  • Updated July 30, 2026
  • langchain-ai/langchain-skills

How to configure LangGraph state persistence, choose checkpointer backends, manage thread-scoped and cross-thread memory, enable time travel replay, and tune subgraph checkpoint scoping for interrupts and parallel execut

About

LangGraph Persistence manages durable graph state through checkpointers that save/load state at every super-step, thread IDs that isolate separate conversation sequences, and Stores for cross-thread long-term memory. Developers use it when building multi-turn agents that must remember conversation history, support time travel/replay from past checkpoints, or share user preferences across sessions. Key workflows include selecting appropriate checkpointers (InMemorySaver for testing, PostgresSaver for production), configuring subgraph persistence modes for interrupt and parallel execution, and using Stores to access user facts outside thread scope via the Runtime object.

  • Checkpointer selection table: InMemorySaver (testing), SqliteSaver (local dev), PostgresSaver (production)
  • Thread ID isolates per-conversation state; omitting it prevents persistence entirely
  • Time travel: browse checkpoint history, replay from past state, or fork by updating then resuming
  • Subgraph checkpointer modes (False/None/True) control interrupt support, multi-turn memory, and parallel safety
  • Store for cross-thread memory (user preferences, facts) accessed via runtime.store in nodes

Langgraph Persistence by the numbers

  • 12,545 all-time installs (skills.sh)
  • +467 installs in the week ending Aug 5, 2026 (Skillselion tracking)
  • Ranked #63 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
  • Security screen: LOW risk (skills.sh audit)
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

langgraph-persistence capabilities & compatibility

Capabilities
checkpoint graph state at every super step · isolate conversation state per thread_id · browse and replay from past checkpoints · fork execution by updating state then resuming · cross thread long term memory via store · subgraph interrupt and parallel execution config
Works with
postgres · anthropic
Use cases
debugging · api development · orchestration · memory
Platforms
macOS · Windows · Linux
Runs
Local or remote
Pricing
Free
npx skills add https://github.com/langchain-ai/langchain-skills --skill langgraph-persistence

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs12.5k
repo stars1.1k
Security audit3 / 3 scanners passed
Last updatedJuly 30, 2026
Repositorylangchain-ai/langchain-skills

What it does

Persist LangGraph conversation state across invocations using checkpointers and thread IDs for multi-turn agents.

Who is it for?

Building multi-turn conversational agents, stateful agentic workflows, human-in-the-loop systems with interrupt/resume, and applications requiring audit trails or replay debugging.

Skip if: Stateless single-turn API endpoints, batch processing without conversation context, or simple tools that do not require memory between calls.

When should I use this skill?

Compiling a StateGraph that must remember prior messages or state across multiple graph.invoke() calls; adding interrupt capability; sharing facts across different user threads; or debugging via state history.

What you get

Production-ready persistent graphs with isolated per-user threads, cross-thread long-term memory, checkpoint history inspection, and safe parallel subgraph execution.

  • Compiled graph with checkpointer and thread_id in config
  • Thread-scoped conversation history
  • Cross-thread Store for long-term memory

By the numbers

  • Three checkpointer options provided with production-ready classification per table
  • Subgraph checkpointer modes: False, None, True with 5 feature rows in scoping table
  • Two memory types: short-term (checkpointer, thread-scoped) and long-term (Store, cross-thread)

Files

SKILL.mdMarkdownGitHub ↗

<overview> LangGraph's persistence layer enables durable execution by checkpointing graph state:

  • Checkpointer: Saves/loads graph state at every super-step
  • Thread ID: Identifies separate checkpoint sequences (conversations)
  • Store: Cross-thread memory for user preferences, facts

Two memory types:

  • Short-term (checkpointer): Thread-scoped conversation history
  • Long-term (store): Cross-thread user preferences, facts

</overview>

<checkpointer-selection>

CheckpointerUse CaseProduction Ready
InMemorySaverTesting, developmentNo
SqliteSaverLocal developmentPartial
PostgresSaverProductionYes

</checkpointer-selection>

---

Checkpointer Setup

<ex-basic-persistence> <python> Set up a basic graph with in-memory checkpointing and thread-based state persistence.

from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
from typing_extensions import TypedDict, Annotated
import operator

class State(TypedDict):
    messages: Annotated[list, operator.add]

def add_message(state: State) -> dict:
    return {"messages": ["Bot response"]}

checkpointer = InMemorySaver()

graph = (
    StateGraph(State)
    .add_node("respond", add_message)
    .add_edge(START, "respond")
    .add_edge("respond", END)
    .compile(checkpointer=checkpointer)  # Pass at compile time
)

# ALWAYS provide thread_id
config = {"configurable": {"thread_id": "conversation-1"}}

result1 = graph.invoke({"messages": ["Hello"]}, config)
print(len(result1["messages"]))  # 2

result2 = graph.invoke({"messages": ["How are you?"]}, config)
print(len(result2["messages"]))  # 4 (previous + new)

</python> <typescript> Set up a basic graph with in-memory checkpointing and thread-based state persistence.

import { MemorySaver, StateGraph, StateSchema, MessagesValue, START, END } from "@langchain/langgraph";
import { HumanMessage } from "@langchain/core/messages";

const State = new StateSchema({ messages: MessagesValue });

const addMessage = async (state: typeof State.State) => {
  return { messages: [{ role: "assistant", content: "Bot response" }] };
};

const checkpointer = new MemorySaver();

const graph = new StateGraph(State)
  .addNode("respond", addMessage)
  .addEdge(START, "respond")
  .addEdge("respond", END)
  .compile({ checkpointer });

// ALWAYS provide thread_id
const config = { configurable: { thread_id: "conversation-1" } };

const result1 = await graph.invoke({ messages: [new HumanMessage("Hello")] }, config);
console.log(result1.messages.length);  // 2

const result2 = await graph.invoke({ messages: [new HumanMessage("How are you?")] }, config);
console.log(result2.messages.length);  // 4 (previous + new)

</typescript> </ex-basic-persistence>

<ex-production-postgres> <python> Configure PostgreSQL-backed checkpointing for production deployments.

import os
from langgraph.checkpoint.postgres import PostgresSaver

# Run once during deployment (not at application startup):
#   PostgresSaver.from_conn_string(os.environ["DATABASE_URL"]).setup()

with PostgresSaver.from_conn_string(os.environ["DATABASE_URL"]) as checkpointer:
    graph = builder.compile(checkpointer=checkpointer)

</python> <typescript> Configure PostgreSQL-backed checkpointing for production deployments.

import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";

// Run once during deployment (not at application startup):
//   await PostgresSaver.fromConnString(process.env.DATABASE_URL!).setup();

const checkpointer = PostgresSaver.fromConnString(process.env.DATABASE_URL!);
const graph = builder.compile({ checkpointer });

</typescript> </ex-production-postgres>

---

Thread Management

<ex-separate-threads> <python> Demonstrate isolated state between different thread IDs.

# Different threads maintain separate state
alice_config = {"configurable": {"thread_id": "user-alice"}}
bob_config = {"configurable": {"thread_id": "user-bob"}}

graph.invoke({"messages": ["Hi from Alice"]}, alice_config)
graph.invoke({"messages": ["Hi from Bob"]}, bob_config)

# Alice's state is isolated from Bob's

</python> <typescript> Demonstrate isolated state between different thread IDs.

// Different threads maintain separate state
const aliceConfig = { configurable: { thread_id: "user-alice" } };
const bobConfig = { configurable: { thread_id: "user-bob" } };

await graph.invoke({ messages: [new HumanMessage("Hi from Alice")] }, aliceConfig);
await graph.invoke({ messages: [new HumanMessage("Hi from Bob")] }, bobConfig);

// Alice's state is isolated from Bob's

</typescript> </ex-separate-threads>

---

State History & Time Travel

<ex-resume-from-checkpoint> <python> Time travel: browse checkpoint history and replay or fork from a past state.

config = {"configurable": {"thread_id": "session-1"}}

result = graph.invoke({"messages": ["start"]}, config)

# Browse checkpoint history
states = list(graph.get_state_history(config))

# Replay from a past checkpoint
past = states[-2]
result = graph.invoke(None, past.config)  # None = resume from checkpoint

# Or fork: update state at a past checkpoint, then resume
fork_config = graph.update_state(past.config, {"messages": ["edited"]})
result = graph.invoke(None, fork_config)

</python> <typescript> Time travel: browse checkpoint history and replay or fork from a past state.

const config = { configurable: { thread_id: "session-1" } };

const result = await graph.invoke({ messages: ["start"] }, config);

// Browse checkpoint history (async iterable, collect to array)
const states: Awaited<ReturnType<typeof graph.getState>>[] = [];
for await (const state of graph.getStateHistory(config)) {
  states.push(state);
}

// Replay from a past checkpoint
const past = states[states.length - 2];
const replayed = await graph.invoke(null, past.config);  // null = resume from checkpoint

// Or fork: update state at a past checkpoint, then resume
const forkConfig = await graph.updateState(past.config, { messages: ["edited"] });
const forked = await graph.invoke(null, forkConfig);

</typescript> </ex-resume-from-checkpoint>

<ex-update-state> <python> Manually update graph state before resuming execution.

config = {"configurable": {"thread_id": "session-1"}}

# Modify state before resuming
graph.update_state(config, {"data": "manually_updated"})

# Resume with updated state
result = graph.invoke(None, config)

</python> <typescript> Manually update graph state before resuming execution.

const config = { configurable: { thread_id: "session-1" } };

// Modify state before resuming
await graph.updateState(config, { data: "manually_updated" });

// Resume with updated state
const result = await graph.invoke(null, config);

</typescript> </ex-update-state>

---

Subgraph Checkpointer Scoping

When compiling a subgraph, the checkpointer parameter controls persistence behavior. This is critical for subgraphs that use interrupts, need multi-turn memory, or run in parallel.

<subgraph-checkpointer-scoping-table>

Featurecheckpointer=FalseNone (default)True
Interrupts (HITL)NoYesYes
Multi-turn memoryNoNoYes
Multiple calls (different subgraphs)YesYesWarning (namespace conflicts possible)
Multiple calls (same subgraph)YesYesNo
State inspectionNoWarning (current invocation only)Yes

</subgraph-checkpointer-scoping-table>

<subgraph-checkpointer-when-to-use>

When to use each mode

  • `checkpointer=False` — Subgraph doesn't need interrupts or persistence. Simplest option, no checkpoint overhead.
  • `None` (default / omit `checkpointer`) — Subgraph needs interrupt() but not multi-turn memory. Each invocation starts fresh but can pause/resume. Parallel execution works because each invocation gets a unique namespace.
  • `checkpointer=True` — Subgraph needs to remember state across invocations (multi-turn conversations). Each call picks up where the last left off.

</subgraph-checkpointer-when-to-use>

<warning-stateful-subgraphs-parallel>

Warning: Stateful subgraphs (checkpointer=True) do NOT support calling the same subgraph instance multiple times within a single node — the calls write to the same checkpoint namespace and conflict.

</warning-stateful-subgraphs-parallel>

<ex-subgraph-checkpointer-modes> <python> Choose the right checkpointer mode for your subgraph.

# No interrupts needed — opt out of checkpointing
subgraph = subgraph_builder.compile(checkpointer=False)

# Need interrupts but not cross-invocation persistence (default)
subgraph = subgraph_builder.compile()

# Need cross-invocation persistence (stateful)
subgraph = subgraph_builder.compile(checkpointer=True)

</python> <typescript> Choose the right checkpointer mode for your subgraph.

// No interrupts needed — opt out of checkpointing
const subgraph = subgraphBuilder.compile({ checkpointer: false });

// Need interrupts but not cross-invocation persistence (default)
const subgraph = subgraphBuilder.compile();

// Need cross-invocation persistence (stateful)
const subgraph = subgraphBuilder.compile({ checkpointer: true });

</typescript> </ex-subgraph-checkpointer-modes>

<parallel-subgraph-namespacing>

Parallel subgraph namespacing

When multiple different stateful subgraphs run in parallel, wrap each in its own StateGraph with a unique node name for stable namespace isolation:

<python>

from langgraph.graph import MessagesState, StateGraph

def create_sub_agent(model, *, name, **kwargs):
    """Wrap an agent with a unique node name for namespace isolation."""
    agent = create_agent(model=model, name=name, **kwargs)
    return (
        StateGraph(MessagesState)
        .add_node(name, agent)  # unique name -> stable namespace
        .add_edge("__start__", name)
        .compile()
    )

fruit_agent = create_sub_agent(
    "gpt-4.1-mini", name="fruit_agent",
    tools=[fruit_info], prompt="...", checkpointer=True,
)
veggie_agent = create_sub_agent(
    "gpt-4.1-mini", name="veggie_agent",
    tools=[veggie_info], prompt="...", checkpointer=True,
)

</python> <typescript>

import { StateGraph, StateSchema, MessagesValue, START } from "@langchain/langgraph";

function createSubAgent(model: string, { name, ...kwargs }: { name: string; [key: string]: any }) {
  const agent = createAgent({ model, name, ...kwargs });
  return new StateGraph(new StateSchema({ messages: MessagesValue }))
    .addNode(name, agent)  // unique name -> stable namespace
    .addEdge(START, name)
    .compile();
}

const fruitAgent = createSubAgent("gpt-4.1-mini", {
  name: "fruit_agent", tools: [fruitInfo], prompt: "...", checkpointer: true,
});
const veggieAgent = createSubAgent("gpt-4.1-mini", {
  name: "veggie_agent", tools: [veggieInfo], prompt: "...", checkpointer: true,
});

</typescript>

Note: Subgraphs added as nodes (via add_node) already get name-based namespaces automatically and don't need this wrapper.

</parallel-subgraph-namespacing>

---

Long-Term Memory (Store)

<ex-long-term-memory-store> <python> Use a Store for cross-thread memory to share user preferences across conversations.

from langgraph.store.memory import InMemoryStore

store = InMemoryStore()

# Save user preference (available across ALL threads)
store.put(("alice", "preferences"), "language", {"preference": "short responses"})

# Node with store — access via runtime
from langgraph.runtime import Runtime

def respond(state, runtime: Runtime):
    prefs = runtime.store.get((state["user_id"], "preferences"), "language")
    return {"response": f"Using preference: {prefs.value}"}

# Compile with BOTH checkpointer and store
graph = builder.compile(checkpointer=checkpointer, store=store)

# Both threads access same long-term memory
graph.invoke({"user_id": "alice"}, {"configurable": {"thread_id": "thread-1"}})
graph.invoke({"user_id": "alice"}, {"configurable": {"thread_id": "thread-2"}})  # Same preferences!

</python> <typescript> Use a Store for cross-thread memory to share user preferences across conversations.

import { MemoryStore } from "@langchain/langgraph";

const store = new MemoryStore();

// Save user preference (available across ALL threads)
await store.put(["alice", "preferences"], "language", { preference: "short responses" });

// Node with store — access via runtime
const respond = async (state: typeof State.State, runtime: any) => {
  const item = await runtime.store?.get(["alice", "preferences"], "language");
  return { response: `Using preference: ${item?.value?.preference}` };
};

// Compile with BOTH checkpointer and store
const graph = builder.compile({ checkpointer, store });

// Both threads access same long-term memory
await graph.invoke({ userId: "alice" }, { configurable: { thread_id: "thread-1" } });
await graph.invoke({ userId: "alice" }, { configurable: { thread_id: "thread-2" } });  // Same preferences!

</typescript> </ex-long-term-memory-store>

<ex-store-operations> <python> Basic store operations: put, get, search, and delete.

from langgraph.store.memory import InMemoryStore

store = InMemoryStore()

store.put(("user-123", "facts"), "location", {"city": "San Francisco"})  # Put
item = store.get(("user-123", "facts"), "location")  # Get
results = store.search(("user-123", "facts"), filter={"city": "San Francisco"})  # Search
store.delete(("user-123", "facts"), "location")  # Delete

</python> </ex-store-operations>

---

Fixes

<fix-thread-id-required> <python> Always provide thread_id in config to enable state persistence.

# WRONG: No thread_id - state NOT persisted!
graph.invoke({"messages": ["Hello"]})
graph.invoke({"messages": ["What did I say?"]})  # Doesn't remember!

# CORRECT: Always provide thread_id
config = {"configurable": {"thread_id": "session-1"}}
graph.invoke({"messages": ["Hello"]}, config)
graph.invoke({"messages": ["What did I say?"]}, config)  # Remembers!

</python> <typescript> Always provide thread_id in config to enable state persistence.

// WRONG: No thread_id - state NOT persisted!
await graph.invoke({ messages: [new HumanMessage("Hello")] });
await graph.invoke({ messages: [new HumanMessage("What did I say?")] });  // Doesn't remember!

// CORRECT: Always provide thread_id
const config = { configurable: { thread_id: "session-1" } };
await graph.invoke({ messages: [new HumanMessage("Hello")] }, config);
await graph.invoke({ messages: [new HumanMessage("What did I say?")] }, config);  // Remembers!

</typescript> </fix-thread-id-required>

<fix-inmemory-not-for-production> <python> Use PostgresSaver instead of InMemorySaver for production persistence.

# WRONG: Data lost on process restart
checkpointer = InMemorySaver()  # In-memory only!

# CORRECT: Use persistent storage for production
from langgraph.checkpoint.postgres import PostgresSaver
with PostgresSaver.from_conn_string("postgresql://...") as checkpointer:
    checkpointer.setup()  # only needed on first use to create tables
    graph = builder.compile(checkpointer=checkpointer)

</python> <typescript> Use PostgresSaver instead of MemorySaver for production persistence.

// WRONG: Data lost on process restart
const checkpointer = new MemorySaver();  // In-memory only!

// CORRECT: Use persistent storage for production
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
const checkpointer = PostgresSaver.fromConnString("postgresql://...");
await checkpointer.setup(); // only needed on first use to create tables

</typescript> </fix-inmemory-not-for-production>

<fix-update-state-with-reducers> <python> Use Overwrite to replace state values instead of passing through reducers.

from langgraph.types import Overwrite

# State with reducer: items: Annotated[list, operator.add]
# Current state: {"items": ["A", "B"]}

# update_state PASSES THROUGH reducers
graph.update_state(config, {"items": ["C"]})  # Result: ["A", "B", "C"] - Appended!

# To REPLACE instead, use Overwrite
graph.update_state(config, {"items": Overwrite(["C"])})  # Result: ["C"] - Replaced

</python> <typescript> Use Overwrite to replace state values instead of passing through reducers.

import { Overwrite } from "@langchain/langgraph";

// State with reducer: items uses concat reducer
// Current state: { items: ["A", "B"] }

// updateState PASSES THROUGH reducers
await graph.updateState(config, { items: ["C"] });  // Result: ["A", "B", "C"] - Appended!

// To REPLACE instead, use Overwrite
await graph.updateState(config, { items: new Overwrite(["C"]) });  // Result: ["C"] - Replaced

</typescript> </fix-update-state-with-reducers>

<fix-store-injection> <python> Access store via the Runtime object in graph nodes.

# WRONG: Store not available in node
def my_node(state):
    store.put(...)  # NameError! store not defined

# CORRECT: Access store via runtime
from langgraph.runtime import Runtime

def my_node(state, runtime: Runtime):
    runtime.store.put(...)  # Correct store instance

</python> <typescript> Access store via runtime parameter in graph nodes.

// WRONG: Store not available in node
const myNode = async (state) => {
  store.put(...);  // ReferenceError!
};

// CORRECT: Access store via runtime
const myNode = async (state, runtime) => {
  await runtime.store?.put(...);  // Correct store instance
};

</typescript> </fix-store-injection>

<boundaries>

What You Should NOT Do

  • Use InMemorySaver in production — data lost on restart; use PostgresSaver
  • Forget thread_id — state won't persist without it
  • Expect update_state to bypass reducers — it passes through them; use Overwrite to replace
  • Run the same stateful subgraph (checkpointer=True) in parallel within one node — namespace conflict
  • Access store directly in a node — use runtime.store via the Runtime param

</boundaries>

Related skills

Forks & variants (1)

Langgraph Persistence has 1 known copy in the catalog totaling 44 installs. They canonicalize to this original listing.

How it compares

Use langgraph-persistence when LangGraph agents need checkpoint resume and thread memory beyond what a basic create_agent loop provides.

FAQ

Why doesn't my graph remember previous messages?

You must always provide thread_id in config: {"configurable": {"thread_id": "session-1"}}. Without it, state is not persisted even if a checkpointer is compiled.

Which checkpointer should I use?

InMemorySaver for testing (not production-safe), SqliteSaver for local development, PostgresSaver for production (only production-ready option).

How do I share state across different threads?

Use Store (InMemoryStore or persistent backend) compiled with the graph. Access it in nodes via runtime.store parameter. Store is cross-thread; checkpointer is thread-scoped.

Is Langgraph Persistence safe to install?

skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.