Large LangGraph applications become easier to reason about when a group of nodes has its own state contract. A compiled subgraph can occupy one node in a parent StateGraph, so the parent delegates that section while keeping the surrounding route explicit.

Direct composition works when the parent and child schemas share the keys that cross their boundary. The child can also define private keys for its internal nodes; those keys stay out of the parent's returned state when the parent schema does not include them.

The model-free program uses request and result as the shared interface, while normalized exists only inside the child. Its printed node order and final dictionary show that execution returns to the parent after the child updates the shared result.

Steps to add a LangGraph subgraph to a parent graph:

  1. Create subgraph_demo.py with the parent and subgraph state schemas.
    subgraph_demo.py
    from typing_extensions import TypedDict
     
    from langgraph.graph import END, START, StateGraph
     
     
    class ParentState(TypedDict):
        request: str
        result: str
     
     
    class SubgraphState(TypedDict):
        request: str
        result: str
        normalized: str
  2. Add the child node functions below the state schemas.
    def normalize_request(state: SubgraphState) -> dict:
        print("subgraph: normalize")
        return {"normalized": state["request"].upper()}
     
     
    def approve_request(state: SubgraphState) -> dict:
        print("subgraph: approve")
        return {"result": f'{state["normalized"]}: approved'}
  3. Build the compiled review subgraph below its node functions.
    subgraph_builder = StateGraph(SubgraphState)
    subgraph_builder.add_node("normalize", normalize_request)
    subgraph_builder.add_node("approve", approve_request)
    subgraph_builder.add_edge(START, "normalize")
    subgraph_builder.add_edge("normalize", "approve")
    subgraph_builder.add_edge("approve", END)
    review_subgraph = subgraph_builder.compile()
  4. Add the parent node functions below the compiled subgraph.
    def prepare_request(state: ParentState) -> dict:
        print("parent: prepare")
        return {"request": state["request"].strip()}
     
     
    def finish_request(state: ParentState) -> dict:
        print(f'parent: finish ({state["result"]})')
        return {}
  5. Build the parent graph with the compiled subgraph registered as its review node.
    parent_builder = StateGraph(ParentState)
    parent_builder.add_node("prepare", prepare_request)
    parent_builder.add_node("review", review_subgraph)
    parent_builder.add_node("finish", finish_request)
    parent_builder.add_edge(START, "prepare")
    parent_builder.add_edge("prepare", "review")
    parent_builder.add_edge("review", "finish")
    parent_builder.add_edge("finish", END)
    parent_graph = parent_builder.compile()

    The compiled child can be passed directly to add_node() because request and result exist in both state schemas. A wrapper node is required when the schemas need input or output transformation.

  6. Append the parent invocation below the compiled parent graph.
    final_state = parent_graph.invoke({"request": "  order-42 ", "result": ""})
    print(final_state)
  7. Run the completed program to confirm the subgraph-to-parent state handoff.
    $ python subgraph_demo.py
    parent: prepare
    subgraph: normalize
    subgraph: approve
    parent: finish (ORDER-42: approved)
    {'request': 'order-42', 'result': 'ORDER-42: approved'}