Subagents
SubAgentMiddleware gives an orchestrator a single tool, task, taking { description, subagent_type }. Each dispatch runs a real child graph in its own tools:<call_id> namespace, and the child is seeded with the orchestrator's description before it emits its first token.
from deepagents import SubAgent, create_deep_agent
FIELD_RESEARCHER: SubAgent = {
"name": "field-researcher",
"description": "Gathers field elevation and runway length for one airport.",
"system_prompt": "You research airport field data for a dispatch desk. ...",
"tools": [lookup_field_elevation, lookup_runway_length],
}
graph = create_deep_agent(
model=ChatOpenAI(model="gpt-4.1", temperature=0),
system_prompt=(PROMPTS_DIR / "subagents.md").read_text(),
subagents=[FIELD_RESEARCHER, WEATHER_ANALYST],
)Passing subagents is what installs the middleware and, with it, the task tool. The demo's orchestrator is given no lookup tools of its own, so it has no way to answer a question without delegating.
What the demo shows
Ask the dispatch desk about two airports at once and the run fans out. Two task calls go out in a single turn, two child agents work in parallel, and two subagent cards stream side by side in the conversation — each with its own transcript, its own tool calls, and no cross-wiring between them.
The fan-out needs a prompt. A model left to itself will serialize dispatches, so one line in the orchestrator's system prompt changes the shape of the run:
When a request covers more than one airport or more than one kind of data,
issue every dispatch you need in a single turn so the specialists work in
parallel. Do not wait for one to report before sending the next.How it reaches the UI
This is the capability that needs the least work on the client, because it needs none.
task is an ordinary tool call on the wire. What makes it render as a child agent is that the Threadplane subagent tracker recognizes the name — and ['task'] is the tracker's default subagentToolNames. A Deep Agents graph therefore lights the subagent cards with no configuration at all. Naming it explicitly is still worth doing as documentation, and it is required only when a dispatch tool is called something else:
provideAgent({
apiUrl: environment.langGraphApiUrl,
assistantId: environment.assistantId,
// The default. Set it when your dispatch tool is named something else.
subagentToolNames: ['task'],
});Why parallel dispatches attribute cleanly
The tracker registers a dispatch from the task tool call itself — including the subagent_type argument, which becomes the card's name — and then matches the child's tools:<call_id> namespace exactly. Because the dispatch is registered before the child emits anything, attribution is structural rather than a guess from message ordering or text similarity. Two children running at the same time land in two cards because their namespaces differ, not because their output happens to look different.
Each dispatch also carries the orchestrator's description verbatim, and the match on it is exact. Two dispatches with identical descriptions are still distinguished by namespace, but a description that names its subject makes the card readable as well as correct.
What to put in the sidebar
The <chat> composition already renders each dispatch as a <chat-subagent-card> inline and keeps it, collapsed, after completion. A separate tray of active subagents would duplicate that. The demo spends the sidebar on the one thing the cards do not show, which is how wide the fan-out went:
private readonly dispatches = computed(() => [...this.agent.subagents().values()]);
protected readonly dispatchCount = computed(() => this.dispatches().length);
protected readonly runningCount = computed(
() => this.dispatches().filter((subagent) => subagent.status() === 'running').length,
);Note that status is itself a signal on the Subagent record, so it is called rather than read.
A dispatch description is both the child's opening instruction and the label a reader sees on the card. A vague description costs twice: the child starts with less to go on, and the card says less about what is happening.
Next steps
- Planning — the orchestrator's own todo list, which pairs naturally with delegation.
- Chat subagent card — the card component on its own.
- Subgraphs — how namespaced child execution is attributed underneath the tracker.