> ## Documentation Index
> Fetch the complete documentation index at: https://langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Subgraphs

This guide explains the mechanics of using subgraphs. A subgraph is a [graph](/oss/javascript/langgraph/graph-api#graphs) that is used as a [node](/oss/javascript/langgraph/graph-api#nodes) in another graph.

Subgraphs are useful for:

* Building [multi-agent systems](/oss/javascript/langchain/multi-agent)
* Re-using a set of nodes in multiple graphs
* Distributing development: when you want different teams to work on different parts of the graph independently, you can define each part as a subgraph, and as long as the subgraph interface (the input and output schemas) is respected, the parent graph can be built without knowing any details of the subgraph

## Setup

```bash theme={null}
npm install @langchain/langgraph
```

<Tip>
  **Set up LangSmith for LangGraph development**
  Sign up for [LangSmith](https://smith.langchain.com) to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started [here](https://docs.smith.langchain.com).
</Tip>

## Define subgraph communication

When adding subgraphs, you need to define how the parent graph and the subgraph communicate:

| Pattern                                                         | When to use                                                                                                        | State schemas                                                                                                  |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| [Call a subgraph inside a node](#call-a-subgraph-inside-a-node) | Parent and subgraph have **different state schemas** (no shared keys), or you need to transform state between them | You write a wrapper function that maps parent state to subgraph input and subgraph output back to parent state |
| [Add a subgraph as a node](#add-a-subgraph-as-a-node)           | Parent and subgraph **share state keys** — the subgraph reads from and writes to the same channels as the parent   | You pass the compiled subgraph directly to `add_node` — no wrapper function needed                             |

<a id="invoke-a-graph-from-a-node" />

### Call a subgraph inside a node

When the parent graph and subgraph have **different state schemas** (no shared keys), invoke the subgraph inside a node function. This is common when you want to keep a private message history for each agent in a [multi-agent](/oss/javascript/langchain/multi-agent) system.

The node function transforms the parent state to the subgraph state before invoking the subgraph, and transforms the results back to the parent state before returning.

```typescript theme={null}
import { StateGraph, StateSchema, START } from "@langchain/langgraph";
import * as z from "zod";

const SubgraphState = new StateSchema({
  bar: z.string(),
});

// Subgraph
const subgraphBuilder = new StateGraph(SubgraphState)
  .addNode("subgraphNode1", (state) => {
    return { bar: "hi! " + state.bar };
  })
  .addEdge(START, "subgraphNode1");

const subgraph = subgraphBuilder.compile();

// Parent graph
const State = new StateSchema({
  foo: z.string(),
});

// Transform the state to the subgraph state and back
const builder = new StateGraph(State)
  .addNode("node1", async (state) => {
    const subgraphOutput = await subgraph.invoke({ bar: state.foo });
    return { foo: subgraphOutput.bar };
  })
  .addEdge(START, "node1");

const graph = builder.compile();
```

<Accordion title="Full example: different state schemas">
  ```typescript theme={null}
  import { StateGraph, StateSchema, START } from "@langchain/langgraph";
  import * as z from "zod";

  // Define subgraph
  const SubgraphState = new StateSchema({
    // note that none of these keys are shared with the parent graph state
    bar: z.string(),
    baz: z.string(),
  });

  const subgraphBuilder = new StateGraph(SubgraphState)
    .addNode("subgraphNode1", (state) => {
      return { baz: "baz" };
    })
    .addNode("subgraphNode2", (state) => {
      return { bar: state.bar + state.baz };
    })
    .addEdge(START, "subgraphNode1")
    .addEdge("subgraphNode1", "subgraphNode2");

  const subgraph = subgraphBuilder.compile();

  // Define parent graph
  const ParentState = new StateSchema({
    foo: z.string(),
  });

  const builder = new StateGraph(ParentState)
    .addNode("node1", (state) => {
      return { foo: "hi! " + state.foo };
    })
    .addNode("node2", async (state) => {
      const response = await subgraph.invoke({ bar: state.foo });   // [!code highlight]
      return { foo: response.bar };   // [!code highlight]
    })
    .addEdge(START, "node1")
    .addEdge("node1", "node2");

  const graph = builder.compile();

  for await (const chunk of await graph.stream(
    { foo: "foo" },
    { subgraphs: true }
  )) {
    console.log(chunk);
  }
  ```

  1. Transform the state to the subgraph state
  2. Transform response back to the parent state

  ```
  [[], { node1: { foo: 'hi! foo' } }]
  [['node2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7'], { subgraphNode1: { baz: 'baz' } }]
  [['node2:9c36dd0f-151a-cb42-cbad-fa2f851f9ab7'], { subgraphNode2: { bar: 'hi! foobaz' } }]
  [[], { node2: { foo: 'hi! foobaz' } }]
  ```
</Accordion>

<Accordion title="Full example: different state schemas (two levels of subgraphs)">
  This is an example with two levels of subgraphs: parent -> child -> grandchild.

  ```typescript theme={null}
  import { StateGraph, StateSchema, START, END } from "@langchain/langgraph";
  import * as z from "zod";

  // Grandchild graph
  const GrandChildState = new StateSchema({
    myGrandchildKey: z.string(),
  });

  const grandchild = new StateGraph(GrandChildState)
    .addNode("grandchild1", (state) => {
      // NOTE: child or parent keys will not be accessible here
      return { myGrandchildKey: state.myGrandchildKey + ", how are you" };
    })
    .addEdge(START, "grandchild1")
    .addEdge("grandchild1", END);

  const grandchildGraph = grandchild.compile();

  // Child graph
  const ChildState = new StateSchema({
    myChildKey: z.string(),
  });

  const child = new StateGraph(ChildState)
    .addNode("child1", async (state) => {
      // NOTE: parent or grandchild keys won't be accessible here
      const grandchildGraphInput = { myGrandchildKey: state.myChildKey };   // [!code highlight]
      const grandchildGraphOutput = await grandchildGraph.invoke(grandchildGraphInput);
      return { myChildKey: grandchildGraphOutput.myGrandchildKey + " today?" };   // [!code highlight]
    })   // [!code highlight]
    .addEdge(START, "child1")
    .addEdge("child1", END);

  const childGraph = child.compile();

  // Parent graph
  const ParentState = new StateSchema({
    myKey: z.string(),
  });

  const parent = new StateGraph(ParentState)
    .addNode("parent1", (state) => {
      // NOTE: child or grandchild keys won't be accessible here
      return { myKey: "hi " + state.myKey };
    })
    .addNode("child", async (state) => {
      const childGraphInput = { myChildKey: state.myKey };   // [!code highlight]
      const childGraphOutput = await childGraph.invoke(childGraphInput);
      return { myKey: childGraphOutput.myChildKey };   // [!code highlight]
    })   // [!code highlight]
    .addNode("parent2", (state) => {
      return { myKey: state.myKey + " bye!" };
    })
    .addEdge(START, "parent1")
    .addEdge("parent1", "child")
    .addEdge("child", "parent2")
    .addEdge("parent2", END);

  const parentGraph = parent.compile();

  for await (const chunk of await parentGraph.stream(
    { myKey: "Bob" },
    { subgraphs: true }
  )) {
    console.log(chunk);
  }
  ```

  1. We're transforming the state from the child state channels (`myChildKey`) to the grandchild state channels (`myGrandchildKey`)
  2. We're transforming the state from the grandchild state channels (`myGrandchildKey`) back to the child state channels (`myChildKey`)
  3. We're passing a function here instead of just compiled graph (`grandchildGraph`)
  4. We're transforming the state from the parent state channels (`myKey`) to the child state channels (`myChildKey`)
  5. We're transforming the state from the child state channels (`myChildKey`) back to the parent state channels (`myKey`)
  6. We're passing a function here instead of just a compiled graph (`childGraph`)

  ```
  [[], { parent1: { myKey: 'hi Bob' } }]
  [['child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b', 'child1:781bb3b1-3971-84ce-810b-acf819a03f9c'], { grandchild1: { myGrandchildKey: 'hi Bob, how are you' } }]
  [['child:2e26e9ce-602f-862c-aa66-1ea5a4655e3b'], { child1: { myChildKey: 'hi Bob, how are you today?' } }]
  [[], { child: { myKey: 'hi Bob, how are you today?' } }]
  [[], { parent2: { myKey: 'hi Bob, how are you today? bye!' } }]
  ```
</Accordion>

<a id="add-a-graph-as-a-node" />

### Add a subgraph as a node

When the parent graph and subgraph **share state keys**, you can pass a compiled subgraph directly to `add_node`. No wrapper function is needed — the subgraph reads from and writes to the parent's state channels automatically. For example, in [multi-agent](/oss/javascript/langchain/multi-agent) systems, the agents often communicate over a shared [messages](/oss/javascript/langgraph/graph-api#why-use-messages) key.

<img src="https://mintcdn.com/langchain-5e9cc07a-preview-srimpr-1771619406-31dcf4f/oMGsdYK8fNjthjqx/oss/images/subgraph.png?fit=max&auto=format&n=oMGsdYK8fNjthjqx&q=85&s=19fd39c19995257c7f8c7b09ef4de268" alt="SQL agent graph" style={{ height: "450px" }} width="1177" height="818" data-path="oss/images/subgraph.png" />

If your subgraph shares state keys with the parent graph, you can follow these steps to add it to your graph:

1. Define the subgraph workflow (`subgraphBuilder` in the example below) and compile it
2. Pass compiled subgraph to the `.addNode` method when defining the parent graph workflow

```typescript theme={null}
import { StateGraph, StateSchema, START } from "@langchain/langgraph";
import * as z from "zod";

const State = new StateSchema({
  foo: z.string(),
});

// Subgraph
const subgraphBuilder = new StateGraph(State)
  .addNode("subgraphNode1", (state) => {
    return { foo: "hi! " + state.foo };
  })
  .addEdge(START, "subgraphNode1");

const subgraph = subgraphBuilder.compile();

// Parent graph
const builder = new StateGraph(State)
  .addNode("node1", subgraph)
  .addEdge(START, "node1");

const graph = builder.compile();
```

<Accordion title="Full example: shared state schemas">
  ```typescript theme={null}
  import { StateGraph, StateSchema, START } from "@langchain/langgraph";
  import * as z from "zod";

  // Define subgraph
  const SubgraphState = new StateSchema({
    foo: z.string(),    // [!code highlight]
    bar: z.string(),    // [!code highlight]
  });

  const subgraphBuilder = new StateGraph(SubgraphState)
    .addNode("subgraphNode1", (state) => {
      return { bar: "bar" };
    })
    .addNode("subgraphNode2", (state) => {
      // note that this node is using a state key ('bar') that is only available in the subgraph
      // and is sending update on the shared state key ('foo')
      return { foo: state.foo + state.bar };
    })
    .addEdge(START, "subgraphNode1")
    .addEdge("subgraphNode1", "subgraphNode2");

  const subgraph = subgraphBuilder.compile();

  // Define parent graph
  const ParentState = new StateSchema({
    foo: z.string(),
  });

  const builder = new StateGraph(ParentState)
    .addNode("node1", (state) => {
      return { foo: "hi! " + state.foo };
    })
    .addNode("node2", subgraph)
    .addEdge(START, "node1")
    .addEdge("node1", "node2");

  const graph = builder.compile();

  for await (const chunk of await graph.stream({ foo: "foo" })) {
    console.log(chunk);
  }
  ```

  1. This key is shared with the parent graph state
  2. This key is private to the `SubgraphState` and is not visible to the parent graph

  ```
  { node1: { foo: 'hi! foo' } }
  { node2: { foo: 'hi! foobar' } }
  ```
</Accordion>

## Subgraph persistence

By default, subgraphs start fresh on every invocation — they have no memory of previous calls. This is the right choice for most applications, including [multi-agent](/oss/javascript/langchain/multi-agent) systems where subagents are invoked as tools.

If a subagent needs to remember previous conversations (multi-turn history), you can enable stateful persistence so state accumulates across invocations on the same thread.

<Note>
  The parent graph must be compiled with a checkpointer for subgraph persistence features (interrupts, state inspection, stateful memory) to work. See [persistence](/oss/javascript/langgraph/persistence).
</Note>

### Stateless

Both `checkpointer=False` and `checkpointer=None` produce stateless behavior — each invocation starts fresh with no memory of previous calls. The difference is whether the subgraph supports [interrupts](/oss/javascript/langgraph/interrupts) within a single invocation.

#### Without interrupts

Compile with `checkpointer=False` to opt out of checkpointing entirely. No checkpoints are written for the subgraph. The subgraph cannot use `interrupt()` and its state is not inspectable via `get_state`. This is the lightest-weight option.

```typescript theme={null}
const subgraphBuilder = new StateGraph(...);
const subgraph = subgraphBuilder.compile({ checkpointer: false });  // [!code highlight]
```

#### With interrupts

<Tip>
  This is the recommended mode for most applications, including [multi-agent](/oss/javascript/langchain/multi-agent) systems where subagents are invoked as tools. It supports interrupts and parallel calls while keeping each invocation isolated.
</Tip>

The default behavior when you omit `checkpointer` or set it to `None`. The subgraph inherits the parent's checkpointer and each invocation gets a unique checkpoint namespace. Within a single invocation, the subgraph can use `interrupt()` to pause and resume.

The following examples use two subagents (fruit expert, veggie expert) wrapped as tools for an outer agent:

```typescript theme={null}
import { createAgent, tool } from "langchain";
import { MemorySaver, Command, interrupt } from "@langchain/langgraph";
import * as z from "zod";

const fruitInfo = tool(
  (input) => `Info about ${input.fruitName}`,
  {
    name: "fruit_info",
    description: "Look up fruit info.",
    schema: z.object({ fruitName: z.string() }),
  }
);

const veggieInfo = tool(
  (input) => `Info about ${input.veggieName}`,
  {
    name: "veggie_info",
    description: "Look up veggie info.",
    schema: z.object({ veggieName: z.string() }),
  }
);

// Subagents — no checkpointer setting (inherits parent)
const fruitAgent = createAgent({
  model: "gpt-4.1-mini",
  tools: [fruitInfo],
  prompt: "You are a fruit expert. Use the fruit_info tool. Respond in one sentence.",
});

const veggieAgent = createAgent({
  model: "gpt-4.1-mini",
  tools: [veggieInfo],
  prompt: "You are a veggie expert. Use the veggie_info tool. Respond in one sentence.",
});

// Wrap subagents as tools for the outer agent
const askFruitExpert = tool(
  async (input) => {
    const response = await fruitAgent.invoke({
      messages: [{ role: "user", content: input.question }],
    });
    return response.messages[response.messages.length - 1].content;
  },
  {
    name: "ask_fruit_expert",
    description: "Ask the fruit expert. Use for ALL fruit questions.",
    schema: z.object({ question: z.string() }),
  }
);

const askVeggieExpert = tool(
  async (input) => {
    const response = await veggieAgent.invoke({
      messages: [{ role: "user", content: input.question }],
    });
    return response.messages[response.messages.length - 1].content;
  },
  {
    name: "ask_veggie_expert",
    description: "Ask the veggie expert. Use for ALL veggie questions.",
    schema: z.object({ question: z.string() }),
  }
);

// Outer agent with checkpointer
const agent = createAgent({
  model: "gpt-4.1-mini",
  tools: [askFruitExpert, askVeggieExpert],
  prompt:
    "You have two experts: ask_fruit_expert and ask_veggie_expert. " +
    "ALWAYS delegate questions to the appropriate expert.",
  checkpointer: new MemorySaver(),
});
```

<Tabs>
  <Tab title="Interrupts">
    Each invocation can use `interrupt()` to pause and resume. Add `interrupt()` to a tool function to require user approval before proceeding:

    ```typescript theme={null}
    const fruitInfo = tool(
      (input) => {
        interrupt("continue?");  // [!code highlight]
        return `Info about ${input.fruitName}`;
      },
      {
        name: "fruit_info",
        description: "Look up fruit info.",
        schema: z.object({ fruitName: z.string() }),
      }
    );
    ```

    ```typescript theme={null}
    const config = { configurable: { thread_id: "1" } };

    // Invoke — the subagent's tool calls interrupt()
    let response = await agent.invoke(
      { messages: [{ role: "user", content: "Tell me about apples" }] },
      config,
    );
    // response contains __interrupt__

    // Resume — approve the interrupt
    response = await agent.invoke(new Command({ resume: true }), config);  // [!code highlight]
    // Subagent message count: 4
    ```
  </Tab>

  <Tab title="Multi-turn">
    Each invocation starts with a fresh subagent state. The subagent does not remember previous calls:

    ```typescript theme={null}
    const config = { configurable: { thread_id: "1" } };

    // First call
    let response = await agent.invoke(
      { messages: [{ role: "user", content: "Tell me about apples" }] },
      config,
    );
    // Subagent message count: 4

    // Second call — subagent starts fresh, no memory of apples
    response = await agent.invoke(
      { messages: [{ role: "user", content: "Now tell me about bananas" }] },
      config,
    );
    // Subagent message count: 4 (still fresh!)
    ```
  </Tab>

  <Tab title="Multiple subgraph calls">
    Multiple calls to the same subgraph work without conflicts, since each invocation gets its own checkpoint namespace:

    ```typescript theme={null}
    const config = { configurable: { thread_id: "1" } };

    // LLM calls ask_fruit_expert for both apples and bananas
    const response = await agent.invoke(
      { messages: [{ role: "user", content: "Tell me about apples and bananas" }] },
      config,
    );
    // Subagent message count: 4 (apples — fresh)
    // Subagent message count: 4 (bananas — fresh)
    ```
  </Tab>
</Tabs>

### Stateful

Compile with `checkpointer=True` to enable state that accumulates across invocations on the same thread. LangGraph strips task IDs from the checkpoint namespace (turning `node:task_id|subnode:subtask_id` into `node|subnode`), so the subgraph writes to the same namespace every time. The subgraph "remembers" previous calls — use this when a subagent needs multi-turn conversation history.

The following examples use a single fruit expert subagent compiled with `checkpointer=True`:

```typescript theme={null}
import { createAgent, tool } from "langchain";
import { MemorySaver, Command, interrupt } from "@langchain/langgraph";
import * as z from "zod";

const fruitInfo = tool(
  (input) => `Info about ${input.fruitName}`,
  {
    name: "fruit_info",
    description: "Look up fruit info.",
    schema: z.object({ fruitName: z.string() }),
  }
);

// Subagent with checkpointer=true for persistent state
const fruitAgent = createAgent({
  model: "gpt-4.1-mini",
  tools: [fruitInfo],
  prompt: "You are a fruit expert. Use the fruit_info tool. Respond in one sentence.",
  checkpointer: true,  // [!code highlight]
});

// Wrap subagent as a tool for the outer agent
const askFruitExpert = tool(
  async (input) => {
    const response = await fruitAgent.invoke({
      messages: [{ role: "user", content: input.question }],
    });
    return response.messages[response.messages.length - 1].content;
  },
  {
    name: "ask_fruit_expert",
    description: "Ask the fruit expert. Use for ALL fruit questions.",
    schema: z.object({ question: z.string() }),
  }
);

// Outer agent with checkpointer
const agent = createAgent({
  model: "gpt-4.1-mini",
  tools: [askFruitExpert],
  prompt: "You have a fruit expert. ALWAYS delegate fruit questions to ask_fruit_expert.",
  checkpointer: new MemorySaver(),
});
```

<Tabs>
  <Tab title="Interrupts">
    Stateful subagents support `interrupt()` just like per-invocation. Add `interrupt()` to a tool function to require user approval:

    ```typescript theme={null}
    const fruitInfo = tool(
      (input) => {
        interrupt("continue?");  // [!code highlight]
        return `Info about ${input.fruitName}`;
      },
      {
        name: "fruit_info",
        description: "Look up fruit info.",
        schema: z.object({ fruitName: z.string() }),
      }
    );
    ```

    ```typescript theme={null}
    const config = { configurable: { thread_id: "1" } };

    // Invoke — the subagent's tool calls interrupt()
    let response = await agent.invoke(
      { messages: [{ role: "user", content: "Tell me about apples" }] },
      config,
    );
    // response contains __interrupt__

    // Resume — approve the interrupt
    response = await agent.invoke(new Command({ resume: true }), config);  // [!code highlight]
    // Subagent message count: 4
    ```
  </Tab>

  <Tab title="Multi-turn">
    State accumulates across invocations — the subagent remembers past conversations:

    ```typescript theme={null}
    const config = { configurable: { thread_id: "1" } };

    // First call
    let response = await agent.invoke(
      { messages: [{ role: "user", content: "Tell me about apples" }] },
      config,
    );
    // Subagent message count: 4

    // Second call — subagent REMEMBERS apples conversation
    response = await agent.invoke(
      { messages: [{ role: "user", content: "Now tell me about bananas" }] },
      config,
    );
    // Subagent message count: 8 (accumulated!)
    ```
  </Tab>

  <Tab title="Multiple subgraph calls">
    Multiple calls to different stateful subgraphs from a single node require [namespace isolation](#parallel-subgraphs) — wrap each agent in a `StateGraph` with a unique node name:

    ```typescript theme={null}
    import { StateGraph, StateSchema, MessagesValue, START } from "@langchain/langgraph";

    function createSubAgent(model: string, { name, ...kwargs }: { name: string; [key: string]: any }) {  // [!code highlight]
      const agent = createAgent({ model, name, ...kwargs });  // [!code highlight]
      return new StateGraph(new StateSchema({ messages: MessagesValue }))  // [!code highlight]
        .addNode(name, agent)  // [!code highlight]
        .addEdge(START, name)  // [!code highlight]
        .compile();  // [!code highlight]
    }  // [!code highlight]

    const fruitAgent = createSubAgent("gpt-4.1-mini", {
      name: "fruit_agent", tools: [fruitInfo], prompt: "...", checkpointer: true,
    });
    const veggieAgent = createSubAgent("gpt-4.1-mini", {
      name: "veggie_agent", tools: [veggieInfo], prompt: "...", checkpointer: true,
    });
    ```

    Each agent accumulates state independently:

    ```typescript theme={null}
    const config = { configurable: { thread_id: "1" } };

    // First call — LLM calls both fruit and veggie experts
    let response = await agent.invoke(
      { messages: [{ role: "user", content: "Tell me about cherries and broccoli" }] },
      config,
    );
    // Fruit subagent message count: 4
    // Veggie subagent message count: 4

    // Second call — both agents accumulate independently
    response = await agent.invoke(
      { messages: [{ role: "user", content: "Now tell me about oranges and carrots" }] },
      config,
    );
    // Fruit subagent message count: 8 (remembers cherries!)
    // Veggie subagent message count: 8 (remembers broccoli!)
    ```
  </Tab>
</Tabs>

<Warning>
  Multiple calls to the **same** subgraph with stateful persistence cause namespace conflicts — both calls read from and write to the same checkpoint, corrupting state. Use [stateless with interrupts](#with-interrupts) persistence instead when you need multiple calls to the same subgraph.
</Warning>

<a id="parallel-subgraphs" />

<Accordion title="Namespace isolation for parallel stateful subgraphs">
  When multiple subgraphs are [called inside a node](#call-a-subgraph-inside-a-node) with `checkpointer=True`, LangGraph assigns each invocation a **position-based namespace suffix**. The first invocation gets the base namespace (e.g., `calling_node`), the second gets `calling_node|1`, and so on. State persists per **call position** — if you reorder calls, each subgraph may load the wrong state.

  Subgraphs [added as nodes](#add-a-subgraph-as-a-node) do not have this limitation because each node has a unique name that becomes part of the namespace.

  | Subgraph pattern                                       | Namespace with `checkpointer=True`     | Isolation                        |
  | ------------------------------------------------------ | -------------------------------------- | -------------------------------- |
  | [Added as a node](#add-a-subgraph-as-a-node)           | `node_name\|internal_node`             | Name-based (stable)              |
  | [Called inside a node](#call-a-subgraph-inside-a-node) | `calling_node`, `calling_node\|1`, ... | Position-based (order-dependent) |

  For stable, name-based namespaces, wrap each subgraph in its own `StateGraph` with a unique node name. Each wrapper must be a **different** compiled subgraph instance — you cannot invoke the same instance twice in a single node.

  ```typescript theme={null}
  import { createAgent } from "langchain";
  import { StateGraph, StateSchema, MessagesValue, START } from "@langchain/langgraph";

  function createSubAgent(model: string, { name, ...kwargs }: { name: string; [key: string]: any }) {
    const agent = createAgent({ model, name, ...kwargs });
    return new StateGraph(new StateSchema({ messages: MessagesValue }))
      .addNode(name, agent)  // [!code highlight]
      .addEdge(START, name)
      .compile();
  }

  const fruitAgent = createSubAgent("gpt-4.1-mini", { name: "fruit_agent", tools: [...], checkpointer: true });
  const veggieAgent = createSubAgent("gpt-4.1-mini", { name: "veggie_agent", tools: [...], checkpointer: true });
  ```
</Accordion>

### Checkpointer reference

Control subgraph persistence with the `checkpointer` parameter on `.compile()`:

```typescript theme={null}
const subgraph = builder.compile({ checkpointer: false });  // or true, or null
```

| Feature                              | Without interrupts | With interrupts (default)                                                                                                                                                                                                                                | Stateful                                                                                                                                    |
| ------------------------------------ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `checkpointer=`                      | `False`            | `None`                                                                                                                                                                                                                                                   | `True`                                                                                                                                      |
| Interrupts (HITL)                    | ❌                  | ✅                                                                                                                                                                                                                                                        | ✅                                                                                                                                           |
| Multi-turn memory                    | ❌                  | ❌                                                                                                                                                                                                                                                        | ✅                                                                                                                                           |
| Multiple calls (different subgraphs) | ✅                  | ✅                                                                                                                                                                                                                                                        | <Tooltip tip="Calls to multiple stateful subgraphs in the same node can cause namespace conflicts. Workarounds are available.">⚠️</Tooltip> |
| Multiple calls (same subgraph)       | ✅                  | ✅                                                                                                                                                                                                                                                        | ❌                                                                                                                                           |
| State inspection                     | ❌                  | <Tooltip tip="State inspection with per-invocation persistence is available for the current invocation only (while interrupted). Each invocation starts fresh, so there is no accumulated state to inspect after the invocation completes.">⚠️</Tooltip> | ✅                                                                                                                                           |

* **Interrupts (HITL)**: The subgraph can use [interrupt()](/oss/javascript/langgraph/interrupts) to pause execution and wait for user input, then resume where it left off.
* **Multi-turn memory**: The subgraph retains its state across multiple invocations within the same [thread](/oss/javascript/langgraph/persistence#threads). Each call picks up where the last one left off rather than starting fresh.
* **Multiple calls (different subgraphs)**: Multiple different subgraph instances can be invoked within a single node without checkpoint namespace conflicts.
* **Multiple calls (same subgraph)**: The same subgraph instance can be invoked multiple times within a single node. With stateful persistence, these calls write to the same checkpoint namespace and conflict — use per-invocation persistence instead.
* **State inspection**: The subgraph's state is available via `get_state(config, subgraphs=True)` for debugging and monitoring.

## View subgraph state

When you enable [persistence](/oss/javascript/langgraph/persistence), you can inspect the subgraph state using the subgraphs option. With `checkpointer=False`, no subgraph checkpoints are saved, so subgraph state is not available.

<Note>
  Viewing subgraph state requires that LangGraph can **statically discover** the subgraph — i.e., it is [added as a node](#add-a-subgraph-as-a-node) or [called inside a node](#call-a-subgraph-inside-a-node). It does not work when a subgraph is called inside a [tool](/oss/javascript/langchain/tools) function or other indirection (e.g., the [subagents](/oss/javascript/langchain/multi-agent/subagents) pattern). Interrupts still propagate to the top-level graph regardless of nesting.
</Note>

<Tabs>
  <Tab title="Stateless">
    Returns subgraph state for the **current invocation only**. Each invocation starts fresh.

    ```typescript theme={null}
    import { StateGraph, StateSchema, START, MemorySaver, interrupt, Command } from "@langchain/langgraph";
    import * as z from "zod";

    const State = new StateSchema({
      foo: z.string(),
    });

    // Subgraph
    const subgraphBuilder = new StateGraph(State)
      .addNode("subgraphNode1", (state) => {
        const value = interrupt("Provide value:");
        return { foo: state.foo + value };
      })
      .addEdge(START, "subgraphNode1");

    const subgraph = subgraphBuilder.compile();  // inherits parent checkpointer

    // Parent graph
    const builder = new StateGraph(State)
      .addNode("node1", subgraph)
      .addEdge(START, "node1");

    const checkpointer = new MemorySaver();
    const graph = builder.compile({ checkpointer });

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

    await graph.invoke({ foo: "" }, config);

    // View subgraph state for the current invocation
    const subgraphState = (await graph.getState(config, { subgraphs: true })).tasks[0].state;  // [!code highlight]

    // Resume the subgraph
    await graph.invoke(new Command({ resume: "bar" }), config);
    ```
  </Tab>

  <Tab title="Stateful">
    Returns **accumulated** subgraph state across all invocations on this thread.

    ```typescript theme={null}
    import { StateGraph, StateSchema, MessagesValue, START, MemorySaver } from "@langchain/langgraph";

    // Subgraph with its own persistent state
    const SubgraphState = new StateSchema({
      messages: MessagesValue,
    });

    const subgraphBuilder = new StateGraph(SubgraphState);
    // ... add nodes and edges
    const subgraph = subgraphBuilder.compile({ checkpointer: true });  // [!code highlight]

    // Parent graph
    const builder = new StateGraph(SubgraphState)
      .addNode("agent", subgraph)
      .addEdge(START, "agent");

    const checkpointer = new MemorySaver();
    const graph = builder.compile({ checkpointer });

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

    await graph.invoke({ messages: [{ role: "user", content: "hi" }] }, config);
    await graph.invoke({ messages: [{ role: "user", content: "what did I say?" }] }, config);

    // View accumulated subgraph state (includes messages from both invocations)
    const subgraphState = (await graph.getState(config, { subgraphs: true })).tasks[0].state;  // [!code highlight]
    ```
  </Tab>
</Tabs>

## Stream subgraph outputs

To include outputs from subgraphs in the streamed outputs, you can set the subgraphs option in the stream method of the parent graph. This will stream outputs from both the parent graph and any subgraphs.

```typescript theme={null}
for await (const chunk of await graph.stream(
  { foo: "foo" },
  {
    subgraphs: true,   // [!code highlight]
    streamMode: "updates",
  }
)) {
  console.log(chunk);
}
```

1. Set `subgraphs: true` to stream outputs from subgraphs.

<Accordion title="Stream from subgraphs">
  ```typescript theme={null}
  import { StateGraph, StateSchema, START } from "@langchain/langgraph";
  import * as z from "zod";

  // Define subgraph
  const SubgraphState = new StateSchema({
    foo: z.string(),
    bar: z.string(),
  });

  const subgraphBuilder = new StateGraph(SubgraphState)
    .addNode("subgraphNode1", (state) => {
      return { bar: "bar" };
    })
    .addNode("subgraphNode2", (state) => {
      // note that this node is using a state key ('bar') that is only available in the subgraph
      // and is sending update on the shared state key ('foo')
      return { foo: state.foo + state.bar };
    })
    .addEdge(START, "subgraphNode1")
    .addEdge("subgraphNode1", "subgraphNode2");

  const subgraph = subgraphBuilder.compile();

  // Define parent graph
  const ParentState = new StateSchema({
    foo: z.string(),
  });

  const builder = new StateGraph(ParentState)
    .addNode("node1", (state) => {
      return { foo: "hi! " + state.foo };
    })
    .addNode("node2", subgraph)
    .addEdge(START, "node1")
    .addEdge("node1", "node2");

  const graph = builder.compile();

  for await (const chunk of await graph.stream(
    { foo: "foo" },
    {
      streamMode: "updates",
      subgraphs: true,   // [!code highlight]
    }
  )) {
    console.log(chunk);
  }
  ```

  1. Set `subgraphs: true` to stream outputs from subgraphs.

  ```
  [[], { node1: { foo: 'hi! foo' } }]
  [['node2:e58e5673-a661-ebb0-70d4-e298a7fc28b7'], { subgraphNode1: { bar: 'bar' } }]
  [['node2:e58e5673-a661-ebb0-70d4-e298a7fc28b7'], { subgraphNode2: { foo: 'hi! foobar' } }]
  [[], { node2: { foo: 'hi! foobar' } }]
  ```
</Accordion>

***

<Callout icon="edit">
  [Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/oss/langgraph/use-subgraphs.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
</Callout>

<Callout icon="terminal-2">
  [Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
</Callout>
