
智能体正在走向异步:用编排器与后台 worker 突破长任务阻塞
LangChain 把 Deep Agents 拆成持续响应的编排器与后台异步子智能体,用稳定任务 ID、完成通知、人工审批和中断策略解决长任务阻塞,并划出具身系统异步调度的安全边界。
异步委派改变了智能体的运行形态
来源:Agents Are Going Async,Sydney Runkle、Thushanth Bengre,LangChain,2026 年 11 月 5 日。
想象一位幕僚长:每当你把一个大型项目交给他,他就彻底消失,直到全部做完才回来。大多数人会立刻换人。真正有用的幕僚长会让项目在后台持续推进,同时保持可联系,并主动同步进展,而不是让每次对话都等待当前最耗时的任务结束。
用户对智能体的期待正在变成这样。当任务从一句问答扩展到持续数分钟甚至更久的目标时,智能体需要同时具备两种性质。它必须保持响应性:新指令能够立刻被理解,并建立在已有上下文之上;它还必须保持生产力:多个大型任务可以并行推进,而不是排成一条只允许一个任务前进的长队。
标准的 ReAct 循环无法同时满足这两点。模型调用工具、等待结果,再决定下一步。这个模式容易理解,但一次慢工具调用会阻塞整个循环,后续所有指令都只能排在它后面。如果一个研究任务需要两分钟,用户连“现在进展如何”这样的问题都无法及时得到回答。
LangChain 认为,智能体系统正在转向另一种分工:一个直接面向用户的智能体,也就是编排器或 supervisor,负责对话和协调;后台子智能体负责执行耗时工作。原文列举了生态中的类似实践,包括 Claude Code 默认在后台运行子智能体、OpenAI 的 dots 把工作交给后台 worker,以及 DeepSeek Harness 用面向用户的 router 和后台并行 worker 构成超级智能体。
Deep Agents 通过异步子智能体把这一模式产品化。编排器保存对话上下文并持续响应用户;异步子智能体在自己的线程中独立执行,完成后再向编排器报告。系统能够承担的工作量,由此不再受一条 agent loop 的长度限制,而可以随着后台 worker 的数量增长。
这不是在单次工具调用内部做一点性能优化,而是改变了编排的基本单位。同步智能体围绕一条活跃控制路径组织;异步智能体则必须管理一组正在运行的任务,以及它们的身份、状态变化、部分结果、失败、审批和取消。后台任务出现之后,核心难点也从提示词设计转向系统设计。
为什么同步循环无法继续扩展
从智能体的视角看,普通工具调用是阻塞操作。模型发出调用,运行时执行,结果再被追加到对话中。当操作很快、结果又是下一步决策所必需时,这种方式非常合适。但当操作是长时间研究、跨仓库代码修改、浏览器流程或持续数分钟的仿真时,阻塞的代价就会迅速显现。
串行等待产生两类成本。第一是用户可见的延迟:当前操作结束前,后续问题无法处理。第二是并发能力被浪费:两个互不依赖的任务本可以同时执行,却仍然一前一后,即使部署里明明有空闲 worker。
可以用一个简化的时间模型说明差异。如果智能体同步执行 $N$ 个独立操作,第 $i$ 个操作耗时为 $T_i$,那么所有结果都可用之前的时间近似为:
$$T_{\mathrm{sync}}=\sum_{i=1}^{N}T_i$$
如果有足够的 worker,这些任务可以重叠。忽略调度开销时,总完成时间取决于分配到工作量最多的那个 worker:
$$T_{\mathrm{async}}\approx\max_{w=1,\ldots,W}\sum_{i\in \mathcal{J}_w}T_i$$
这两个公式是分析模型,不是 LangChain 原文给出的实测结果。真实系统还会遇到排队、模型推理、网络传输、重试和资源竞争。关键结论是,异步执行改变了瓶颈位置:问题不再只是“一条循环有多快”,而变成“部署能同时承受多少任务,以及编排器能否可靠地协调它们”。
这张图区分了并发和执行响应性。仅仅让多个任务并行并不够,如果编排器仍要等待整批任务返回,用户依然会阻塞。异步子智能体把“提交任务”和“收集结果”拆开,对话与后台工作才能各自推进。
编排器与后台 worker 的分工
异步子智能体更像 asyncio.create_task 创建的任务,或一个暂时不 await 的 JavaScript Promise,而不是普通同步函数调用。启动操作立刻返回一个标识,工作在独立线程中继续,结果稍后才可取回。
这种设计依赖两个性质:
- 用户仍能触达编排器。编排器是用户直接交互的智能体。子智能体不再占用它的循环,因此后台任务运行时,用户仍可继续对话。
- 任务之间不会互相阻塞。编排器可以在请求到来时启动新的子智能体,不必等整批任务一起返回。
这种拆分也改善了上下文管理。编排器需要知道如何拆解任务、哪些工作正在运行、最后如何综合结果,但不必把每个 worker 访问过的搜索页面、终端输出或失败浏览器动作都装进自己的消息历史。每个 worker 可以在更聚焦的上下文中工作,只把结果交回协调者。
原文列出了暴露给编排器的五个工具:
| 工具 | 职责 | 实际操作问题 |
|---|---|---|
start_async_task |
在独立线程启动子智能体,并立即返回任务 ID。 | 这个任务应该交给哪个专家,需要带哪些上下文? |
check_async_task |
读取当前状态;任务完成后同时取回结果。 | 任务是在运行、等待、成功、失败还是已取消? |
update_async_task |
向正在运行的任务发送新指令。 | 这次修改应中断当前运行,还是等它结束后再执行? |
cancel_async_task |
停止正在运行的任务。 | 任务是否已经造成不可逆结果,仍需后续核对? |
list_async_tasks |
列出所有已跟踪任务及其状态。 | 当前有哪些任务在飞,哪一个结果尚未回收? |
五个操作形成一个小型任务生命周期:启动、监控、控制、收集和停止。接口刻意保持狭窄。如果每种后台行为都定义一套独立协议,编排器就会和不同 worker 的具体实现深度耦合;共享同一组任务操作后,研究智能体、编码智能体和其他专家都能通过同一个控制面参与协作。
如何控制正在运行的子智能体
“编排器仍能联系用户”和“用户能改变 worker 正在做什么”是两件不同的事。用户可以继续向编排器发送消息,但编排器也可能需要修改后台任务本身。Deep Agents 通过 update_async_task 和每个子智能体自己的 update_strategy 处理这件事。
| 策略 | 行为 | 适用场景 |
|---|---|---|
interrupt |
默认策略。停止当前运行,并带着新指令和已有对话历史重新启动子智能体。 | 新信息推翻了原方向,例如“改用 Postgres”。 |
enqueue |
保留当前运行,把后续指令排在它之后。 | 新要求不否定当前工作,例如“做完以后把测试跑一遍”。 |
这个区别很重要,因为“更新”并不是一种单一操作。如果新指令只是补充,interrupt 可能丢掉已经完成的有效工作;如果原任务已经被证明方向错误,enqueue 又会浪费后续时间。最清晰的接口是在子智能体定义层明确策略,再由编排器决定发送什么消息。
当前官方文档把 update_strategy="enqueue" 标为 beta,并要求较新的 Deep Agents 版本。排队更新会在当前运行之后等待,任务在那之前处于 pending。如果子智能体正在等待人工审批,而队列中的下一次运行还没有开始,后来的 update 可能丢掉待处理请求。这些不是可以留给应用自行猜测的实现细节,而是产品界面必须明确呈现的状态语义。
通知把轮询改造成事件系统
编排器需要知道后台任务何时变化。基础协议允许用 check_async_task 轮询,并且所有未完成任务都会出现在编排器的系统上下文中,使它能够知道还有哪些工作在飞。但只靠轮询无法得到真正响应式的系统。一个模型启动任务后立刻检查、再做一次模型调用又检查、然后反复循环,只是用更多请求重新造出了阻塞行为。
Deep Agents 通过完成通知让子智能体主动唤醒或更新编排器。原文描述了三种情况:
- 编排器正在忙时,它会在每次模型调用前检查未完成任务,并在运行过程中吸收已经到达的结果。
- 编排器空闲时,子智能体完成工作会以新的 run 唤醒它。
- 编排器等待审批时,原审批保持不动,结果会等用户回答后再送达。
通知还应被识别为机器生成事件,而不是伪装成一条新的用户消息。当前文档说明,通知以普通消息形式进入状态,但在 additional_kwargs 中带有 deepagents_notification,包含任务 ID、智能体名、状态和 run ID。应用因此可以在同一个消息流中把状态更新与用户指令区分开来展示。
部署也可以通过 webhook 在子任务完成时通知外部应用。如果应用希望自己负责结果投递,可以设置 notify_parent=False,不让子智能体直接唤醒父级。当移动端通知、审批 UI 或外部任务系统负责最终用户体验时,这种边界更合适。
LangChain 将任务通知标为 beta,因此 API 仍可能变化。async-deep-agents 参考仓库提供了可运行的 supervisor,以及用 Python 和 TypeScript 编写的 researcher、coder 子智能体,比只看示意图更容易核对协议细节。
原文预计这一模式会从子智能体扩展到更广泛的工具调用。OpenAI 最近加入了异步工具调用支持:模型发出工具调用后可以继续工作,应用在下一次请求中再把结果送回。于是子智能体与工具面对同一个高层设计问题:一个长操作需要身份、后续结果通道,以及把结果与最初请求上下文重新对齐的机制。
审批是一等异步状态
智能体系统中最重要的状态不一定是“运行中”。子智能体可能在等待人工批准某个动作。原文指出,这样的任务显示为 waiting,用户在子智能体自己的线程中回答,而不必打断编排器。在 Slack 中的 Managed Deep Agents 里,审批请求可以回到原始 thread。
这体现了正确的职责分离。编排器不应代替用户批准有实际后果的动作,也不应因为一个 worker 暂停就结束自己当前的回合。worker 保留待处理 interrupt,编排器可以继续协调其他任务。
用户回答后,worker 通过自己线程上的 command 恢复执行,完成后再向编排器发送正常通知。如果 worker 需要的是编排器的信息,而不是用户审批,它应把问题写进结果;编排器随后通过 update_async_task 回复。这个区分避免把“人工审批边界”变成隐式的 agent-to-agent 对话通道。
当前文档还补充了重要边界:对 waiting 任务发送 update 会丢掉待处理请求,取消 waiting 任务因为没有活跃 run,只会把状态写成 cancelled。生产界面必须把这些状态变化显示清楚,否则用户可能以为自己批准或取消的是当前动作,而执行层已经不这样认为。
任务状态必须能穿过上下文压缩
编排器对后台任务的记忆不能只存在普通工具消息里。Deep Agents 在消息历史接近上下文上限时会进行压缩。如果任务 ID 和状态只出现在这些消息中,摘要过程可能刚好抹掉回收结果所需的信息。
因此,任务元数据存放在独立于消息历史的专用状态通道中。每个任务记录任务 ID、子智能体名、线程 ID、当前 run ID、状态和时间戳。经过多轮上下文压缩后,编排器仍能通过 list_async_tasks 找回任务注册表,因为它不属于会被总结成散文的对话内容。
任务 ID 被有意设计为稳定标识。参考实现直接使用子智能体线程 ID,而不是把线程和不断变化的 run 拼成一个值。更新任务会在同一线程上创建新的 run,因此用户和编排器可以始终使用同一个标识。使用不带语义的 UUID,也能降低模型尝试拆解或重写 ID 结构的概率。
ID 稳定并不能自动解决状态过期。对话历史里上一次打印的状态可能已经改变。middleware 注入的系统提示必须明确几条运行规则:启动后立即把控制权还给用户;不要在循环里轮询;历史状态不能当成当前状态;永远不要截断或改写任务 ID。它们不是可有可无的提示词偏好,而是在防止语言模型把异步协议重新压扁成阻塞协议。
传输方式与部署拓扑
异步子智能体通过 Agent Protocol 通信。当 supervisor 和 worker 一起部署时,LangGraph SDK 可以用 ASGI 在进程内调用。worker 仍然运行在独立线程、拥有独立状态,只是调用不必经过公网。这是官方推荐的起点,因为少一个网络跳点,也让认证和运维更简单。
在 worker 定义中加入 url 后,通信会切换为远程 HTTP。某种专家需要不同算力、独立扩缩容或由其他团队维护时,这种拆分很有价值。混合部署可以保留一部分通用的同进程 worker,同时把重型专家放到另一台服务器。
flowchart LR
U[User] --> O[Orchestrator]
O -->|start / update / cancel| R[Researcher thread]
O -->|start / update / cancel| C[Coder thread]
R -->|result notification| O
C -->|result notification| O
O -->|check / list| R
O -->|check / list| C
拓扑不只影响网络延迟。同进程部署共享 worker 池,一个有 3 个活跃子任务的 supervisor 需要 4 个执行槽位:1 个给编排器,3 个给 worker。容量不足时,异步启动只是把“循环阻塞”换成了“任务在平台队列里等待”。拆分部署可以隔离负载,但也增加远程认证、网络失败和独立发布带来的运维成本。
线程生命周期同样需要管理。每次启动都会创建一个 thread,直到被删除才消失。长期运行的系统应为完成和取消的任务设置保留策略,并用 thread ID 把编排器的工具调用与每个 worker run 串起来。LangSmith trace 可以关联这些记录,但前提是应用从头到尾保存同一个身份。
同步委派和异步委派是两种工具
异步执行并不天然优于所有委派。编排器下一步马上就需要一个短计算结果时,保持同步更合适。耗时研究、仓库迁移或独立后台分析,才是异步的典型对象。
| 维度 | 同步子智能体 | 异步子智能体 |
|---|---|---|
| 控制流 | 编排器等待任务完成。 | 启动后立即返回任务 ID。 |
| 并发 | 内部可以并行,但调用者仍被阻塞。 | 多个 worker 推进时,编排器仍可响应。 |
| 运行中控制 | 没有标准控制通道。 | 通过 interrupt 或 enqueue 语义更新任务。 |
| 取消 | 通常只能结束调用方。 | 可以通过独立执行记录取消 worker。 |
| 状态 | 通常局限于父调用。 | 在专用 worker thread 中持续存在。 |
| 上下文压力 | 结果立即回到父级。 | 编排器按需取结果,并维护紧凑的任务注册表。 |
| 运维成本 | 较低。 | 较高:需要排队、状态跟踪、清理和事件投递。 |
实用规则是:用户不应等待、任务互相独立,或任务运行中需要持续调整时,选择异步;结果直接决定下一轮 token 级决策,且能在正常延迟预算内完成时,保持同步。
如何配置异步子智能体
原文展示了 Managed Deep Agents 的最小配置:只需要给子智能体加一个字段,就能把研究专家放入后台运行。
from managed_deepagents import define_deep_agent
agent = define_deep_agent(
name="research-assistant",
model="openai:gpt-5.5",
subagents=[
{
"name": "researcher",
"description": "Researches a topic in the background.",
"system_prompt": "Research the topic and report your findings.",
"async": True,
},
],
)
在 Managed Deep Agents 中,每个被服务的 agent 还会自动得到一个 general-purpose 子智能体的异步副本。编排器因此可以在运行时决定,某次通用委派应在前台还是后台执行,而不必为每一种通用请求预先声明独立 worker。
开源 Deep Agents 配置暴露更多显式控制。当前文档中的 AsyncSubAgent 规格包含名称、描述、graph ID、可选远程 URL、更新策略、启动模式、通知设置和 webhook:
from deepagents import AsyncSubAgent, create_deep_agent
async_subagents = [
AsyncSubAgent(
name="researcher",
description=(
"Conducts in-depth research using web search. "
"Use for questions that require multiple searches and synthesis."
),
graph_id="researcher",
),
AsyncSubAgent(
name="coder",
description="Generates, reviews, and debugs code in an isolated repository.",
graph_id="coder",
url="https://coder-deployment.example.com",
update_strategy="enqueue",
),
]
agent = create_deep_agent(
model="google_genai:gemini-3.6-flash",
subagents=async_subagents,
)
worker 的 description 是路由接口的一部分。像“helps with stuff”这样的描述无法给编排器提供可靠的选择信号。好的描述应说明专业领域、worker 可访问的工具或环境,以及什么类型的任务应交给它。
开源包还提供两种启动模式。isolated 是默认值,只把委派任务发送给 worker;fork 则把父级当前有效对话的快照一并发送,使 worker 能延续既定风格或约束。fork 可以改善上下文连续性,但它不是复制父级 checkpoint 或全部状态,也会增大 prompt,并使缓存命中更依赖模型和提示配置是否一致。
异步智能体对物理 AI 意味着什么
LangChain 原文的例子是研究与编码智能体,但这套架构可以直接映射到具身智能。机器人编排器可能需要同时协调建图、感知模型、轨迹规划、仿真扫描和政策评估,还要继续回答操作员。这些任务耗时不同,不应该全部占住对话循环。
物理世界又增加了文档和代码 worker 没有的约束。机器人任务不能被当成一次可以随意重试的纯计算。一个任务被取消时,夹爪可能已经打开、机械臂可能已经移动,物体状态也可能已经改变。因此任务身份必须绑定到动作边界的幂等键;完成状态不确定时,依赖它的后续动作应被阻塞,直到系统核对实际发生了什么。
把异步模式用于机器人时,有三条额外规则:
- 资源所有权必须明确。两个子智能体不能在没有队列或锁的情况下同时向同一条机械臂发运动指令。独立的相机读取可以和运动重叠,互相竞争的控制命令不能。
- 观测必须带版本。异步感知结果可能在机器人移动后才返回。编排器必须知道该结果对应哪一套相机位姿和机器人状态,不能把旧图像与新的几何信息混在一起。
- 审批必须保留物理安全语义。等待中的 worker 不应被一个泛化的“继续”消息唤醒,以致丢失用户到底批准了哪个动作。审批需要绑定子智能体 thread、具体 interrupt 和预期物理命令。
这些是对 LangChain 模式的延伸,不是原文已经验证的机器人结论。它们说明编排器-worker 拆分很有价值,但单靠它不足以构成安全的具身系统。异步执行给系统争取了更多推理时间,也创造了一个外部状态可能变化的窗口。机器人既需要并发,也需要判断“结果现在是否仍然有效”的机制。
一个实用设计是按控制权限划分 worker。只读感知、搜索和仿真可以广泛并行;规划和评估也可以重叠。任何能改变物理世界的工作,都必须获得独占资源授权,执行有界动作,产生明确完成状态,并为不确定结果准备恢复路径。对话层可以异步,硬件控制则必须在安全需要的地方保持串行。
限制来自运维,而不只是架构
异步子智能体消除了最明显的阻塞,却没有消除协调成本。后台 worker 仍然消耗模型推理、CPU、内存和平台 worker 槽位。增加并行任务可以缩短完成时间,直到部署饱和;此后排队延迟会成为新的主导因素。真正有用的并发上限由资源决定,而不是由编排器能“想象”出多少任务决定。
随着用户目标变化,结果也可能过期。如果一个研究任务是在旧约束下启动的,用户后来又加入新的限制,正确操作可能是中断 worker,而不是继续等待一个已经不能回答问题的结果。这就是为什么更新语义必须属于协议,而不能只埋在应用代码里。
人工审批带来另一种延迟。一个系统即使能运行十个 worker,如果无法呈现正确的审批请求、识别哪个任务在等待,或恢复正确的 interrupt,实际容量也不会提升。通知设计和 thread 级审批状态与原始并发能力同样重要。
任务注册表还必须持久。若某个任务 ID 在上下文压缩中消失,即使 worker 仍健康运行,编排器也无法取回它的结果。进程重启后,系统也需要重建足够信息,区分 running、waiting approval、completed、failed 和 cancelled。独立任务状态通道是可靠基础,因为它把编排状态与可被摘要的对话文本分开了。
最后,可观测性必须跨越父子边界。只显示编排器工具调用的 trace,只能证明 worker 被启动,无法说明它做了什么;只显示 worker run,又丢失了触发它的用户请求。共享任务或 thread ID 是串联完整系统、进行调试的连接键。
一条务实的采用路径
团队不必一次把所有子智能体都改成异步。更稳妥的路径是:
- 先从同步开始。短而可预测的任务继续留在现有循环,同时明确每个 worker 的工具和输出格式。
- 先迁移一个真正耗时的任务。选择用户能明显感到等待、但结果又不是下一轮对话立即必需的工作。
- 定义控制面。明确哪些更新应 interrupt、哪些应 enqueue、哪些动作可以取消,以及谁有权批准 waiting 任务。
- 增加持久任务状态。把稳定 ID、状态、当前 run、父 thread、时间戳和恢复信息存到临时聊天文本之外。
- 跟踪完整生命周期。把启动、排队、模型调用、等待、完成、取消和结果收集记录为一条关联工作流。
- 对 worker 池做压力测试。先测出部署可承受的并发任务数,再增加专家数量或自动委派。
- 最后接入物理或不可逆动作。它们需要比只读后台任务更严格的幂等、资源锁和审批语义。
核心经验是:只有当接口承认“工作本来就需要时间”,智能体才能真正承担长程任务。同步循环假装每次操作都是短函数调用;异步架构则把长任务、部分进展、人工决定和不断变化的指令视为交互的正常组成部分。
因此,“智能体正在走向异步”并不只是一次性能优化。编排器从一个模型调用序列,变成了对正在运行的多个工作单元进行协调的控制器。这样得到的是一个在后台持续产出、同时仍能陪在用户身边的智能体。代价是更复杂的状态机,而这个状态机已经成为产品本身的一部分。
主要来源:LangChain,《Agents Are Going Async》。实现细节另与 Deep Agents 异步子智能体官方文档及 async-deep-agents 参考仓库核对。
原文来源:LangChain Bloghttps://www.langchain.com/blog/agents-are-going-async


