How does state work in LangGraph subgraphs?

The principle of subgraphs in LangGraph: for each node, if it is a subgraph, is its state isolated—unaffected by other subgraphs? Specifically, if multiple subgraphs run concurrently and modify the same key (which could be defined in the parent graph or shared among these subgraphs), suppose Subgraph 1 finishes early, while Subgraph 2 has many nodes to execute. After Subgraph 1 completes, a node within Subgraph 2 might access that shared key—would it see the original value (before Subgraph 1’s modification) rather than the value modified by Subgraph 1?

Similarly, after a subgraph finishes execution, are keys that are not shared with the parent graph automatically discarded, so that in the next run these keys are reinitialized—similar to local variables in a Python function?

This is my personal guess; otherwise, the documentation wouldn’t emphasize that you can only access a subgraph’s values when the graph is interrupted.

1 Like

Hi @tiebingice

Sub-graphs documentation has references about state be it shared or different: Use subgraphs

Parallel execution: Use the Graph API

Defer Node execution: Use the Graph API

I know how to use subgraphs; my question is whether the states between subgraphs are isolated from each other.

Tried to create a minimal example similar to your original query

class State(TypedDict):
    messages: Annotated[Sequence[BaseMessage], add_messages]


class Subgraph1State(TypedDict):
    messages: Annotated[Sequence[BaseMessage], add_messages]
    subgraph1_messages: Annotated[Sequence[BaseMessage], add_messages]


# Graph nodes
def subgraph_1_entry_node(state: State) -> Subgraph1State:
    print(f"state in subgraph 1 entry node: {state}")
    # Update the state to use subgraph 1 state for internal processing
    state["messages"].append(HumanMessage(content="Inside subgraph 1 entry node"))
    return {"subgraph1_messages": state["messages"]}

def subgraph_1_node2(state: Subgraph1State):
    print(f"state in subgraph 1 node 2: {state}")

    state["subgraph1_messages"].append(HumanMessage(content="Inside subgraph 1 node 2"))
    # Now update the state to return main graph state as returning access to main graph
    return {"messages": state["subgraph1_messages"]}

# Subgraph 1 compilation
subgraph_1_builder = StateGraph(Subgraph1State)

subgraph_1_builder.add_node("subgraph_1_entry_node", subgraph_1_entry_node)
subgraph_1_builder.add_node("subgraph_1_node2", subgraph_1_node2)

subgraph_1_builder.add_edge(START, "subgraph_1_entry_node")
subgraph_1_builder.add_edge("subgraph_1_entry_node", "subgraph_1_node2")
subgraph_1_builder.add_edge("subgraph_1_node2", END)

subgraph_1 = subgraph_1_builder.compile(name="subgraph_1")


class Subgraph2State(TypedDict):
    messages: Annotated[Sequence[BaseMessage], add_messages]
    subgraph2_messages: Annotated[Sequence[BaseMessage], add_messages]

# Graph nodes
def subgraph_2_entry_node(state: State) -> Subgraph2State:
    print(f"state in subgraph 2 entry node: {state}")
    # Update the state to use subgraph 2 state for internal processing
    state["messages"].append(HumanMessage(content="Inside subgraph 2 entry node"))
    return {"subgraph2_messages": state["messages"]}

def subgraph_2_node2(state: Subgraph2State):
    print(f"state in subgraph 2 node 2: {state}")

    state["subgraph2_messages"].append(HumanMessage(content="Inside subgraph 2 node 2"))
    # Now update the state to return main graph state as returning access to main graph
    return {"messages": state["subgraph2_messages"]}

# Subgraph 2 compilation
subgraph_2_builder = StateGraph(Subgraph2State)

subgraph_2_builder.add_node("subgraph_2_entry_node", subgraph_2_entry_node)
subgraph_2_builder.add_node("subgraph_2_node2", subgraph_2_node2)

subgraph_2_builder.add_edge(START, "subgraph_2_entry_node")
subgraph_2_builder.add_edge("subgraph_2_entry_node", "subgraph_2_node2")
subgraph_2_builder.add_edge("subgraph_2_node2", END)

subgraph_2 = subgraph_2_builder.compile(name="subgraph_2")


# Graph nodes - It will use same state as main graph
def subgraph_3_entry_node(state: State):
    print(f"state in subgraph 3 entry node: {state}")

    state["messages"].append(HumanMessage(content="Inside subgraph 3 entry node"))
    return state

def subgraph_3_node2(state: State):
    print(f"state in subgraph 3 node 2: {state}")

    state["messages"].append(HumanMessage(content="Inside subgraph 3 node 2"))
    return state


# Subgraph 3 compilation
subgraph_3_builder = StateGraph(State)

subgraph_3_builder.add_node("subgraph_3_entry_node", subgraph_3_entry_node)
subgraph_3_builder.add_node("subgraph_3_node2", subgraph_3_node2)

subgraph_3_builder.add_edge(START, "subgraph_3_entry_node")
subgraph_3_builder.add_edge("subgraph_3_entry_node", "subgraph_3_node2")
subgraph_3_builder.add_edge("subgraph_3_node2", END)

subgraph_3 = subgraph_3_builder.compile(name="subgraph_3")


def entry_node(state: State):
    print(f"state in main graph entry node: {state}")
    state["messages"].append(HumanMessage(content="Inside main graph entry node"))
    return state

def node2(state: State):
    print(f"state in main graph node 2: {state}")
    state["messages"].append(HumanMessage(content="Inside main graph node 2"))
    return state


# Main graph compilation
builder = StateGraph(State)

builder.add_node("entry_node", entry_node)
builder.add_node("subgraph_1", subgraph_1)
builder.add_node("subgraph_2", subgraph_2)
builder.add_node("subgraph_3", subgraph_3)
builder.add_node("node2", node2)

builder.add_edge(START, "entry_node")
builder.add_edge("entry_node", "subgraph_1")
builder.add_edge("entry_node", "subgraph_2")
builder.add_edge(["subgraph_1", "subgraph_2"], "subgraph_3")

builder.add_edge("subgraph_3", "node2")
builder.add_edge("node2", END)

main_graph = builder.compile(name="main_graph")

print(main_graph.get_graph(xray=1).draw_mermaid())

Graph is compiled like below:

Now invoked graph like below:

main_graph.invoke({"messages": [HumanMessage(content="Start the process with Hello in main graph entry node")]})

Response is bit verbose but lets focus on print statements with “state in subgraph1 entry node” and “state in subgraph 2 entry node”.

Subgraph 1 and Subgraph2 are at same level, so as per my understanding they will be executed in same superstep hence a copy of state will be provided as input to both and processing in each subgraph does not impact the state of other subgraph

After execution of main graph entry node state would look like below

{'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a')

State in subgraph 1 and subgraph 2 is same as state object passed from main graph entry node, even though as per complete response subgraph1 was executed first but it didn’t changed the state for subgraph2 execution.

state in subgraph 1 entry node: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a')]}


state in subgraph 2 entry node: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a')]}

Complete response

state in main graph entry node: {'messages': [HumanMessage(content='Hello', additional_kwargs={}, response_metadata={}, id='0c9190c0-791e-4a07-86c6-bcdcef0a2cd6')]}
--- Node: entry_node ---
================================ Human Message =================================

Hello
================================ Human Message =================================

Inside main graph entry node


..
state in main graph entry node: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf')]}

state in subgraph 1 entry node: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a')]}


state in subgraph 1 node 2: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a'), HumanMessage(content='Inside subgraph 1 entry node', additional_kwargs={}, response_metadata={}, id='6de6cc34-5104-499d-a97e-cd94e238ec60')], 'subgraph1_messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a'), HumanMessage(content='Inside subgraph 1 entry node', additional_kwargs={}, response_metadata={}, id='6de6cc34-5104-499d-a97e-cd94e238ec60')]}

state in subgraph 2 entry node: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a')]}


state in subgraph 2 node 2: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a'), HumanMessage(content='Inside subgraph 2 entry node', additional_kwargs={}, response_metadata={}, id='9e95196c-cb2a-450b-b895-96f7ae53edb1')], 'subgraph2_messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a'), HumanMessage(content='Inside subgraph 2 entry node', additional_kwargs={}, response_metadata={}, id='9e95196c-cb2a-450b-b895-96f7ae53edb1')]}

state in subgraph 3 entry node: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a'), HumanMessage(content='Inside subgraph 1 entry node', additional_kwargs={}, response_metadata={}, id='6de6cc34-5104-499d-a97e-cd94e238ec60'), HumanMessage(content='Inside subgraph 1 node 2', additional_kwargs={}, response_metadata={}, id='f52650dc-e2d3-4e88-b98b-f6870b6e325c'), HumanMessage(content='Inside subgraph 2 entry node', additional_kwargs={}, response_metadata={}, id='9e95196c-cb2a-450b-b895-96f7ae53edb1'), HumanMessage(content='Inside subgraph 2 node 2', additional_kwargs={}, response_metadata={}, id='e3b0ee81-ca75-47de-8cbe-d583d8a0836d')]}

state in subgraph 3 node 2: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a'), HumanMessage(content='Inside subgraph 1 entry node', additional_kwargs={}, response_metadata={}, id='6de6cc34-5104-499d-a97e-cd94e238ec60'), HumanMessage(content='Inside subgraph 1 node 2', additional_kwargs={}, response_metadata={}, id='f52650dc-e2d3-4e88-b98b-f6870b6e325c'), HumanMessage(content='Inside subgraph 2 entry node', additional_kwargs={}, response_metadata={}, id='9e95196c-cb2a-450b-b895-96f7ae53edb1'), HumanMessage(content='Inside subgraph 2 node 2', additional_kwargs={}, response_metadata={}, id='e3b0ee81-ca75-47de-8cbe-d583d8a0836d'), HumanMessage(content='Inside subgraph 3 entry node', additional_kwargs={}, response_metadata={}, id='bd4fd0cc-9aab-4cee-86c8-5470e5fb6498')]}
state in main graph node 2: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a'), HumanMessage(content='Inside subgraph 1 entry node', additional_kwargs={}, response_metadata={}, id='6de6cc34-5104-499d-a97e-cd94e238ec60'), HumanMessage(content='Inside subgraph 1 node 2', additional_kwargs={}, response_metadata={}, id='f52650dc-e2d3-4e88-b98b-f6870b6e325c'), HumanMessage(content='Inside subgraph 2 entry node', additional_kwargs={}, response_metadata={}, id='9e95196c-cb2a-450b-b895-96f7ae53edb1'), HumanMessage(content='Inside subgraph 2 node 2', additional_kwargs={}, response_metadata={}, id='e3b0ee81-ca75-47de-8cbe-d583d8a0836d'), HumanMessage(content='Inside subgraph 3 entry node', additional_kwargs={}, response_metadata={}, id='bd4fd0cc-9aab-4cee-86c8-5470e5fb6498'), HumanMessage(content='Inside subgraph 3 node 2', additional_kwargs={}, response_metadata={}, id='e92dca0f-2236-4f3f-b336-e1d6d2f50e09')]}

Parallel execution: Use the Graph API is something that talks about this behavior.

Now for the question does subgraphs have isolated state?

I believe it depends on the implementation, if you have implemented like subgraph1 and subgraph2 above yes they are isolated but for subgraph3 that is not the case because in execution it comes after subgraph1 and subgraph2 execution so both of those updated the state and subgraph3 received the updated state

state in subgraph 3 entry node: {'messages': [HumanMessage(content='Start the process with Hello in main graph entry node', additional_kwargs={}, response_metadata={}, id='8895c3bd-b051-47ab-8945-71b0cbc64abf'), HumanMessage(content='Inside main graph entry node', additional_kwargs={}, response_metadata={}, id='18d8b08c-15a0-4ace-b0e0-1097a6ecac3a'), HumanMessage(content='Inside subgraph 1 entry node', additional_kwargs={}, response_metadata={}, id='6de6cc34-5104-499d-a97e-cd94e238ec60'), HumanMessage(content='Inside subgraph 1 node 2', additional_kwargs={}, response_metadata={}, id='f52650dc-e2d3-4e88-b98b-f6870b6e325c'), HumanMessage(content='Inside subgraph 2 entry node', additional_kwargs={}, response_metadata={}, id='9e95196c-cb2a-450b-b895-96f7ae53edb1'), HumanMessage(content='Inside subgraph 2 node 2', additional_kwargs={}, response_metadata={}, id='e3b0ee81-ca75-47de-8cbe-d583d8a0836d')]}

Hope this answers your question…

1 Like