langgraph-persistence

star 3

Implementing persistence and checkpointing in LangGraph: saving state, resuming execution, thread IDs, and checkpointer libraries

christian-bromann By christian-bromann schedule Updated 2/11/2026

name: langgraph-persistence description: Implementing persistence and checkpointing in LangGraph: saving state, resuming execution, thread IDs, and checkpointer libraries language: python

langgraph-persistence (Python)


name: langgraph-persistence description: Implementing persistence and checkpointing - saving state, resuming execution, thread IDs, and checkpointer libraries

Overview

LangGraph's persistence layer enables durable execution by checkpointing graph state at every super-step. This unlocks human-in-the-loop, memory, time travel, and fault-tolerance capabilities.

Key Components:

  • Checkpointer: Saves/loads graph state
  • Thread ID: Identifier for checkpoint sequences
  • Checkpoints: Snapshots of state at each step

Decision Table: Checkpointer Selection

Checkpointer Use Case Persistence Production Ready
InMemorySaver Testing, development In-memory only ❌ No
SqliteSaver Local development SQLite file ⚠️ Single-user
PostgresSaver Production PostgreSQL ✅ Yes
AsyncPostgresSaver Async production PostgreSQL ✅ Yes

Code Examples

Basic Persistence with InMemorySaver

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"]}

# Create checkpointer
checkpointer = InMemorySaver()

# Compile with checkpointer
graph = (
    StateGraph(State)
    .add_node("respond", add_message)
    .add_edge(START, "respond")
    .add_edge("respond", END)
    .compile(checkpointer=checkpointer)  # Enable persistence
)

# First invocation with thread_id
config = {"configurable": {"thread_id": "conversation-1"}}
result1 = graph.invoke({"messages": ["Hello"]}, config)
print(len(result1["messages"]))  # 2

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

SQLite Persistence

from langgraph.checkpoint.sqlite import SqliteSaver

# Create SQLite checkpointer
checkpointer = SqliteSaver.from_conn_string("checkpoints.db")

graph = (
    StateGraph(State)
    .add_node("process", process_node)
    .add_edge(START, "process")
    .add_edge("process", END)
    .compile(checkpointer=checkpointer)
)

# Use with thread_id
config = {"configurable": {"thread_id": "user-123"}}
result = graph.invoke({"data": "test"}, config)

Async PostgreSQL Persistence

from langgraph.checkpoint.postgres import AsyncPostgresSaver

async def main():
    # Create async Postgres checkpointer
    async with AsyncPostgresSaver.from_conn_string(
        "postgresql://user:pass@localhost/db"
    ) as checkpointer:
        graph = (
            StateGraph(State)
            .add_node("process", process_node)
            .add_edge(START, "process")
            .add_edge("process", END)
            .compile(checkpointer=checkpointer)
        )
        
        config = {"configurable": {"thread_id": "thread-1"}}
        result = await graph.ainvoke({"data": "test"}, config)

Retrieving State

# Get current state
config = {"configurable": {"thread_id": "conversation-1"}}
current_state = graph.get_state(config)
print(current_state.values)  # Current state
print(current_state.next)    # Next nodes to execute

# Get state history
for state in graph.get_state_history(config):
    print(f"Step: {state.values}")

Resuming from Checkpoint

from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver()

def step1(state): 
    return {"data": "step1"}

def step2(state):
    return {"data": state["data"] + "_step2"}

graph = (
    StateGraph(State)
    .add_node("step1", step1)
    .add_node("step2", step2)
    .add_edge(START, "step1")
    .add_edge("step1", "step2")
    .add_edge("step2", END)
    .compile(
        checkpointer=checkpointer,
        interrupt_before=["step2"]  # Pause before step2
    )
)

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

# Run until breakpoint
result = graph.invoke({"data": "start"}, config)

# Resume execution
result = graph.invoke(None, config)  # None continues from checkpoint

Update State

# Modify state before resuming
config = {"configurable": {"thread_id": "1"}}

# Update state
graph.update_state(
    config,
    {"data": "manually_updated"}
)

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

Thread Management

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

# Alice's conversation
graph.invoke({"messages": ["Hi from Alice"]}, thread1_config)

# Bob's conversation (separate state)
graph.invoke({"messages": ["Hi from Bob"]}, thread2_config)

# Alice's state is isolated from Bob's

Checkpointer in Subgraphs

from langgraph.graph import StateGraph, START

# Only parent graph needs checkpointer
def subgraph_node(state):
    return {"data": "subgraph"}

subgraph = (
    StateGraph(State)
    .add_node("process", subgraph_node)
    .add_edge(START, "process")
    .compile()  # No checkpointer needed
)

# Parent graph with checkpointer
checkpointer = InMemorySaver()

parent = (
    StateGraph(State)
    .add_node("subgraph", subgraph)
    .add_edge(START, "subgraph")
    .compile(checkpointer=checkpointer)  # Propagates to subgraph
)

Boundaries

What You CAN Configure

✅ Choose checkpointer implementation ✅ Specify thread IDs ✅ Retrieve state at any checkpoint ✅ Update state between invocations ✅ Set breakpoints for pausing ✅ Access state history ✅ Resume from any checkpoint

What You CANNOT Configure

❌ Checkpoint format/schema (internal) ❌ Checkpoint timing (every super-step) ❌ Thread ID structure (arbitrary strings only)

Gotchas

1. Thread ID Required for Persistence

# ❌ WRONG - No thread_id, state not saved
graph.invoke({"data": "test"})  # Lost after execution!

# ✅ CORRECT - Always provide thread_id
config = {"configurable": {"thread_id": "session-1"}}
graph.invoke({"data": "test"}, config)

2. InMemorySaver Not for Production

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

# ✅ CORRECT - Use persistent storage
from langgraph.checkpoint.postgres import PostgresSaver
checkpointer = PostgresSaver.from_conn_string("postgresql://...")

3. Resuming Requires None Input

# ❌ WRONG - Providing input restarts
graph.invoke({"new": "data"}, config)  # Restarts from beginning

# ✅ CORRECT - Use None to resume
graph.invoke(None, config)  # Resumes from checkpoint

4. Update State Respects Reducers

from typing import Annotated
import operator

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

# Assume current state: {"items": ["A", "B"]}

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

# To overwrite, use different approach
from langgraph.types import Overwrite
graph.update_state(config, {"items": Overwrite(["C"])})
# Result: {"items": ["C"]}  # Replaced

5. Checkpointer Must Be Passed to Compile

# ❌ WRONG - Checkpointer after compile
graph = builder.compile()
graph.checkpointer = checkpointer  # Too late!

# ✅ CORRECT - Pass during compile
graph = builder.compile(checkpointer=checkpointer)

Links

Install via CLI
npx skills add https://github.com/christian-bromann/langchain-skills --skill langgraph-persistence
Repository Details
star Stars 3
call_split Forks 1
navigation Branch main
article Path SKILL.md
More from Creator
christian-bromann
christian-bromann Explore all skills →