Subgraph namespace isolation for parallel execution
When using inherited checkpointer mode (default), each subgraph invocation gets a unique namespace, allowing parallel execution to work correctly without conflicts. The subgraph starts fresh each time it is invoked.
MULTIPLE_SUBGRAPHS error when checkpointer=True
The MULTIPLE_SUBGRAPHS error occurs when you call a subgraph inside a node multiple times, and the subgraph is compiled with checkpointer=True (continuations mode). This prevents multiple invocations of the same subgraph from sharing checkpointing state.
Disable checkpointing for subgraph without interrupts
To avoid the MULTIPLE_SUBGRAPHS error when you don't need interrupts, compile the subgraph with checkpointer=False. In Python: subgraph = subgraph_builder.compile(checkpointer=False). In TypeScript: const subgraph = subgraphBuilder.compile({ checkpointer: false }). This opts out of checkpointing entirely.
Use inherited mode for interrupts without cross-invocation persistence
To allow interrupts while avoiding the MULTIPLE_SUBGRAPHS error when you don't need cross-invocation persistence, omit the checkpointer parameter and use the default inherited mode. In Python: subgraph = subgraph_builder.compile(). In TypeScript: const subgraph = subgraphBuilder.compile(). Each invocation gets a unique namespace for parallel execution, the subgraph starts fresh each time, but can still use interrupt().
Enable cross-invocation persistence for subgraphs with position-based namespaces
To enable cross-invocation persistence when calling a subgraph multiple times, compile with checkpointer=True. LangGraph automatically assigns each invocation a position-based namespace suffix (calling_node, calling_node|1, etc.) to prevent conflicts. For stable, name-based namespaces instead, wrap each subgraph invocation with a unique node name.
Interrupts with subgraphs called as functions
When invoking a subgraph within a node, if the subgraph contains an interrupt() call, both the parent node and the subgraph node will resume from the beginning when resumed. The parent graph will resume execution from the beginning of the node where the subgraph was invoked and the interrupt() was triggered. Similarly, the subgraph will also resume from the beginning of the node where interrupt() was called. Any code before the interrupt in both the parent and subgraph nodes will re-execute.
State access from parent graph to subgraph
When a subgraph updates state, the parent graph may not see the changes immediately because each subgraph manages its own checkpoint namespace. Fix: Use shared state via Store for data that needs to cross graph boundaries, or configure your subgraph to write to the parent checkpoint.
Subgraph persistence modes and checkpointer parameter
Subgraph persistence is controlled via the checkpointer parameter on .compile(): Per-invocation (checkpointer=None, default): each call starts fresh and inherits parent's checkpointer, supporting interrupts and durable execution within a single call. Per-thread (checkpointer=True): state accumulates across calls on same thread, picking up where last call left off. Stateless (checkpointer=False): no checkpointing, runs like plain function call, no interrupts or durable execution.
Per-invocation subgraph persistence details
Per-invocation is the recommended mode for most applications, including multi-agent systems where subagents handle independent requests. Each call starts fresh with no memory of previous calls. Supports interrupts and pause/resume within a single call. Multiple calls to the same subgraph work without conflicts since each invocation gets its own checkpoint namespace. Parent graph must be compiled with a checkpointer for this to work.
Per-thread subgraph persistence details
Use per-thread when a subagent needs to remember previous interactions, such as a research assistant building context over several exchanges or a coding assistant tracking edited files. State accumulates across calls on the same thread. Each call picks up where the last one left off. Does not support parallel tool calls to the same per-thread subagent—both calls write to same namespace causing checkpoint conflicts. Use ToolCallLimitMiddleware to prevent parallel calls.
Stateless subgraph persistence details
Stateless mode (checkpointer=False) runs a subgraph like a plain function call with no checkpointing overhead. No durable execution: if process crashes mid-run, subgraph cannot recover and must be re-run from beginning. Subgraph cannot pause/resume via interrupt().
Namespace isolation for multiple per-thread subgraphs
When calling multiple different per-thread subgraphs in the same node, each needs its own storage space so checkpoints don't overwrite each other. If calling subgraphs inside a node, namespaces are assigned based on call order (first call, second call, etc.), so reordering calls can mix up which subgraph loads which state. To avoid this, wrap each subagent in its own StateGraph with a unique node name—this gives each subgraph a stable, unique namespace. Subgraphs added as nodes already get name-based namespaces automatically.
View subgraph state with get_state
When persistence is enabled, inspect subgraph state using graph.get_state(config, subgraphs=True). With per-invocation persistence, returns subgraph state for current invocation only—each invocation starts fresh. With per-thread persistence, returns accumulated subgraph state across all invocations on the thread. Requires LangGraph to statically discover the subgraph (added as node or called inside node). Does not work when subgraph is called inside tool function or other indirection.
Checkpointer reference table
Subgraph checkpointer feature comparison: Per-invocation (checkpointer=None): supports interrupts (HITL) ✅, multi-turn memory ❌, multiple calls different subgraphs ✅, multiple calls same subgraph ✅, state inspection ⚠️ (current invocation only). Per-thread (checkpointer=True): supports interrupts ✅, multi-turn memory ✅, multiple calls different subgraphs ⚠️ (namespace conflicts), multiple calls same subgraph ❌, state inspection ✅. Stateless (checkpointer=False): supports interrupts ❌, multi-turn memory ❌, multiple calls different subgraphs ✅, multiple calls same subgraph ✅, state inspection ❌.
Example: Per-thread subgraph with ToolCallLimitMiddleware (Python)
from langchain.agents import create_agent
from langchain.agents.middleware import ToolCallLimitMiddleware
from langchain.tools import tool
from langgraph.checkpoint.memory import MemorySaver
@tool
def fruit_info(fruit_name: str) -> str:
"""Look up fruit info."""
return f"Info about {fruit_name}"
fruit_agent = create_agent(
model="gpt-5.4-mini",
tools=[fruit_info],
prompt="You are a fruit expert. Use the fruit_info tool. Respond in one sentence.",
checkpointer=True,
)
@tool
def ask_fruit_expert(question: str) -> str:
"""Ask the fruit expert. Use for ALL fruit questions."""
response = fruit_agent.invoke(
{"messages": [{"role": "user", "content": question}]},
)
return response["messages"][-1].content
agent = create_agent(
model="gpt-5.4-mini",
tools=[ask_fruit_expert],
prompt="You have a fruit expert. ALWAYS delegate fruit questions to ask_fruit_expert.",
middleware=[ToolCallLimitMiddleware(tool_name="ask_fruit_expert", run_limit=1)],
checkpointer=MemorySaver(),
)
Example: Namespace isolation with unique node names (Python)
from langgraph.graph import MessagesState, StateGraph
from langchain.agents import create_agent
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)
.add_edge("__start__", name)
.compile()
)
fruit_agent = create_sub_agent(
"gpt-5.4-mini", name="fruit_agent",
tools=[fruit_info], prompt="...", checkpointer=True,
)
veggie_agent = create_sub_agent(
"gpt-5.4-mini", name="veggie_agent",
tools=[veggie_info], prompt="...", checkpointer=True,
)
Example: View per-invocation subgraph state (Python)
from langgraph.graph import START, StateGraph
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import interrupt, Command
from typing_extensions import TypedDict
class State(TypedDict):
foo: str
def subgraph_node_1(state: State):
value = interrupt("Provide value:")
return {"foo": state["foo"] + value}
subgraph_builder = StateGraph(State)
subgraph_builder.add_node(subgraph_node_1)
subgraph_builder.add_edge(START, "subgraph_node_1")
subgraph = subgraph_builder.compile()
builder = StateGraph(State)
builder.add_node("node_1", subgraph)
builder.add_edge(START, "node_1")
checkpointer = MemorySaver()
graph = builder.compile(checkpointer=checkpointer)
config = {"configurable": {"thread_id": "1"}}
graph.invoke({"foo": ""}, config)
subgraph_state = graph.get_state(config, subgraphs=True).tasks[0].state
graph.invoke(Command(resume="bar"), config)
Subgraph time travel: inherited checkpointer (default)
By default, a subgraph inherits the parent's checkpointer. The parent treats the entire subgraph as a single super-step—there is only one parent-level checkpoint for the whole subgraph execution. Time traveling from before the subgraph re-executes it from scratch. You cannot time travel to a point between nodes in a default subgraph; you can only time travel from the parent level.
Subgraph with own checkpointer for fine-grained time travel
Set checkpointer=True on the subgraph to give it its own checkpoint history. This creates checkpoints at each step within the subgraph, allowing you to time travel from a specific point inside it—for example, between two interrupts. Use get_state(config, subgraphs=True) to access the subgraph's own checkpoint config in the parent state's tasks[0].state.config, then fork from it.
Subgraph with checkpointer fork example
Python example: Compile subgraph with checkpointer=True. After hitting interrupts in both step_a and step_b, call parent_state = graph.get_state(config, subgraphs=True), then sub_config = parent_state.tasks[0].state.config. Fork with fork_config = graph.update_state(sub_config, {'value': ['forked']}), then graph.invoke(None, fork_config). step_b re-executes, step_a's result is preserved.
RemoteCheckpointer enables subgraph checkpointing
Implemented RemoteCheckpointer to enable subgraph checkpointing, enhancing task execution reliability.
Checkpointer automatically propagated to subgraphs
When a parent graph is compiled with a checkpointer, LangGraph automatically propagates the checkpointer to all child subgraphs. Subgraphs do not need to be separately compiled with a checkpointer.
Subgraph can be compiled with checkpointer=True
A subgraph can be compiled with checkpointer=True to enable subgraph-specific checkpointing behavior. This allows configuring persistence levels including interrupt support and stateful continuations.
Example: Subgraph with parent checkpointer
Example showing how to compile a subgraph without a checkpointer and add it as a node to a parent graph that is compiled with a checkpointer. The parent's checkpointer is automatically propagated to the subgraph.