Skip to content
←Back to Blog
Agents Are Going Async: Orchestrators, Background Workers and the Long-Running Task
AI AgentAgent ArchitectureAsync Subagents

Agents Are Going Async: Orchestrators, Background Workers and the Long-Running Task

LangChain separates an always-responsive orchestrator from background subagents, using stable task IDs, notifications, approvals and steering to make long-running agent work concurrent and controllable.

Sydney Runkle, Thushanth BengreNovember 5, 202621 min read51 views
ZH

Async delegation changes the shape of an agent

Source: Agents Are Going Async, Sydney Runkle and Thushanth Bengre, LangChain, November 5, 2026.

Imagine a chief of staff who disappears whenever you hand them a large project and returns only after the work is finished. Most people would replace that person immediately. A useful chief of staff keeps projects moving in the background, remains reachable, and provides updates without forcing every conversation to wait for the longest open task.

Agents are approaching the same expectation. As users give them larger goals, two properties become necessary at once. The agent must remain responsive: a new instruction should be understood immediately, with the context of what came before. It must also remain productive: several substantial tasks should be able to make progress concurrently instead of forming one long queue.

The standard ReAct loop does not provide both properties. The model calls a tool, waits for the result, and decides what to do next. That pattern is easy to reason about, but a slow tool call blocks the loop. Every later instruction waits behind it. If one research request takes two minutes, a simple status question cannot be answered until those two minutes have elapsed.

LangChain argues that agent systems are converging on a different split: one user-facing agent, called an orchestrator or supervisor, stays available for conversation and coordination, while background subagents perform long-running work. The source article points to several examples of this pattern, including Claude Code running subagents in the background, OpenAI's dots delegating work to background workers, and DeepSeek Harness separating a user-facing router from parallel workers.

Deep Agents now expose this model through async subagents. The orchestrator retains the conversation context and remains available to the user. Async subagents run independently on their own threads and report back when they finish. The amount of work the system can absorb then grows with the number of background workers, rather than with the duration of a single agent loop.

This is more than an optimization inside one tool call. It changes the unit of orchestration. A synchronous agent is organized around one active control path. An asynchronous agent has to manage a set of live jobs, their identities, status transitions, partial results, failures, approvals, and cancellation. Once background work exists, the hard questions move from prompting to systems design.

Why the synchronous loop stops scaling

A normal tool call is a blocking operation from the agent's perspective. The model emits a call, the runtime executes it, and the result is appended to the conversation. This works well when the operation is fast and its result is needed immediately. It becomes inefficient when the operation is a long report, a repository-wide code change, a browser workflow, or a simulation that may run for minutes.

Serial waiting creates two separate costs. The first is user-visible latency. A follow-up question cannot be handled until the current operation ends. The second is lost concurrency. Two independent jobs with no shared dependency still execute one after the other, even though a worker pool could run them at the same time.

A rough performance model makes the distinction clear. If an agent performs $N$ independent operations synchronously and operation $i$ takes $T_i$, the time before every result is available is approximately:

$$T_{\mathrm{sync}}=\sum_{i=1}^{N}T_i$$

With enough workers, the same set of jobs can overlap. If $W$ workers are available and scheduling overhead is ignored, completion time is bounded by the work assigned to the busiest worker:

$$T_{\mathrm{async}}\approx\max_{w=1,\ldots,W}\sum_{i\in \mathcal{J}_w}T_i$$

These equations are conceptual, not a measurement from LangChain. Real systems also pay queueing delay, model inference, network transport, retries, and resource contention. The important point is that asynchronous execution changes the bottleneck. The question is no longer only "how fast is one loop?" It becomes "how many jobs can the deployment sustain, and how well can the orchestrator coordinate them?"

Synchronous and asynchronous subagent timelines
In the synchronous case, the agent waits while the subagent works. In the asynchronous case, the orchestrator starts the task, returns control to the user, and receives the result later.

The diagram makes an important distinction between concurrency and responsiveness. Parallel execution alone is not enough if the orchestrator still waits for the whole batch. Async subagents decouple task submission from result collection, so the conversation and the work can advance independently.

The orchestrator and the workers

An async subagent behaves more like a task created with asyncio.create_task or a JavaScript Promise that is not awaited immediately than like a normal function call. The launch returns right away with an identifier. The work continues on its own thread, and the result becomes available later.

The design depends on two properties:

  • The user can reach the orchestrator. The orchestrator is the agent the user talks to directly. A subagent no longer occupies its loop, so the user can continue the conversation while background work is running.
  • Tasks do not block one another. The orchestrator can start new subagents as requests arrive instead of waiting for an entire batch to complete.

This separation also improves context management. The orchestrator needs enough context to decompose work, remember what is running, and synthesize results. It does not need every intermediate search page, shell output, or failed browser action from each subagent inside its own message history. Each worker can operate in a focused context and return a result to the coordinator.

The source article summarizes five tools exposed to the orchestrator:

Tool Responsibility Operational question
start_async_task Starts a subagent on its own thread and returns a task ID. Which specialist should receive this job, and what context should it see?
check_async_task Returns current status and, after completion, the result. Is the job still running, waiting, successful, failed, or cancelled?
update_async_task Sends new instructions to an active task. Should the correction interrupt the current run or wait until it finishes?
cancel_async_task Stops a running task. Has the task performed any irreversible action that still needs accounting for?
list_async_tasks Lists tracked tasks and their current statuses. What is in flight, and which result is still missing?

The five operations form a small task lifecycle: launch, monitor, steer, collect, and stop. That interface is intentionally narrow. If every kind of background behavior were exposed as a separate protocol, the orchestrator would become coupled to each worker implementation. A shared set of task operations lets research agents, coding agents, and other specialists participate through the same control surface.

Steering a running subagent

Staying reachable is different from steering a worker. The user can send a new message to the orchestrator without blocking the job, but the orchestrator may also need to change what that job is doing. Deep Agents exposes this through update_async_task and a per-subagent update_strategy.

Strategy Behavior Appropriate use
interrupt The default. Stops the current run and restarts the subagent with the new instruction and its existing conversation history. A correction that invalidates the current direction, such as “use Postgres instead.”
enqueue Keeps the current run going and queues the follow-up behind it. An addition that does not invalidate current work, such as “run the tests when you are done.”

This distinction matters because "update" is not one operation. An interrupt can throw away useful progress if the new instruction is merely supplementary. An enqueue can waste work if the original task is now known to be wrong. The cleanest interface therefore makes the choice explicit at the subagent definition level, while leaving the specific message to the orchestrator.

In the current official documentation, update_strategy="enqueue" is marked as beta and requires a recent Deep Agents release. A queued update waits behind the active run and leaves the task in a pending state until it begins. If the subagent is waiting for approval before the queued run starts, the newer update can drop that pending request. Those details are not incidental. They define the semantics a production UI must communicate to the user.

Notifications turn polling into an event system

An orchestrator needs to know when background work changes state. The basic protocol supports polling with check_async_task, and every unfinished task is included in the orchestrator's system context so it can see what remains in flight. Polling is useful, but it is not enough for a responsive system. An agent that checks immediately after launch, again after one model call, and again in a loop has re-created blocking behavior with extra requests.

Deep Agents supports completion notifications so that a subagent can signal the orchestrator. The source article describes three cases:

  • If the orchestrator is busy, it checks unfinished tasks before each model call and can incorporate a completed result in the middle of a run.
  • If the orchestrator is idle, a completed subagent wakes it with a new run.
  • If the orchestrator is paused on an approval, the approval is left untouched and the result arrives after the user answers.
Async subagent notification paths when the orchestrator is busy, idle, or paused
Completion is delivered differently depending on whether the orchestrator is already running, idle, or waiting for a human decision.

The event itself should be identifiable as machine-generated rather than confused with a new user message. The current documentation says notifications arrive as a message with a deepagents_notification payload containing fields such as task ID, agent name, status, and run ID. That lets the application render a status update differently from a user instruction while still placing the event in the agent's normal message stream.

A deployment can also use a webhook to notify the surrounding application when a run finishes. In that configuration, the app can choose to deliver the result itself and disable parent notifications with notify_parent=False. This is the right boundary when an external job system, approval UI, or mobile notification service owns the user experience.

LangChain marks task notifications as beta, so the API can still change. The async-deep-agents reference repository provides a working supervisor with researcher and coder subagents in Python and TypeScript, which makes the protocol easier to inspect than a diagram alone.

The source article expects this pattern to extend beyond subagents to tools in general. OpenAI recently added asynchronous tool calling, where the model can continue working after issuing a call and the application sends the result back in a later request. Subagents and tools then share the same high-level design problem: a long operation needs an identity, a later result channel, and a way to reconcile the response with the context in which it was requested.

Approval is a first-class asynchronous state

The most important state in an agent system is not always “running.” A subagent may be waiting for a human to approve an action. The source article explains that such a task appears as waiting, and the user answers on the subagent's own thread rather than interrupting the orchestrator. In Managed Deep Agents on Slack, the approval request can be posted back to the original thread.

This is a sensible separation of responsibilities. The orchestrator should not approve a consequential action on the user's behalf. It also should not be forced to end its current turn merely because one worker is paused. The worker remains alive with its pending interrupt, while the orchestrator can continue coordinating other tasks.

Once the human responds, the worker resumes through a command against its own thread. After it finishes, the normal completion notification returns to the orchestrator. If a worker instead needs guidance from the orchestrator, it should include the question in its result; the orchestrator can then use update_async_task to reply. That distinction prevents a human approval boundary from becoming an implicit channel for agent-to-agent conversation.

The current docs add an important edge case: updating a task while it is waiting drops the pending request, and cancelling it only marks the task as cancelled because no active run exists. A production interface has to make those transitions visible. Otherwise a user can believe they approved or cancelled something that the execution layer no longer considers current.

State has to survive context compaction

The orchestrator's memory of its jobs cannot live only in ordinary tool messages. Deep Agents compact long conversation histories when they approach the context limit. If task IDs and statuses exist only in those messages, summarization can erase the very information needed to collect a result.

For this reason, task metadata is stored in a dedicated state channel separate from the message history. Each task includes its task ID, subagent name, thread ID, current run ID, status, and timestamps. The orchestrator can still call list_async_tasks after several rounds of context compression because the job registry is not part of the summarized prose.

The task ID is deliberately stable. In the reference implementation it is the subagent thread ID, not a value assembled from a thread and a changing run. Updating a job creates a new run on the same thread, so the user and orchestrator can keep using one identifier throughout the task's lifecycle. A raw UUID also discourages the model from trying to parse or rewrite its structure.

Stability alone does not solve stale reasoning. A status printed in an earlier conversation turn may already be obsolete. The middleware prompt therefore needs to establish operational rules: return control to the user immediately after launch, do not poll in a loop, do not trust historical status as current, and never truncate a task ID. These are not cosmetic prompt preferences. They prevent the language model from collapsing an asynchronous protocol back into a blocking one.

Transport and deployment topology

Async subagents communicate through the Agent Protocol. When a supervisor and its workers are deployed together, the LangGraph SDK can use ASGI transport for in-process calls. The workers still run on separate threads with independent state, but calls do not cross the public network. This is the recommended starting point because it removes a network hop and keeps authentication and operations simpler.

Adding a url to a worker definition switches it to HTTP transport and a remote deployment. That is useful when a specialist needs a different compute profile, independent scaling, or separate ownership. A hybrid deployment can keep a general-purpose worker co-deployed while routing a heavier specialist to another server.

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

Choosing a topology changes more than latency. Co-deployed agents share a worker pool, so a supervisor with three active subagents needs four execution slots: one for the orchestrator and three for the workers. Under-provisioning turns an asynchronous launch into a task that sits in the platform queue. A split deployment can isolate that load, but it adds remote authentication, network failures, and independent deployment lifecycle management.

Thread lifecycle is another operational responsibility. Every launch creates a thread that persists until it is deleted. Long-running systems need a retention policy for completed and cancelled jobs, along with tracing that links the orchestrator's tool calls to each worker's run. LangSmith traces can correlate those records through the thread ID, but only if the application preserves the same identity end to end.

Synchronous and asynchronous delegation are different tools

Asynchronous execution is not automatically better for every delegation. A short calculation that the orchestrator needs before its next sentence should remain synchronous. A long research task, a repository migration, or an independent background analysis is a better candidate for async execution.

Dimension Synchronous subagent Asynchronous subagent
Control flow The orchestrator waits for completion. The launch returns a task ID immediately.
Concurrency May be parallel internally, but the caller still blocks. Multiple workers can progress while the orchestrator remains available.
Mid-task steering No standard control channel. Update the live task with interrupt or enqueue semantics.
Cancellation Usually limited to ending the caller. The worker can be cancelled through its own execution record.
State Often scoped to the parent invocation. Persists on a dedicated worker thread.
Context pressure The result is returned to the parent immediately. The orchestrator can collect results on demand and keep a compact job registry.
Operational cost Lower. Higher: queueing, status tracking, cleanup, and event delivery are required.

The practical rule is to choose async delegation when the user should not wait, when work is independent, or when the task must remain steerable. Keep synchronous delegation for results that are prerequisites for the next token-level decision and for operations that finish within the normal latency budget.

Using async subagents

The source article demonstrates the Managed Deep Agents API with a single configuration field. The following example starts a research specialist asynchronously:

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,
        },
    ],
)

In Managed Deep Agents, each served agent also receives an asynchronous copy of the general-purpose subagent. The orchestrator can therefore choose at runtime whether a delegated task should run in the foreground or background without predefining a separate worker for every generic request.

The open-source Deep Agents configuration exposes more explicit controls. The current documentation defines each worker with an AsyncSubAgent specification containing its name, description, graph ID, optional remote URL, update strategy, launch mode, notification setting, and 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 descriptions are part of the routing interface. A vague description such as “helps with stuff” gives the orchestrator no reliable signal for delegation. A specific description should state the domain, the tool or environment the worker can access, and the kind of job for which it should be selected.

The open-source package also supports two launch modes. isolated is the default and sends only the delegated task to the worker. fork sends a snapshot of the parent's effective conversation history so the worker can preserve style or established constraints. Fork mode can improve context continuity, but it is not a copy of the parent's checkpoint or full state. It also increases prompt size and makes cache behavior more sensitive to matching model and prompt configuration.

What asynchronous agents mean for physical AI

LangChain's examples are research and coding agents, but the architecture maps directly onto embodied AI. A robot orchestrator may need to coordinate a mapping job, a perception model, a trajectory planner, a simulation sweep, and a policy evaluation while continuing to answer an operator. Those jobs have different durations and should not all occupy the conversational loop.

The physical world adds constraints that a document or code worker does not have. A robot job cannot be treated as a pure computation that can be retried without consequence. A cancelled task may have already opened a gripper, moved an arm, or changed the state of an object. The task identity must therefore be tied to an idempotency key at the action boundary, and an uncertain completion must block dependent actions until the system reconciles what happened.

Three additional rules become important when applying the async pattern to a robot:

  • Resource ownership must be explicit. Two subagents should not issue motions to the same arm without a queue or lock. Independent camera reads can overlap with motion; competing control commands cannot.
  • Observations need versions. An asynchronous perception result can arrive after the robot has moved. The orchestrator must know which camera pose and robot state produced that result instead of combining an old image with a new geometry.
  • Approval must preserve physical safety. A waiting worker should not be woken by a generic “continue” message that loses the exact action being approved. The approval has to remain bound to the subagent thread, the interrupt, and the intended physical command.

These are extensions of the LangChain pattern, not claims tested in the source article. They explain why the orchestrator-worker split is useful but insufficient by itself for embodied systems. Async execution creates time for additional reasoning; it also creates a window in which external state can change. A robot needs both concurrency and a mechanism for deciding whether the result is still valid.

A useful design divides workers by control authority. Read-only perception, search, and simulation tasks can run broadly in parallel. Planning and evaluation may also overlap. Any worker that can change the physical world needs an exclusive resource grant, a bounded action, an explicit completion state, and a recovery path for uncertain outcomes. The conversation stays asynchronous; control of the hardware remains serialized where safety requires it.

The limits are operational, not just architectural

Async subagents remove the most obvious source of blocking, but they do not remove coordination cost. A background worker still consumes model inference, CPU, memory, and a platform worker slot. More parallel jobs can reduce completion time until the deployment becomes saturated, after which queueing delay dominates. The useful concurrency limit is set by available resources, not by the number of tasks the orchestrator can imagine.

Results can also become stale while the user's goal changes. If a research task was launched under one set of assumptions and the user later imposes a new constraint, the right response may be to interrupt the worker rather than wait for a result that no longer answers the question. This is why update semantics belong in the protocol, not only in application code.

Human approval adds a different kind of latency. A system that can run ten workers but cannot present the right approval request, identify which task is waiting, or resume the correct interrupt does not gain much practical capacity. Notification design and thread-level approval state are as important as raw concurrency.

The state register must also be durable. A task whose ID disappears during context compaction becomes impossible to collect even if the worker is still healthy. A process restart must reconstruct enough information to distinguish running, pending approval, completed, failed, and cancelled jobs. The dedicated task channel is a good foundation because it separates orchestration state from summarizable conversation text.

Finally, observability has to cross the boundary between parent and child. A trace that shows only the orchestrator's tool call proves that a worker was launched, not what the worker did. A trace that shows only the worker run loses the user request that caused it. The shared task or thread ID is the join key that makes the complete system debuggable.

A practical adoption sequence

Teams do not need to turn every subagent asynchronous at once. A safer sequence is:

  1. Start synchronous. Keep short, predictable tasks in the existing loop and establish a clean definition of each worker's tools and output schema.
  2. Move one genuinely long task. Choose a job whose latency is visible to users and whose result is not required for the next conversational turn.
  3. Define the control surface. Decide which updates interrupt, which enqueue, which actions are cancellable, and who may approve a waiting task.
  4. Add durable task state. Store the stable ID, status, current run, parent thread, timestamps, and recovery information outside transient chat text.
  5. Instrument the whole lifecycle. Trace launch, queueing, model calls, waiting, completion, cancellation, and result collection as one linked workflow.
  6. Load-test the worker pool. Measure the concurrent jobs the deployment can sustain before increasing the number of specialist agents or automatic delegation.
  7. Add physical or irreversible actions last. Give them stricter idempotency, resource locking, and approval semantics than read-only background jobs.

The central lesson is that an agent becomes useful over long horizons only when the interface admits that work takes time. A synchronous loop pretends every operation is a short function call. An asynchronous architecture acknowledges long jobs, partial progress, human decisions, and changing instructions as normal parts of the interaction.

That is why "agents are going async" is more than a performance headline. The orchestrator becomes a coordinator of live work, not merely a sequence of model calls. The result can be an agent that stays present with the user while its workers make progress elsewhere. The cost is a richer state machine, and that state machine is now part of the product.

Primary source: LangChain, “Agents Are Going Async”. Additional implementation details were checked against the Deep Agents async subagents documentation and the async-deep-agents reference repository.

Related Articles

As an Amazon Associate, we earn from qualifying purchases.