Skip to content

feat(sdk): add async subagent middleware for remote LangGraph servers#1758

Merged
Mason Daugherty (mdrxy) merged 48 commits intomainfrom
open-swe/598550c7-9d68-8519-3f24-5a92b98bea26
Mar 16, 2026
Merged

feat(sdk): add async subagent middleware for remote LangGraph servers#1758
Mason Daugherty (mdrxy) merged 48 commits intomainfrom
open-swe/598550c7-9d68-8519-3f24-5a92b98bea26

Conversation

@hwchase17
Copy link
Copy Markdown
Contributor

@hwchase17 Harrison Chase (hwchase17) commented Mar 10, 2026

Adds AsyncSubAgentMiddleware to the SDK, enabling agents to launch background jobs on remote LangGraph deployments via the LangGraph SDK. Unlike synchronous subagents that block until completion, async subagents return a job ID immediately — the main agent can continue working, check progress on demand, send follow-up instructions, or cancel jobs.

The CLI reads async subagent definitions from [async_subagents] in ~/.deepagents/config.toml and wires them in automatically.

Changes

  • Add AsyncSubAgentMiddleware in deepagents/middleware/async_subagents.py with five tools: launch_async_subagent, check_async_subagent, update_async_subagent, cancel_async_subagent, and list_async_subagent_jobs — each with sync + async implementations that return Command objects to persist job state
  • Track jobs via AsyncSubAgentState with a _jobs_reducer that merges updates into a dict[str, AsyncSubAgentJob], surviving context compaction
  • Cache LangGraph SDK clients per (url, headers) tuple in _ClientCache to avoid reconnecting on every tool call
  • Add async_subagents parameter to create_deep_agent in graph.py — when provided, appends AsyncSubAgentMiddleware to the middleware stack
  • Add load_async_subagents() in the CLI to parse config.toml and pass specs through to create_cli_agent in both interactive and server modes
  • Wire HITL interrupt config for launch_async_subagent, update_async_subagent, and cancel_async_subagent in the CLI's approval system
  • Export AsyncSubAgent, AsyncSubAgentJob, and AsyncSubAgentMiddleware from deepagents and deepagents.middleware

@github-actions github-actions bot added deepagents Related to the `deepagents` SDK / agent harness internal User is a member of the `langchain-ai` GitHub organization feature New feature/enhancement or request for one size: L 500-999 LOC cli Related to `deepagents-cli` labels Mar 10, 2026
if name not in self._async:
spec = self._agents[name]
self._async[name] = get_client(
url=spec["url"],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this default to None? In most cases we want to use the asgi transport, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

url is now NotRequired in AsyncSubAgent and both get_sync and get_async use spec.get("url"), so omitting it falls through to ASGI transport. Does that make sense? Can you confirm?

return error
spec = agent_map[subagent_type]
client = clients.sync(subagent_type)
thread = client.threads.create()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooc: why do we need sync methods? Can we use the single async client and avoid the ssl cache hit and avoid the fact that sync client doesn't support asgi transport?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we can just remove them?


from deepagents_cli.agent import load_async_subagents

async_subagents = load_async_subagents() or None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooc can we load from the server?

@github-actions github-actions bot added size: XL 1000+ LOC and removed size: L 500-999 LOC labels Mar 12, 2026
@open-swe
Copy link
Copy Markdown

open-swe bot commented Mar 12, 2026

Reading this PR to understand the changes and architecture.

@colifran Colin Francis (colifran) force-pushed the open-swe/598550c7-9d68-8519-3f24-5a92b98bea26 branch 4 times, most recently from 0e3c580 to ba35fd7 Compare March 16, 2026 18:56
Open SWE (open-swe) and others added 16 commits March 16, 2026 11:58
Introduces AsyncSubAgentMiddleware that uses the LangGraph SDK to launch,
monitor, and update background runs on remote LangGraph deployments.
Wired into create_deep_agent via a new async_subagents parameter.
The LangGraph Platform server requires the x-auth-scheme header to know
which auth flow to use. Without it, requests fail with "Missing agent
builder trigger auth scheme, and passthrough headers." Now included by
default on all SDK client connections.
Adds load_async_subagents() to read [async_subagents] from config.toml
and passes them through create_cli_agent to create_deep_agent. Wired
into interactive mode, non-interactive mode, and model hot-swap paths.
The JSON job_id format caused JSONDecodeError when LLMs decomposed the
structured data instead of passing it verbatim. Switched to an opaque
thread_id::run_id format that LLMs treat as an atomic string. The parser
is robust and accepts the canonical format, full launch output, and
legacy JSON for backwards compatibility.
The update tool was using update_state which only appended a message
without triggering a new run. After the original run finished, checking
the job would show "success" with the update message as the result.

Now update creates a new run on the same thread so the subagent
processes the follow-up with full conversation history. Returns a new
job_id for tracking. Also encodes agent name in job_id for correct
client routing with multiple agent types.
When update_async_subagent is called while a run is still active, the
default "reject" strategy would reject the new run. Now uses "interrupt"
so the current run is stopped and the follow-up is processed immediately.
Adds launch_async_subagent and update_async_subagent to the CLI's
interrupt_on map, matching the existing behavior for the sync task tool.
check_async_subagent is read-only and doesn't require approval.
…returns

Tools now accept ToolRuntime and return Command(update={...}) to write
AsyncSubAgentJob records to the async_subagent_jobs state field, ensuring
job metadata survives context compaction.
@colifran Colin Francis (colifran) force-pushed the open-swe/598550c7-9d68-8519-3f24-5a92b98bea26 branch from d2652ec to acc9437 Compare March 16, 2026 18:58
@mdrxy Mason Daugherty (mdrxy) changed the title feat(sdk): add async subagents via remote langgraph servers feat(sdk): add async subagent middleware for remote LangGraph servers Mar 16, 2026
@colifran Colin Francis (colifran) marked this pull request as ready for review March 16, 2026 19:35
mdrxy

This comment was marked as resolved.

key = self._cache_key(spec)
if key not in self._async:
self._async[key] = get_client(
url=spec.get("url"),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in what circumstances does this work when url is None? if we deploy both the deepagent and a dedicated async subagent through langgraph.json, would this work?

@mdrxy Mason Daugherty (mdrxy) merged commit 0c5d501 into main Mar 16, 2026
29 checks passed
@mdrxy Mason Daugherty (mdrxy) deleted the open-swe/598550c7-9d68-8519-3f24-5a92b98bea26 branch March 16, 2026 21:01
Mason Daugherty (mdrxy) added a commit that referenced this pull request Mar 16, 2026
PR #1758 landed CLI code that passes `async_subagents` to
`create_deep_agent()`, but the CLI still pins SDK 0.4.x which doesn't
accept that kwarg — meaning even `async_subagents=None` raises
`TypeError` at runtime. This switches to a conditional `**kwargs`
pattern so the kwarg is only passed when async subagents are actually
configured, keeping the CLI compatible with the pinned SDK until 0.5.0
ships.
james8814 pushed a commit to james8814/deepagents that referenced this pull request Mar 17, 2026
…langchain-ai#1758)

Adds `AsyncSubAgentMiddleware` to the SDK, enabling agents to launch
background jobs on remote LangGraph deployments via the LangGraph SDK.
Unlike synchronous subagents that block until completion, async
subagents return a job ID immediately — the main agent can continue
working, check progress on demand, send follow-up instructions, or
cancel jobs.

The CLI reads async subagent definitions from `[async_subagents]` in
`~/.deepagents/config.toml` and wires them in automatically.

## Changes
- Add `AsyncSubAgentMiddleware` in
`deepagents/middleware/async_subagents.py` with five tools:
`launch_async_subagent`, `check_async_subagent`,
`update_async_subagent`, `cancel_async_subagent`, and
`list_async_subagent_jobs` — each with sync + async implementations that
return `Command` objects to persist job state
- Track jobs via `AsyncSubAgentState` with a `_jobs_reducer` that merges
updates into a `dict[str, AsyncSubAgentJob]`, surviving context
compaction
- Cache LangGraph SDK clients per `(url, headers)` tuple in
`_ClientCache` to avoid reconnecting on every tool call
- Add `async_subagents` parameter to `create_deep_agent` in `graph.py` —
when provided, appends `AsyncSubAgentMiddleware` to the middleware stack
- Add `load_async_subagents()` in the CLI to parse `config.toml` and
pass specs through to `create_cli_agent` in both interactive and server
modes
- Wire HITL interrupt config for `launch_async_subagent`,
`update_async_subagent`, and `cancel_async_subagent` in the CLI's
approval system
- Export `AsyncSubAgent`, `AsyncSubAgentJob`, and
`AsyncSubAgentMiddleware` from `deepagents` and `deepagents.middleware`

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Co-authored-by: Colin Francis <colin.francis@langchain.dev>
Co-authored-by: Colin Francis <colinfrancis53@gmail.com>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
james8814 pushed a commit to james8814/deepagents that referenced this pull request Mar 17, 2026
…chain-ai#1931)

PR langchain-ai#1758 landed CLI code that passes `async_subagents` to
`create_deep_agent()`, but the CLI still pins SDK 0.4.x which doesn't
accept that kwarg — meaning even `async_subagents=None` raises
`TypeError` at runtime. This switches to a conditional `**kwargs`
pattern so the kwarg is only passed when async subagents are actually
configured, keeping the CLI compatible with the pinned SDK until 0.5.0
ships.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli Related to `deepagents-cli` deepagents Related to the `deepagents` SDK / agent harness feature New feature/enhancement or request for one internal User is a member of the `langchain-ai` GitHub organization size: XL 1000+ LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants