Skip to content

Subgraph (using interrupt) restarts instead of resuming from internal breakpoint #4796

Description

@michalgala

Checked other resources

  • This is a bug, not a usage question. For questions, please use GitHub Discussions.
  • I added a clear and detailed title that summarizes the issue.
  • I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
  • I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.

Example Code

from typing import TypedDict, Optional
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver


class State(TypedDict):
    """The graph state from the example using interrupt()"""
    state_counter: int
    human_response: Optional[str]

counter_node_in_subgraph = 0
counter_human_node = 0

def node_in_subgraph(state: State):
    """A node in the sub-graph."""
    global counter_node_in_subgraph
    counter_node_in_subgraph += 1
    print(f"SUBGRAPH: Entered `node_in_subgraph` a total of {counter_node_in_subgraph} times")
    answer = state.get("human_response","None")
    print(f"SUBGRAPH: `node_in_subgraph` has human reponse: '{answer}'")
    return {}

def human_node(state: State):
    """This node would process human input if it ran."""
    global counter_human_node
    counter_human_node += 1
    print(f"SUBGRAPH: Entered `human_node` in sub-graph a total of {counter_human_node} times")
    answer = state.get("human_response")
    print(f"SUBGRAPH: `human_node` got an answer of '{answer}' from state.")
    return {}

checkpointer = MemorySaver()

subgraph_builder = StateGraph(State)
subgraph_builder.add_node("some_node", node_in_subgraph)
subgraph_builder.add_node("human_node", human_node)
subgraph_builder.add_edge(START, "some_node")
subgraph_builder.add_edge("some_node", "human_node")
subgraph_builder.add_edge("human_node", END)

subgraph = subgraph_builder.compile(
    checkpointer=checkpointer,
    interrupt_before=["human_node"]
)

counter_parent_node = 0

def parent_node(state: State, config: dict): # Added config
    """This parent node will invoke the subgraph."""
    global counter_parent_node
    counter_parent_node += 1
    print(f"PARENT: Entered `parent_node` a total of {counter_parent_node} times")
    print(f"  PARENT: state before subgraph.invoke: {state}")

    subgraph_output_state = subgraph.invoke(state, config=config)

    print(f"  PARENT: subgraph_output_state: {subgraph_output_state}")
    # The parent node returns the full state output from the subgraph.
    return subgraph_output_state


builder = StateGraph(State)
builder.add_node("parent_node", parent_node)
builder.add_edge(START, "parent_node")
builder.add_edge("parent_node", END)

graph = builder.compile(checkpointer=checkpointer)
print("Main graph compiled.")

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

# Initial run - should interrupt before subgraph's 'human_node'
print("\n--- Initial Run (expecting interrupt) ---")
initial_input = State(state_counter=1, human_response=None) # type: ignore
for chunk in graph.stream(initial_input, config, subgraphs=True):
    print(f"CHUNK Initial: {chunk}")

print(f"\n--- Graph interrupted. Current state (Main Graph): {graph.get_state(config).values} ---")
print(f"--- Next node in Main Graph: {graph.get_state(config).next} ---")
print(f"--- Next node in Sub Graph: {graph.get_state(config, subgraphs=True).tasks[0].state.next} ---")


print('\n--- Resuming ---')
hil_response_value = "35" # The input the human provides

# Update state with the HIL response
graph.update_state(
    config,
    State(state_counter=graph.get_state(config).values["state_counter"],
          human_response=hil_response_value) # type: ignore
)
print(f"State updated with human_response: {hil_response_value}")
print(f"  Current state (Main Graph) before resume stream: {graph.get_state(config).values}")


# Resume by streaming with None as input
for chunk in graph.stream(None, config, subgraphs=True):
    print(f"CHUNK Resumed: {chunk}")

print(f"\n--- Final State (Main Graph): {graph.get_state(config).values} ---")
print(f"--- Final counter_node_in_subgraph: {counter_node_in_subgraph} ---")
print(f"--- Final counter_human_node: {counter_human_node} ---")
print(f"--- Final counter_parent_node: {counter_parent_node} ---")

Error Message and Stack Trace (if applicable)

Main graph compiled.

--- Initial Run (expecting interrupt) ---
PARENT: Entered `parent_node` a total of 1 times
  PARENT: state before subgraph.invoke: {'state_counter': 1, 'human_response': None}
SUBGRAPH: Entered `node_in_subgraph` a total of 1 times
SUBGRAPH: `node_in_subgraph` has human reponse: 'None'
CHUNK Initial: (('parent_node:34b0ebce-0f4f-349e-2e37-3278639278b7',), {'some_node': None})
CHUNK Initial: ((), {'__interrupt__': ()})

--- Graph interrupted. Current state (Main Graph): {'state_counter': 1, 'human_response': None} ---
--- Next node in Main Graph: ('parent_node',) ---
--- Next node in Sub Graph: ('human_node',) ---

--- Resuming ---
State updated with human_response: 35
  Current state (Main Graph) before resume stream: {'state_counter': 1, 'human_response': '35'}
PARENT: Entered `parent_node` a total of 2 times
  PARENT: state before subgraph.invoke: {'state_counter': 1, 'human_response': '35'}
SUBGRAPH: Entered `node_in_subgraph` a total of 2 times
SUBGRAPH: `node_in_subgraph` has human reponse: '35'
CHUNK Resumed: (('parent_node:e104621c-052e-bf14-5fa0-e956f82d4312',), {'some_node': None})
CHUNK Resumed: ((), {'__interrupt__': ()})

--- Final State (Main Graph): {'state_counter': 1, 'human_response': '35'} ---
--- Final counter_node_in_subgraph: 2 ---
--- Final counter_human_node: 0 ---
--- Final counter_parent_node: 2 ---

Description

I have recreated the example from the docs (https://langchain-ai.github.io/langgraph/how-tos/human_in_the_loop/add-human-in-the-loop/#subgraphs-called-as-functions).
But instead of using interrupt and Command to resume, i am using interrupt_before that i define at the subgraph compilation.

The expected behavior, based on documentation for nested checkpoints and interruptible subgraphs, is that if the subgraph interrupts (e.g., before its human_node), the parent graph's calling node (parent_node) will re-execute upon resuming the main graph. When this re-executing parent_node calls the subgraph again, the subgraph should use its own checkpoint to resume from its internal breakpoint (i.e., start executing its human_node).

However, as you can see in the output, the subgraph is restarting from the entry point node, despite its checkpoint correctly indicating next=('human_node',).
This leads to some_node executing twice and human_node (the HIL target) never executing, effectively breaking the HIL flow within the subgraph.

System Info

System Information

OS: Darwin
OS Version: Darwin Kernel Version 23.6.0: Fri Jul 5 17:55:37 PDT 2024; root:xnu-10063.141.1~2/RELEASE_ARM64_T6030
Python Version: 3.12.2 (main, Mar 7 2024, 22:00:34) [Clang 15.0.0 (clang-1500.1.0.2.5)]

Package Information

langchain_core: 0.3.47
langchain: 0.3.20
langchain_community: 0.3.19
langsmith: 0.3.13
langchain_experimental: 0.3.4
langchain_openai: 0.3.9
langchain_text_splitters: 0.3.7
langgraph_api: 0.2.27
langgraph_cli: 0.2.10
langgraph_license: Installed. No version info available.
langgraph_runtime: Installed. No version info available.
langgraph_runtime_inmem: 0.0.11
langgraph_sdk: 0.1.69
langgraph_supervisor: 0.0.20

Optional packages not installed

langserve

Other Dependencies

aiohttp<4.0.0,>=3.8.3: Installed. No version info available.
async-timeout<5.0.0,>=4.0.0;: Installed. No version info available.
blockbuster: 1.5.24
click: 8.1.8
cloudpickle: 3.1.1
cryptography: 43.0.3
dataclasses-json<0.7,>=0.5.7: Installed. No version info available.
httpx: 0.28.1
httpx-sse<1.0.0,>=0.4.0: Installed. No version info available.
jsonpatch<2.0,>=1.33: Installed. No version info available.
jsonschema-rs: 0.29.1
langchain-anthropic;: Installed. No version info available.
langchain-aws;: Installed. No version info available.
langchain-cohere;: Installed. No version info available.
langchain-community;: Installed. No version info available.
langchain-core<0.4.0,>=0.3.40: Installed. No version info available.
langchain-core<1.0.0,>=0.3.41: Installed. No version info available.
langchain-core<1.0.0,>=0.3.45: Installed. No version info available.
langchain-deepseek;: Installed. No version info available.
langchain-fireworks;: Installed. No version info available.
langchain-google-genai;: Installed. No version info available.
langchain-google-vertexai;: Installed. No version info available.
langchain-groq;: Installed. No version info available.
langchain-huggingface;: Installed. No version info available.
langchain-mistralai;: Installed. No version info available.
langchain-ollama;: Installed. No version info available.
langchain-openai;: Installed. No version info available.
langchain-text-splitters<1.0.0,>=0.3.6: Installed. No version info available.
langchain-together;: Installed. No version info available.
langchain-xai;: Installed. No version info available.
langchain<1.0.0,>=0.3.20: Installed. No version info available.
langgraph: 0.4.5
langgraph-checkpoint: 2.0.26
langgraph-prebuilt<0.2.0,>=0.1.7: Installed. No version info available.
langgraph>=0.3.5: Installed. No version info available.
langsmith-pyo3: Installed. No version info available.
langsmith<0.4,>=0.1.125: Installed. No version info available.
langsmith<0.4,>=0.1.17: Installed. No version info available.
numpy<3,>=1.26.2: Installed. No version info available.
openai<2.0.0,>=1.66.3: Installed. No version info available.
orjson: 3.10.15
packaging: 24.2
packaging<25,>=23.2: Installed. No version info available.
pydantic: 2.10.6
pydantic-settings<3.0.0,>=2.4.0: Installed. No version info available.
pydantic<3.0.0,>=2.5.2;: Installed. No version info available.
pydantic<3.0.0,>=2.7.4: Installed. No version info available.
pydantic<3.0.0,>=2.7.4;: Installed. No version info available.
pyjwt: 2.10.1
pytest: Installed. No version info available.
python-dotenv: 1.0.1
PyYAML>=5.3: Installed. No version info available.
requests: 2.32.3
requests-toolbelt: 1.0.0
requests<3,>=2: Installed. No version info available.
rich: Installed. No version info available.
SQLAlchemy<3,>=1.4: Installed. No version info available.
sse-starlette: 2.1.3
starlette: 0.41.3
structlog: 25.2.0
tenacity: 8.5.0
tenacity!=8.4.0,<10,>=8.1.0: Installed. No version info available.
tenacity!=8.4.0,<10.0.0,>=8.1.0: Installed. No version info available.
tiktoken<1,>=0.7: Installed. No version info available.
truststore: 0.10.1
typing-extensions>=4.7: Installed. No version info available.
watchfiles: 0.20.0
zstandard: 0.23.0

Metadata

Metadata

Labels

duplicateThis issue or pull request already exists

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions