子智能体 架构中,一个中央主 智能体 (通常称为 监督者 )通过将子智能体作为 工具 调用来协调它们。主智能体决定调用哪个子智能体、提供什么输入以及如何组合结果。子智能体是无状态的——它们不记住过去的交互,所有对话记忆都由主智能体维护。这提供了 上下文 隔离:每次子智能体调用都在干净的上下文窗口中工作,防止主对话中的上下文膨胀。

主要特点

  • 集中控制:所有路由都通过主智能体进行
  • 无直接用户交互:子智能体将结果返回给主智能体,而不是用户(尽管您可以在子智能体中使用 中断 来允许用户交互)
  • 通过工具调用子智能体:子智能体通过工具被调用
  • 并行执行:主智能体可以在单个回合中调用多个子智能体
监督者 vs. 路由器 :监督者智能体(此模式)与 路由器 不同。监督者是一个完整的智能体,它维护对话上下文并动态决定在多个回合中调用哪些子智能体。路由器通常是一个单一的分类步骤,它将任务分派给智能体,而不维护持续的对话状态。

何时使用

当您有多个不同的领域(例如日历、电子邮件、CRM、数据库),子智能体不需要直接与用户对话,或者您想要集中的工作流控制时,请使用子智能体模式。对于只有少量 工具 的简单情况,请使用 单个智能体
需要在子智能体中进行用户交互? 虽然子智能体通常将结果返回给主智能体而不是直接与用户对话,但您可以在子智能体中使用 中断 来暂停执行并收集用户输入。当子智能体在继续之前需要澄清或批准时,这很有用。主智能体仍然是协调者,但子智能体可以在任务进行中从用户那里收集信息。

基本实现

核心机制将子智能体包装为主智能体可以调用的工具:
import { createAgent, tool } from "langchain";
import { z } from "zod";

// Create a subagent
const subagent = createAgent({ model: "anthropic:claude-sonnet-4-20250514", tools: [...] });

// Wrap it as a tool
const callResearchAgent = tool(
  async ({ query }) => {
    const result = await subagent.invoke({
      messages: [{ role: "user", content: query }]
    });
    return result.messages.at(-1)?.content;
  },
  {
    name: "research",
    description: "Research a topic and return findings",
    schema: z.object({ query: z.string() })
  }
);

// Main agent with subagent as a tool
const mainAgent = createAgent({ model: "anthropic:claude-sonnet-4-20250514", tools: [callResearchAgent] });

Tutorial: Build a personal assistant with subagents

Learn how to build a personal assistant using the subagents pattern, where a central main agent (supervisor) coordinates specialized worker agents.

Design decisions

When implementing the subagents pattern, you’ll make several key design choices. This table summarizes the options—each is covered in detail in the sections below.
Decision Options
Sync vs. async Sync (blocking) vs. async (background)
Tool patterns Tool per agent vs. single dispatch tool
Subagent inputs Query only vs. full context
Subagent outputs Subagent result vs full conversation history

Sync vs. async

Subagent execution can be synchronous (blocking) or asynchronous (background). Your choice depends on whether the main agent needs the result to continue.
Mode Main agent behavior Best for Tradeoff
Sync Waits for subagent to complete Main agent needs result to continue Simple, but blocks the conversation
Async Continues while subagent runs in background Independent tasks, user shouldn’t wait Responsive, but more complex
Not to be confused with Python’s async / await . Here, “async” means the main agent kicks off a background job (typically in a separate process or service) and continues without blocking.

Synchronous (default)

By default, subagent calls are synchronous —the main agent waits for each subagent to complete before continuing. Use sync when the main agent’s next action depends on the subagent’s result. When to use sync:
  • Main agent needs the subagent’s result to formulate its response
  • Tasks have order dependencies (e.g., fetch data → analyze → respond)
  • Subagent failures should block the main agent’s response
Tradeoffs:
  • Simple implementation—just call and wait
  • User sees no response until all subagents complete
  • Long-running tasks freeze the conversation

Asynchronous

Use asynchronous execution when the subagent’s work is independent—the main agent doesn’t need the result to continue conversing with the user. The main agent kicks off a background job and remains responsive. When to use async:
  • Subagent work is independent of the main conversation flow
  • Users should be able to continue chatting while work happens
  • You want to run multiple independent tasks in parallel
Three-tool pattern:
  1. Start job : Kicks off the background task, returns a job ID
  2. Check status : Returns current state (pending, running, completed, failed)
  3. Get result : Retrieves the completed result
Handling job completion: When a job finishes, your application needs to notify the user. One approach: surface a notification that, when clicked, sends a HumanMessage like “Check job_123 and summarize the results.”

Tool patterns

There are two main ways to expose subagents as tools:
Pattern Best for Trade-off
Tool per agent Fine-grained control over each subagent’s input/output More setup, but more customization
Single dispatch tool Many agents, distributed teams, convention over configuration Simpler composition, less per-agent customization

Tool per agent

The key idea is wrapping subagents as tools that the main agent can call:
import { createAgent, tool } from "langchain";
import * as z from "zod";

// Create a sub-agent
const subagent = createAgent({...});  

// Wrap it as a tool
const callSubagent = tool(  
  async ({ query }) => {  
    const result = await subagent.invoke({
      messages: [{ role: "user", content: query }]
    });
    return result.messages.at(-1)?.text;
  },
  {
    name: "subagent_name",
    description: "subagent_description",
    schema: z.object({
      query: z.string().describe("The query to send to subagent")
    })
  }
);

// Main agent with subagent as a tool
const mainAgent = createAgent({ model, tools: [callSubagent] });  
The main agent invokes the subagent tool when it decides the task matches the subagent’s description, receives the result, and continues orchestration. See Context engineering for fine-grained control.

Single dispatch tool

An alternative approach uses a single parameterized tool to invoke ephemeral sub-agents for independent tasks. Unlike the tool per agent approach where each sub-agent is wrapped as a separate tool, this uses a convention-based approach with a single task tool: the task description is passed as a human message to the sub-agent, and the sub-agent’s final message is returned as the tool result. Use this approach when you want to distribute agent development across multiple teams, need to isolate complex tasks into separate context windows, need a scalable way to add new agents without modifying the coordinator, or prefer convention over customization. This approach trades flexibility in context engineering for simplicity in agent composition and strong context isolation. Key characteristics:
  • Single task tool: One parameterized tool that can invoke any registered sub-agent by name
  • Convention-based invocation: Agent selected by name, task passed as human message, final message returned as tool result
  • Team distribution: Different teams can develop and deploy agents independently
  • Agent discovery: Sub-agents can be discovered via system prompt (listing available agents) or through progressive disclosure (loading agent information on-demand via tools)
An interesting aspect of this approach is that sub-agents may have the exact same capabilities as the main agent. In such cases, invoking a sub-agent is really about context isolation as the primary reason—allowing complex, multi-step tasks to run in isolated context windows without bloating the main agent’s conversation history. The sub-agent completes its work autonomously and returns only a concise summary, keeping the main thread focused and efficient.
import { tool, createAgent } from "langchain";
import * as z from "zod";

// Sub-agents developed by different teams
const researchAgent = createAgent({
  model: "gpt-4o",
  prompt: "You are a research specialist...",
});

const writerAgent = createAgent({
  model: "gpt-4o",
  prompt: "You are a writing specialist...",
});

// Registry of available sub-agents
const SUBAGENTS = {
  research: researchAgent,
  writer: writerAgent,
};

const task = tool(
  async ({ agentName, description }) => {
    const agent = SUBAGENTS[agentName];
    const result = await agent.invoke({
      messages: [
        { role: "user", content: description }
      ],
    });
    return result.messages.at(-1)?.content;
  },
  {
    name: "task",
    description: `Launch an ephemeral subagent.

Available agents:
- research: Research and fact-finding
- writer: Content creation and editing`,
    schema: z.object({
      agentName: z
        .string()
        .describe("Name of agent to invoke"),
      description: z
        .string()
        .describe("Task description"),
    }),
  }
);

// Main coordinator agent
const mainAgent = createAgent({
  model: "gpt-4o",
  tools: [task],
  prompt: (
    "You coordinate specialized sub-agents. " +
    "Available: research (fact-finding), " +
    "writer (content creation). " +
    "Use the task tool to delegate work."
  ),
});

Context engineering

Control how context flows between the main agent and its subagents:
Category Purpose Impacts
Subagent specs Ensure subagents are invoked when they should be Main agent routing decisions
Subagent inputs Ensure subagents can execute well with optimized context Subagent performance
Subagent outputs Ensure the supervisor can act on subagent results Main agent performance
See also our comprehensive guide on context engineering for agents.

Subagent specs

The names and descriptions associated with subagents are the primary way the main agent knows which subagents to invoke. These are prompting levers—choose them carefully.
  • Name : How the main agent refers to the sub-agent. Keep it clear and action-oriented (e.g., research_agent , code_reviewer ).
  • Description : What the main agent knows about the sub-agent’s capabilities. Be specific about what tasks it handles and when to use it.
For the single dispatch tool design, the main agent needs to call the task tool with the name of the subagent to invoke. The available tools can be provided to the main agent via one of the following methods:
  • System prompt enumeration : List available agents in the system prompt.
  • Enum constraint on dispatch tool : For small agent lists, add an enum to the agent_name field.
  • Tool-based discovery : For large or dynamic agent registries, provide a separate tool (e.g., list_agents or search_agents ) that returns available agents.

Subagent inputs

Customize what context the subagent receives to execute its task. Add input that isn’t practical to capture in a static prompt—full message history, prior results, or task metadata—by pulling from the agent’s state.
Subagent inputs example
import { createAgent, tool, AgentState, ToolMessage } from "langchain";
import { Command } from "@langchain/langgraph";
import * as z from "zod";

// Example of passing the full conversation history to the sub agent via the state.
const callSubagent1 = tool(
  async ({query}) => {
    const state = getCurrentTaskInput<AgentState>();
    // Apply any logic needed to transform the messages into a suitable input
    const subAgentInput = someLogic(query, state.messages);
    const result = await subagent1.invoke({
      messages: subAgentInput,
      // You could also pass other state keys here as needed.
      // Make sure to define these in both the main and subagent's
      // state schemas.
      exampleStateKey: state.exampleStateKey
    });
    return result.messages.at(-1)?.content;
  },
  {
    name: "subagent1_name",
    description: "subagent1_description",
  }
);

Subagent outputs

Customize what the main agent receives back so it can make good decisions. Two strategies:
  1. Prompt the sub-agent : Specify exactly what should be returned. A common failure mode is that the sub-agent performs tool calls or reasoning but doesn’t include results in its final message—remind it that the supervisor only sees the final output.
  2. Format in code : Adjust or enrich the response before returning it. For example, pass specific state keys back in addition to the final text using a Command .
Subagent outputs example
import { tool, ToolMessage } from "langchain";
import { Command } from "@langchain/langgraph";
import * as z from "zod";

const callSubagent1 = tool(
  async ({ query }, config) => {
    const result = await subagent1.invoke({
      messages: [{ role: "user", content: query }]
    });

    // Return a Command to update multiple state keys
    return new Command({
      update: {
        // Pass back additional state from the subagent
        exampleStateKey: result.exampleStateKey,
        messages: [
          new ToolMessage({
            content: result.messages.at(-1)?.text,
            tool_call_id: config.toolCall?.id!
          })
        ]
      }
    });
  },
  {
    name: "subagent1_name",
    description: "subagent1_description",
    schema: z.object({
      query: z.string().describe("The query to send to subagent1")
    })
  }
);

Connect these docs to Claude, VSCode, and more via MCP for real-time answers.