Skip to content

异步子智能体 (Async subagents) ​

启动非阻塞后台任务并进行实时中途引导控制

同步子智能体在执行期间会完全阻塞主协调器智能体,直到子任务彻底完成并返回单份报告。 然而在真实业务中,许多任务耗时数十分钟(如拉取庞大代码仓库、运行漫长的端到端测试、爬取分析海量网页),如果一直同步阻塞,用户将无法继续与主智能体交流。

异步子智能体 (Async Subagents) 允许主智能体将任务作为非阻塞后台进程启动。 在子任务于后台并发推进的同时,用户可以继续与主智能体畅聊;主智能体还可以随时查询子任务进度、在运行中向子任务下发新指示,甚至提前取消无意义的任务。

何时使用异步子智能体? ​

  • 长周期后台任务:编译、部署、全量安全扫描或海量数据挖掘。
  • 中途需要干预引导 (Mid-flight steering):用户可能在任务执行到一半时改变需求,需要向运行中的子智能体追加新约束。
  • 保持即时响应体验:即使后台有十几个子智能体在跑,前端用户界面依然能对用户的新提问做到秒级响应。

配置异步子智能体 (Configuration) ​

配置异步子智能体需要在中间件中注册专门的异步执行器:

mermaid
graph TB
    User([User]) --> Supervisor[Supervisor Agent]

    Supervisor --> |launch| Researcher[Researcher]
    Supervisor --> |launch| Coder[Coder]

    Researcher --> |check| Supervisor
    Coder --> |check| Supervisor

异步子智能体核心工具集 ​

启用异步支持后,主协调器会获得一组专门的异步管理工具:

  • launch:在后台异步启动指定的子智能体,立即返回一个唯一的 task_id,不会阻塞主流程。
  • check:根据 task_id 检查子任务当前的运行状态(running、completed、failed)以及最新的中间产物。
  • update:在子任务运行过程中向其发送追加提示词或指令,动态调整其执行策略。
  • cancel:当用户取消操作或需求发生变更时,提前终止正在执行的后台子任务,节省算力资源。

理解生命周期状态流转 ​

launch -> (异步并发后台运行) -> 随时 check 查询 / update 调整 -> 最终自动完成或执行 cancel。

状态管理与数据隔离 ​

异步子智能体拥有属于自己的独立检查点线程。主智能体通过专门的跨线程状态通道与其通信,确保并发执行下的数据安全与读写一致性。

传输协议选型 (Transport) ​

  • ASGI 传输 (同机部署):协调器与子智能体运行在同一个服务集群内部,通信延迟极低。
  • HTTP 传输 (远程分离):子智能体部署在完全独立的专用计算节点或沙箱集群中,通过 REST API 与主智能体进行跨网络调度。

启动模式选择 (Launch mode) ​

  • 隔离模式 (Isolated):子任务在全新的空白上下文中启动。
  • 分叉模式 (Fork mode):子任务克隆主智能体现有的历史上下文,但在独立的时间线上继续执行。

部署拓扑结构 (Deployment topology) ​

  • 单一集群部署 (Single deployment):所有主从智能体部署在相同的容器服务上。
  • 主从分离部署 (Split deployment):主协调器部署在轻量级 API 服务上,繁重的异步子智能体部署在弹性伸缩的 GPU/计算节点池中。

最佳实践与避坑指南 ​

  1. 本地开发时合理配置 Worker 线程池大小:避免同时启动过多异步任务导致本地开发机 CPU/内存耗尽。
  2. 描述信息清晰严谨:子智能体的 description 应当明确强调其适合异步后台长跑的属性。
  3. 基于 Thread ID 进行全链路追踪:在 LangSmith 中将异步子任务关联至父级会话链路,方便回溯审计。
ts
import { createDeepAgent, type AsyncSubAgent } from "deepagents";

const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",
    description: "Research agent for information gathering and synthesis",
    graphId: "researcher",
    // No url → ASGI transport (co-deployed in the same deployment)
  },
  {
    name: "coder",
    description: "Coding agent for code generation and review",
    graphId: "coder",
    // url: "https://coder-deployment.langsmith.dev"  // Optional: HTTP transport for remote
  },
];

const agent = createDeepAgent({
  model: "google:gemini-3.6-flash",
  subagents: [...asyncSubagents],
});
ts
import { createDeepAgent, type AsyncSubAgent } from "deepagents";

const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",
    description: "Research agent for information gathering and synthesis",
    graphId: "researcher",
    // No url → ASGI transport (co-deployed in the same deployment)
  },
  {
    name: "coder",
    description: "Coding agent for code generation and review",
    graphId: "coder",
    // url: "https://coder-deployment.langsmith.dev"  // Optional: HTTP transport for remote
  },
];

const agent = createDeepAgent({
  model: "openai:gpt-5.5",
  subagents: [...asyncSubagents],
});
ts
import { createDeepAgent, type AsyncSubAgent } from "deepagents";

const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",
    description: "Research agent for information gathering and synthesis",
    graphId: "researcher",
    // No url → ASGI transport (co-deployed in the same deployment)
  },
  {
    name: "coder",
    description: "Coding agent for code generation and review",
    graphId: "coder",
    // url: "https://coder-deployment.langsmith.dev"  // Optional: HTTP transport for remote
  },
];

const agent = createDeepAgent({
  model: "anthropic:claude-sonnet-5",
  subagents: [...asyncSubagents],
});
ts
import { createDeepAgent, type AsyncSubAgent } from "deepagents";

const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",
    description: "Research agent for information gathering and synthesis",
    graphId: "researcher",
    // No url → ASGI transport (co-deployed in the same deployment)
  },
  {
    name: "coder",
    description: "Coding agent for code generation and review",
    graphId: "coder",
    // url: "https://coder-deployment.langsmith.dev"  // Optional: HTTP transport for remote
  },
];

const agent = createDeepAgent({
  model: "openrouter:z-ai/glm-5.2",
  subagents: [...asyncSubagents],
});
ts
import { createDeepAgent, type AsyncSubAgent } from "deepagents";

const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",
    description: "Research agent for information gathering and synthesis",
    graphId: "researcher",
    // No url → ASGI transport (co-deployed in the same deployment)
  },
  {
    name: "coder",
    description: "Coding agent for code generation and review",
    graphId: "coder",
    // url: "https://coder-deployment.langsmith.dev"  // Optional: HTTP transport for remote
  },
];

const agent = createDeepAgent({
  model: "fireworks:accounts/fireworks/models/glm-5p2",
  subagents: [...asyncSubagents],
});
ts
import { createDeepAgent, type AsyncSubAgent } from "deepagents";

const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",
    description: "Research agent for information gathering and synthesis",
    graphId: "researcher",
    // No url → ASGI transport (co-deployed in the same deployment)
  },
  {
    name: "coder",
    description: "Coding agent for code generation and review",
    graphId: "coder",
    // url: "https://coder-deployment.langsmith.dev"  // Optional: HTTP transport for remote
  },
];

const agent = createDeepAgent({
  model: "baseten:zai-org/GLM-5.2",
  subagents: [...asyncSubagents],
});
ts
import { createDeepAgent, type AsyncSubAgent } from "deepagents";

const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",
    description: "Research agent for information gathering and synthesis",
    graphId: "researcher",
    // No url → ASGI transport (co-deployed in the same deployment)
  },
  {
    name: "coder",
    description: "Coding agent for code generation and review",
    graphId: "coder",
    // url: "https://coder-deployment.langsmith.dev"  // Optional: HTTP transport for remote
  },
];

const agent = createDeepAgent({
  model: "ollama:north-mini-code-1.0",
  subagents: [...asyncSubagents],
});
FieldTypeDescription
namestringRequired. Unique identifier. The supervisor uses this when launching tasks.
descriptionstringRequired. What this subagent does. The supervisor uses this to decide which agent to delegate to.
graphIdstringRequired. The graph ID (or assistant ID) on the Agent Protocol server. For LangGraph-based deployments, this must match a graph registered in langgraph.json.
urlstringOptional. When omitted, uses ASGI transport (in-process). When set, uses HTTP transport to a remote Agent Protocol server.
headersRecord<string, string>Optional. Additional headers for requests to the remote server. Use for custom authentication with self-hosted Agent Protocol servers.

For LangGraph-based deployments, register all graphs in the same langgraph.json for co-deployed setups:

json
{
  "graphs": {
    "supervisor": "./src/supervisor.py:graph",
    "researcher": "./src/researcher.py:graph",
    "coder": "./src/coder.py:graph"
  }
}

Use the async subagent tools ​

The AsyncSubAgentMiddleware which is included in the Deep Agents stack when async subagents are configured, gives the supervisor five tools:

ToolPurposeReturns
start_async_taskStart a new background taskTask ID (immediately)
check_async_taskGet current status and result of a taskStatus + result (if complete)
update_async_taskSend new instructions to a running taskConfirmation + updated status
cancel_async_taskStop a running taskConfirmation
list_async_tasksList all tracked tasks with live statusesSummary of all tasks

The supervisor's LLM calls these tools like any other tool. The middleware handles thread creation, run management, and state persistence automatically.

Understand the lifecycle ​

A typical interaction follows this sequence:

mermaid
sequenceDiagram
    participant User
    participant Supervisor
    participant Platform as Agent Protocol Server

    User->>Supervisor: "Research topic X"
    Supervisor->>Platform: launch(researcher, "topic X")
    Platform-->>Supervisor: task_id: abc123

    Note over Platform: Researcher working...

    Supervisor-->>User: "Started task abc123"

    Note over User,Platform: User continues conversation

    User->>Supervisor: "How's the research going?"
    Supervisor->>Platform: check(abc123)
    Platform-->>Supervisor: status: success, result: "findings..."
    Supervisor-->>User: "Here are the results"
  • Launch creates a new thread on the server, starts a run with the task description as input, and returns the thread ID as the task ID. The supervisor reports this ID to the user and does not poll for completion.
  • Check fetches the current run status. If the run succeeded, it retrieves the thread state to extract the subagent's final output. If still running, it reports that to the user.
  • Update creates a new run on the same thread with an interrupt multitask strategy. The previous run is interrupted, and the subagent restarts with the full conversation history plus the new instructions. The task ID stays the same.
  • Cancel calls runs.cancel() on the server and marks the task as "cancelled".
  • List iterates over all tracked tasks. For non-terminal tasks, it fetches live status from the server in parallel. Terminal statuses (success, error, cancelled) are returned from cache.

Understand state management ​

Task metadata is stored in a dedicated state channel (asyncTasks) on the supervisor's graph, separate from the message history. This is critical because deep agents compact their message history when the context window fills up/ If task IDs were only in tool messages, they would be lost during compaction. The dedicated channel ensures the supervisor can always recall its tasks through list_async_tasks, even after multiple rounds of summarization.

Each tracked task records the task ID, agent name, thread ID, run ID, status, and timestamps (createdAt, checkedAt, updatedAt).

Choose a transport ​

ASGI transport (co-deployed) ​

When a subagent spec omits the url field, the LangGraph SDK uses ASGI transport -- SDK calls are routed through in-process function calls rather than HTTP. For LangGraph-based deployments, this requires both graphs to be registered in the same langgraph.json.

ASGI transport eliminates network latency and requires no additional auth configuration. The subagent still runs as a separate thread with its own state. This is the recommended default.

HTTP transport (remote) ​

Add a url field to switch to HTTP transport, where SDK calls go over the network to a remote Agent Protocol server:

typescript
{
  name: "researcher",
  description: "Research agent",
  graphId: "researcher",
  url: "https://my-research-deployment.langsmith.dev",
}

For LangGraph deployments, authentication is handled by the LangGraph SDK using LANGSMITH_API_KEY (or LANGGRAPH_API_KEY) from environment variables. Self-hosted Agent Protocol servers may use a different authentication mechanism.

Use HTTP transport when subagents need independent scaling, different resource profiles, or are maintained by a different team.

Choose a launch mode ​

The mode field on each AsyncSubAgent spec controls what conversation context the subagent receives when a task starts.

ModeBehavior
"isolated" (default)Sends only the delegated task description. The subagent starts fresh with no knowledge of the parent's conversation history.
"fork"Sends a snapshot of the parent's conversation at launch, followed by the task description. The subagent can use that context while working in the background.

"isolated" is the right choice for most tasks. Use "fork" when the subagent needs established context — for example, a background drafting agent that should write in the style and voice already set up in the conversation.

Fork mode ​

WARNING

mode="fork" is available in Python only. It is experimental and may change in a future release.

At launch, start_async_task sends the parent's full conversation history — replayed up to that point — followed by the task description. Later messages from the parent are not forwarded to the child.

Prompt-cache reuse. To get cache hits on the forked conversation prefix, configure the remote agent with the same model, system prompt, tools, and prompt-processing middleware as the parent. Fork mode copies conversation history; it does not synchronize those settings. Matching configuration is necessary but not sufficient: the rendered prefix must match, and cache hits depend on the provider.

Choose a deployment topology ​

Single deployment ​

A single deployment means all agents are co-deployed on the same server using ASGI transport. For LangGraph-based deployments, register all graphs in one langgraph.json. This is the recommended starting point -- one server to manage, zero network latency between agents.

Split deployment ​

Supervisor on one server, subagents on another via HTTP transport. Use when subagents need different compute profiles or independent scaling.

Hybrid ​

In a hybrid deployment, some subagents are co-deployed via ASGI, others remote via HTTP:

ts
import type { AsyncSubAgent } from "deepagents";

const asyncSubagents: AsyncSubAgent[] = [
  {
    name: "researcher",
    description: "Research agent",
    graphId: "researcher",
    // No url → ASGI (co-deployed)
  },
  {
    name: "coder",
    description: "Coding agent",
    graphId: "coder",
    url: "https://coder-deployment.langsmith.dev",
    // url present → HTTP (remote)
  },
];

Best practices ​

Size the worker pool for local development ​

When running locally with langgraph dev, increase the worker pool to accommodate concurrent subagent runs. Each active run occupies a worker slot. A supervisor with 3 concurrent subagent tasks requires 4 slots (1 supervisor + 3 subagents). Under-provisioning causes launches to queue.

bash
langgraph dev --n-jobs-per-worker 10

Write clear subagent descriptions ​

The supervisor uses descriptions to decide which subagent to launch. Be specific and action-oriented:

typescript
// Good
{
  name: "researcher",
  description: "Conducts in-depth research using web search. Use for questions requiring multiple searches and synthesis.",
  graphId: "researcher",
}
typescript
// Bad
{
  name: "helper",
  description: "helps with stuff",
  graphId: "helper",
}

Trace with thread IDs ​

When using LangGraph-based deployments, every async subagent run is a standard LangGraph run, fully visible in LangSmith. The supervisor's trace shows tool calls for launch, check, update, cancel, and list. Each subagent run appears as a separate trace, linked by thread ID. Use the thread ID (task ID) to correlate supervisor orchestration traces with subagent execution traces.

Troubleshooting ​

Supervisor polls immediately after launch ​

Problem: The supervisor calls check in a loop right after launching, turning async execution into blocking.

Solution: The middleware injects system prompt rules to prevent this. If polling persists, reinforce the behavior in your supervisor's system prompt:

ts
import { createDeepAgent } from "deepagents";

const agent = createDeepAgent({
  model: "google:gemini-3.6-flash",
  systemPrompt: `...your instructions...

    After launching an async subagent, ALWAYS return control to the user.
    Never call check_async_task immediately after launch.`,
  subagents: [...asyncSubagents],
});

Supervisor reports stale status ​

Problem: The supervisor references a task status from earlier in conversation history instead of making a fresh check call.

Solution: The middleware prompt instructs the model that "task statuses in conversation history are always stale." If this still occurs, add explicit instructions to always call check or list before reporting status.

Task ID lookup failures ​

Problem: The supervisor truncates or reformats the task ID, causing check or cancel to fail.

Solution: The middleware prompt instructs the model to always use the full task ID. If truncation persists, this is typically a model-specific issue -- try a different model or add "always show the full task_id, never truncate or abbreviate it" to your system prompt.

Subagent launches queue instead of running ​

Problem: Launching a subagent hangs or takes a long time to start.

Solution: The worker pool is likely exhausted. Increase the pool size with --n-jobs-per-worker. See Size the worker pool.

Reference implementation ​

The async-deep-agents repository contains working examples in both Python and TypeScript that deploy to LangSmith Deployments. It demonstrates a supervisor with researcher and coder subagents running as background tasks.


Connect these docs to your agent of choice via MCP for real-time answers.

Edit this page on GitHub or file an issue.