Multi-Agent Orchestration Patterns: Diagrams and Framework Support (2026)

TL;DR
Seven multi-agent patterns, one diagram each, and which framework ships each natively: OpenAI Agents SDK, LangGraph, Mastra, AI SDK, ADK, CrewAI, A2A v1.0.
Last updated: October 17, 2026
Every multi-agent system is built from one of two moves: call a specialist and get its answer back (agent-as-tool, orchestrator-worker, supervisor, subagents), or give the conversation away (handoffs, swarm). A2A is the same delegation stretched across a network boundary. The verdict: start with agent-as-tool or subagents, because every major framework ships them natively; reach for handoffs when the specialist has to talk to the user directly; and use A2A only when the other agent belongs to another service or another team.
This post is the mechanism-level reference. For each pattern you get a diagram, the native primitive in each framework with its current version, the deprecations from the last six months, and the A2A wire details. If you want the broader vocabulary first (debate, harness, hierarchical teams), read 7 AI agent orchestration patterns and come back.
Official Sources#
| Resource | What it confirms |
|---|---|
| Anthropic: Building effective agents | Orchestrator-workers definition |
| OpenAI Agents SDK: multi-agent and handoffs | Agents as tools vs handoffs, transfer_to_<agent_name> |
| LangChain multi-agent docs | Subagents, handoffs, router, skills, custom workflow |
| Mastra subagents and A2A | Supervisor agents, A2AAgent, network deprecation |
| AI SDK subagents | Subagents as tools, toModelOutput |
| Google ADK collaboration and ADK 2.0 | sub_agents modes, graph Workflow runtime |
| CrewAI processes and A2A delegation | Process.hierarchical, A2AClientConfig |
| Microsoft Agent Framework orchestrations | Sequential, Concurrent, Handoff, GroupChat, Magentic builders |
| A2A specification and changelog | v1.0 operations, bindings, task states |
| Claude Agent SDK subagents | agents option, nesting and concurrency limits |
The two primitives under every pattern#
Two framework docs draw the line explicitly, and they agree.
Google ADK separates Agent-as-a-Tool, where Agent B's answer is passed back to Agent A and A keeps control, from a sub-agent, where responsibility for answering is transferred to Agent B (ADK function tools docs). The OpenAI Agents SDK says the same thing in different words: with Agent.as_tool() a manager calls specialists and keeps control of the conversation, while with a handoff the specialist becomes the active agent for the rest of the turn (OpenAI multi-agent docs).
%% title: Agent-as-tool vs handoff
flowchart LR
subgraph AsTool[Agent-as-tool: delegate and return]
direction TB
M1[Manager] -->|tool call| S1[Specialist]
S1 -->|result| M1
M1 -->|owns the reply| U1[User]
end
subgraph Handoff[Handoff: transfer control]
direction TB
M2[Triage] -->|transfer_to_specialist| S2[Specialist]
S2 -->|owns the reply| U2[User]
endEverything below is a composition of these two moves:
- Delegate and return: orchestrator-worker, supervisor, subagents, agent-as-tool. One agent owns the final answer.
- Transfer control: handoffs and swarm. Ownership of the conversation moves.
- Across a network: A2A. Either move, but the other agent is an opaque service you discover through an Agent Card.
Both moves are usually implemented as tool calls. The difference is what happens after the tool returns: in delegate-and-return, the caller reads the result and keeps going; in a handoff, the framework swaps which agent runs next. That is why the OpenAI docs note the two can be combined in one system.
Pattern 1: Orchestrator-worker#
Anthropic defines orchestrator-workers as a workflow where "a central LLM dynamically breaks down tasks, delegates them to worker LLMs, and synthesizes their results" (Building effective agents). The key difference from plain parallelization is that the subtasks are not predefined: the orchestrator decides them from the input. Anthropic's example is a coding product that has to change multiple files, where the number and nature of the edits depend on the task.
%% title: Orchestrator-worker with subagents
flowchart TD
U[User task] --> O[Orchestrator]
O -->|plan subtasks at runtime| P{How many?}
P --> W1[Worker: file A]
P --> W2[Worker: file B]
P --> W3[Worker: tests]
W1 -->|summary only| S[Orchestrator synthesizes]
W2 -->|summary only| S
W3 -->|summary only| S
S --> R[Final answer]Native support: the AI SDK documents it as the "Orchestrator-Worker" workflow pattern (AI SDK workflows); LangChain's subagents architecture lets the main agent invoke several subagents in one turn; the Claude Agent SDK's agents option and Mastra's supervisor agents both give you the orchestrator role directly.
Here is the AI SDK version as a code reviewer. The orchestrator gets a reviewFile tool, and the tool's execute runs a separate ToolLoopAgent with its own context window. The abortSignal passes through so cancelling the parent cancels the worker.
import { ToolLoopAgent, stepCountIs, tool } from 'ai';
import { z } from 'zod';
// Worker: runs in its own context window.
const reviewer = new ToolLoopAgent({
model: 'anthropic/claude-sonnet-5.5',
instructions: 'Review one file for bugs. Reply with a short list of findings.',
stopWhen: stepCountIs(10),
});
// Orchestrator: decides which files need review and fans out.
const orchestrator = new ToolLoopAgent({
model: 'openai/gpt-6-luna',
instructions:
'Plan the code review. Call reviewFile once per changed file, then merge the findings.',
tools: {
reviewFile: tool({
description: 'Review a single file and return findings.',
inputSchema: z.object({ path: z.string(), diff: z.string() }),
execute: async ({ path, diff }, { abortSignal }) => {
const result = await reviewer.generate({
prompt: `File: ${path}\n\n${diff}`,
abortSignal,
});
return result.text; // only the summary goes back to the orchestrator
},
}),
},
stopWhen: stepCountIs(20),
});
const { text } = await orchestrator.generate({
prompt: 'Review the diff in this PR: ...',
});
console.log(text);
Type-checked against ai@7.0.143 and zod@4.6.7.
Gotcha: the AI SDK docs explicitly advise against subagents for simple tasks, because each one adds latency and complexity (AI SDK subagents). If the decomposition is always the same three steps, a fixed pipeline or Promise.all is cheaper and easier to debug than a model deciding the plan every time.
Pattern 2: Supervisor#
A supervisor is an orchestrator that lives for the whole conversation. LangChain's docs say the main agent in the subagents architecture is "often referred to as a supervisor" and draw a useful distinction: a supervisor is a full agent that keeps conversation context, while a router is a single classification step that dispatches and then gets out of the way (LangChain subagents).
Native support, and what changed this year:
- LangChain / LangGraph: the recommended path is now the subagents pattern built on
create_agent(Python) orcreateAgent(JS). The docs state that "the langgraph-supervisor package is no longer actively maintained" and link a migration fromcreate_supervisorto subagents. The package still installs (@langchain/langgraph-supervisor1.1.3 on npm,langgraph-supervisor0.0.31 on PyPI, last uploaded 2025-11-19), but new code should follow the migration. - Mastra: supervisor agents arrived in
@mastra/core1.8.0. You list subagents in the parentAgent'sagentsproperty, and the parent uses its instructions plus each subagent'sdescriptionto decide when to delegate. Delegation hooks such asonDelegationStartcan intercept, modify or reject a delegation (Mastra subagents). Agent networks (.network()) are deprecated and will be removed in a future major release, with supervisor agents as the replacement (Mastra networks). - CrewAI:
Process.hierarchical, which requires eithermanager_llmor a custommanager_agenton theCrew(CrewAI processes). - Microsoft Agent Framework:
MagenticBuilderandGroupChatBuilderinagent-framework-orchestrationsgive you a manager-driven team (MAF orchestrations). - Google ADK: a coordinator
Agentwithsub_agents. ADK auto-injects a delegation tool for each subagent (ADK collaboration).
The Mastra version is short. Note that one of the subagents here is a remote A2A agent, which we come back to in Pattern 7:
import { Agent } from '@mastra/core/agent';
import { A2AAgent } from '@mastra/core/a2a';
const billingAgent = new Agent({
id: 'billing-agent',
name: 'Billing agent',
description: 'Answers invoice, plan and payment questions.',
instructions: 'Answer billing questions using the account data you are given.',
model: 'openai/gpt-6-astra',
});
// A remote agent owned by another team, reached over A2A v1.0.
const refundsService = new A2AAgent({
id: 'refunds-service',
name: 'Refunds service',
description: 'Issues and tracks refunds. Owned by the payments team.',
url: 'https://payments.example.com/api/.well-known/refunds/agent-card.json',
protocolVersion: '1.0',
});
export const supportLead = new Agent({
id: 'support-lead',
name: 'Support lead',
instructions:
'Delegate billing questions to the billing agent and refunds to the refunds service. Write the final reply yourself.',
model: 'anthropic/claude-sonnet-5.5',
agents: { billingAgent, refundsService },
});
const res = await supportLead.generate('Why was I charged twice, and can I get one back?');
console.log(res.text);
Type-checked against @mastra/core@1.78.0. The description fields are not decoration: they are the routing signal the supervisor reads.
The router variant. If every request goes to exactly one specialist and there is no follow-up synthesis, a router is cheaper than a supervisor. LangChain documents a Router pattern (a classification step dispatches, then results are synthesized), and an ADK coordinator with narrowly described subagents behaves like a dispatcher. Use a router for intake and a supervisor when the lead agent has to remember earlier turns or combine several specialists' answers.
Our earlier multi-agent systems in TypeScript post covers the router and supervisor in plain code. Its framework paragraph predates the langgraph-supervisor and Mastra network changes above.
Pattern 3: Handoffs#
A handoff is a tool call that changes who is driving. In the OpenAI Agents SDK, handoffs are exposed to the model as tools named transfer_to_<agent_name> by default (for example transfer_to_refund_agent). handoff() accepts tool_name_override, tool_description_override, on_handoff, input_type and input_filter, and each agent can carry a handoff_description that hints when to pick it (OpenAI handoffs). LangChain credits OpenAI with coining the term.
%% title: Handoff transfers the conversation
sequenceDiagram
participant U as User
participant T as Triage agent
participant R as Refund agent
U->>T: I was charged twice
T->>T: calls transfer_to_refund_agent
Note over T,R: history passes through input_filter
T-->>R: control transfers
R->>U: What is the order id?
U->>R: 8812
R->>U: Refund issued
Note over T: triage is not in the loop anymoreHere is a TypeScript triage agent that uses both primitives at once: a summarizer as a tool, and a refund specialist as a handoff.
import { Agent, handoff, run } from '@openai/agents';
import { z } from 'zod';
// Agent-as-tool: the triage agent calls it and keeps control.
const summarizer = new Agent({
name: 'Ticket summarizer',
instructions: 'Summarize the customer ticket in two sentences.',
});
// Handoff target: owns the rest of the turn once selected.
const refundAgent = new Agent({
name: 'Refund agent',
handoffDescription: 'Handles refund requests and billing disputes.',
instructions: 'Collect the order id, check eligibility, and explain next steps.',
});
const triage = Agent.create({
name: 'Triage agent',
instructions:
'Summarize long tickets with the summarizer tool. Hand off refund requests.',
tools: [
summarizer.asTool({
toolName: 'summarize_ticket',
toolDescription: 'Summarize a long support ticket.',
}),
],
handoffs: [
handoff(refundAgent, {
inputType: z.object({ reason: z.string() }),
onHandoff: (_ctx, input) => console.log('handoff reason:', input?.reason),
}),
],
});
const result = await run(triage, 'I was charged twice for order 8812.');
console.log(result.lastAgent?.name); // "Refund agent" if the handoff fired
console.log(result.finalOutput);
Type-checked against @openai/agents@0.20.0 and zod@4.6.7. Agent.create (rather than new Agent) keeps the output type correct when handoff targets have different output types. result.lastAgent tells you who ended the turn, which is what you persist so the next user message goes to the right agent. The OpenAI Agents SDK TypeScript guide covers guardrails and tracing around this.
Other native implementations:
- LangGraph: a handoff is a tool that returns a
Command(fromlanggraph.typesor@langchain/langgraph) whoseupdatechanges state such asactive_agentorcurrent_step(LangChain handoffs). - Microsoft Agent Framework:
HandoffBuilder. If you call no.add_handoff(), every participant can hand off to every other (a mesh). By default each user response returns to the agent that asked for it. - Google ADK: a sub-agent in
'chat'mode (the default) takes over with full user interaction and returns manually. Ingoogle-adk2.12.0 the source describes chat-mode agents as reachable viatransfer_to_agent.
The MAF builder, with explicit routes instead of the default mesh:
from agent_framework import Agent
from agent_framework.orchestrations import HandoffBuilder
def build_support_workflow(triage: Agent, billing: Agent, refunds: Agent):
return (
HandoffBuilder(name="support")
.participants([triage, billing, refunds])
.with_start_agent(triage)
.add_handoff(triage, [billing, refunds])
.add_handoff(billing, [refunds])
.build()
)
Passed py_compile and an import check against agent-framework-orchestrations 1.3.2 (with agent-framework-core 1.22.0).
Gotchas: the receiving agent sees the conversation history unless you filter it, so use input_filter (OpenAI) or an explicit state update (LangGraph) when the previous agent's tool chatter would confuse the next one. And because no single agent owns the final answer, output guardrails have to run on every agent that can reply to the user.
Pattern 4: Swarm#
A swarm is handoffs with no central router. The @langchain/langgraph-swarm README defines it as agents that "dynamically hand off control to one another based on their specializations," with the system remembering the last active agent so the next user message resumes there.
Native support:
- LangGraph:
@langchain/langgraph-swarm(1.0.4) shipscreateSwarmandcreateHandoffTool; the Python package islanggraph-swarm0.1.0. The README notes the library has not yet been tested with the newcreateAgentand recommends the prebuiltcreateReactAgentfor now, so check that before building on it. - OpenAI: the original Swarm repo is labelled "experimental, educational" and says it "is now replaced by the OpenAI Agents SDK." Agents SDK handoffs, with each agent listing the others it can transfer to, are the production successor.
- Microsoft Agent Framework:
HandoffBuilderwithout explicit routes gives you the same all-to-all topology.
Swarms suit conversational routing, such as a support line where the user drifts between billing, account and technical questions, because each specialist talks to the user directly and hands off when the topic changes. The tradeoff is global control: there is no single place to enforce a budget, apply a guardrail or read a coherent trace, so plan for tracing on every transfer from day one.
Pattern 5: Subagents (context isolation)#
Subagents are delegate-and-return with one specific goal: a fresh context window. The subagent starts clean, does a lot of reading or tool calls, and only its final message comes back. LangChain's docs say subagents are stateless by default, which is exactly what gives each one context isolation.
The Claude Agent SDK is the most explicit about this. Subagents are defined through the agents option of query(), keyed by name, using AgentDefinition fields such as description, prompt, tools, disallowedTools, model, skills, mcpServers, maxTurns, background and effort. The Agent tool must be in allowedTools to auto-approve spawning, and only the subagent's final message returns to the parent (Claude Agent SDK subagents).
import { query } from '@anthropic-ai/claude-agent-sdk';
for await (const message of query({
prompt: 'Fix the failing tests in packages/billing, then review the change.',
options: {
allowedTools: ['Read', 'Edit', 'Bash', 'Grep', 'Agent'],
agents: {
'test-runner': {
description: 'Runs the test suite and reports failures. Use after edits.',
prompt: 'Run the tests, then report only failing tests and their errors.',
tools: ['Bash', 'Read', 'Grep'],
model: 'sonnet',
maxTurns: 20,
},
'code-reviewer': {
description: 'Reviews a diff for bugs and missing tests.',
prompt: 'Review the current diff. Do not edit files.',
tools: ['Read', 'Grep'],
effort: 'high',
},
},
maxBudgetUsd: 5,
env: {
...process.env,
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH: '2',
CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS: '4',
},
},
})) {
if (message.type === 'result') console.log(message.subtype);
}
Type-checked against @anthropic-ai/claude-agent-sdk@0.3.302 (a misspelled maxTurnz was rejected by tsc, so the option names are checked).
Limits you should know about. Claude Agent SDK subagents can spawn their own subagents. On TS SDK v0.3.219+ and Python SDK v0.2.127+, the defaults are:
| Setting | Default | Notes |
|---|---|---|
CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH | 3 layers | 1 disables nesting |
CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS | 20 | Over the limit returns "Concurrent subagent limit reached" |
maxBudgetUsd / max_budget_usd | none | No spend cap unless you set one |
The TS SDK replaces the subprocess environment with env, which is why the example spreads process.env first; otherwise variables such as PATH and your API key are not passed through.
Google ADK gets at the same idea through subagent modes: 'chat' (full user interaction, manual return), 'task' (can ask clarifying questions, returns automatically) and 'single_turn' (no user interaction, returns automatically, can run in parallel) (ADK collaboration). single_turn is ADK's closest match to a context-isolated worker.
For the Claude Code product side, where the same subagents show up next to agent teams and workflows, see Claude Code subagents vs agent teams vs workflows.
Pattern 6: Agent-as-tool#
Agent-as-tool is the smallest unit in this list, and most of the patterns above are built from it. The specialist looks like any other function to the caller.
- OpenAI Agents SDK (JS):
agent.asTool()creates a function tool with a singleinputparameter by default, runs the sub-agent in a nestedRunner, and returns the last message or the output ofcustomOutputExtractor. Options includeneedsApproval,isEnabled,parameters(a Zod or JSON Schema for structured input),inputBuilder,includeInputSchemaandresumeState(OpenAI JS tools guide). - Google ADK:
AgentTool(agent=...)in Python,new AgentTool({agent})in TypeScript. - Microsoft Agent Framework:
Agent.as_tool()(withapproval_modeandarg_name, verified by inspectingagent-framework-core1.22.0). - AI SDK: a
tool()whoseexecutecallsagent.generate(), as in Pattern 1.toModelOutputcontrols what the parent model sees, and an async-generatorexecutestreams preliminary results to the UI. - LangChain: wrap a
create_agentagent inside a@toolfunction.
ADK's version, combined with a single_turn subagent and a remote A2A agent so you can see all three side by side:
from google.adk.agents import Agent
from google.adk.tools.agent_tool import AgentTool
from google.adk.a2a.agent import RemoteA2aAgent
# Agent-as-a-tool: the coordinator gets the answer back and keeps control.
diff_summarizer = Agent(
name="diff_summarizer",
model="gemini-3.5-flash",
instruction="Summarize a code diff in three bullet points.",
)
# Sub-agent in single_turn mode: no user interaction, returns automatically.
test_triager = Agent(
name="test_triager",
model="gemini-3.5-flash",
description="Classifies CI test failures as flaky, regression, or infra.",
instruction="Classify each failing test and give a one-line reason.",
mode="single_turn",
)
# A remote agent another team runs, discovered through its Agent Card.
release_notes = RemoteA2aAgent(
name="release_notes",
description="Drafts release notes. Owned by the docs team.",
agent_card="https://docs-agents.example.com/.well-known/agent-card.json",
)
root_agent = Agent(
name="release_coordinator",
model="gemini-3.5-flash",
instruction=(
"Prepare a release. Summarize the diff with the tool, triage failing "
"tests, then ask release_notes for a draft."
),
tools=[AgentTool(agent=diff_summarizer)],
sub_agents=[test_triager, release_notes],
)
Passed py_compile and an import check against google-adk[a2a]==2.12.0 and a2a-sdk 1.2.2. Constructing RemoteA2aAgent prints an [EXPERIMENTAL] warning, covered below.
When to prefer it over a handoff: when one agent should own the final answer. The OpenAI docs point out that agents-as-tools keep the manager in control, which means guardrails, formatting and tone are applied in one place. Use a handoff when the specialist genuinely needs a multi-turn exchange with the user.
Pattern 7: A2A between services#
Everything so far runs inside one process and one codebase. A2A (Agent2Agent) is for when the other agent does not: a different team, a different language, a different deployment. It is an open source project under the Linux Foundation, contributed by Google and licensed Apache 2.0, built so that opaque agents can collaborate "without needing access to each other's internal state, memory, or tools" (A2A repo). It reuses HTTP, JSON-RPC 2.0 and Server-Sent Events, and it is async-first.
Versions: A2A v1.0.0 shipped 2026-03-12 and v1.0.1 on 2026-05-26 (changelog). v1.0 separated the application protocol from its transport bindings, renamed supportsAuthenticatedExtendedCard to supportsExtendedAgentCard, adopted the spelling canceled, dropped the implicit and password OAuth flows in favor of device code and PKCE, and added ListTasks with filtering and pagination.
What it adds over an in-process subagent:
- Discovery: an Agent Card at
https://{server_domain}/.well-known/agent-card.jsondescribing skills, endpoints and auth. - Long-running tasks with states:
TASK_STATE_SUBMITTED,WORKING,COMPLETED,FAILED,CANCELED,INPUT_REQUIRED,REJECTEDandAUTH_REQUIRED.COMPLETED,FAILED,CANCELEDandREJECTEDare terminal;INPUT_REQUIREDandAUTH_REQUIREDare interrupted states the client can resume (a2a.proto). - Streaming over SSE with
TaskStatusUpdateEventandTaskArtifactUpdateEvent, plus push notifications for very long jobs. - Enterprise auth: security schemes for API keys, HTTP auth, OAuth 2.0, OpenID Connect and mutual TLS, declared in the card.
%% title: A2A task between two services
sequenceDiagram
participant C as Client agent
participant S as Remote agent
C->>S: GET /.well-known/agent-card.json
S-->>C: Agent Card with skills and auth
C->>S: SendStreamingMessage
S-->>C: Task SUBMITTED
S-->>C: TaskStatusUpdateEvent WORKING
S-->>C: TaskStatusUpdateEvent INPUT_REQUIRED
C->>S: SendMessage with same taskId
S-->>C: TaskArtifactUpdateEvent
S-->>C: TaskStatusUpdateEvent COMPLETEDThe core operations, from spec section 5.3 (gRPC method names match the JSON-RPC ones):
| Operation | JSON-RPC method | REST endpoint |
|---|---|---|
| Send message | SendMessage | POST /message:send |
| Send streaming message | SendStreamingMessage | POST /message:stream |
| Get task | GetTask | GET /tasks/{id} |
| List tasks | ListTasks | GET /tasks |
| Cancel task | CancelTask | POST /tasks/{id}:cancel |
| Subscribe to task | SubscribeToTask | POST /tasks/{id}:subscribe |
| Get extended Agent Card | GetExtendedAgentCard | GET /extendedAgentCard |
Push notification config has its own Create/Get/List/Delete methods under /tasks/{id}/pushNotificationConfigs.
Version gotcha: v1.0 clients must send an A2A-Version header, and the spec says a server treats an empty header as 0.3. If your client and server disagree on the version, you get a VersionNotSupportedError rather than silent misbehavior, but only if the client actually sends the header.
MCP vs A2A in one line. The A2A docs frame MCP as vertical (more tools and resources for one agent) and A2A as horizontal (agent to agent): "MCP gives each agent depth, and A2A gives your system reach" (A2A and MCP). Most real systems use both.
Native support today:
- Mastra:
A2AAgentwraps a remote A2A agent as a subagent (see Pattern 2). It defaults to protocol v0.3;protocolVersion: '1.0'opts into v1.0 and sendsA2A-Version: 1.0. Served Mastra agents publish their card at/api/.well-known/<agent-id>/agent-card.jsonand execute at/api/a2a/<agent-id>(Mastra A2A). - Google ADK:
RemoteA2aAgentto consume,to_a2a(root_agent)to serve a Starlette app. ADK's A2A support is marked Experimental in Python, Go and Java; the runtime warning adds that "A2A protocol and SDK are themselves not experimental." - CrewAI:
pip install 'crewai[a2a]', thenAgent(a2a=A2AClientConfig(endpoint=..., timeout=..., max_turns=...))as a client andA2AServerConfigto serve. The olderA2AConfigis deprecated and will be removed in v2.0.0 (CrewAI A2A). - LangSmith Agent Server: an A2A endpoint at
/a2a/{assistant_id}speaking the v1.0 JSON-RPC binding (plus four v0.3 method names for compatibility).SubscribeToTaskand push notification config are not supported yet and return-32601(LangSmith A2A). - Microsoft Agent Framework: ships
a2aandhosting-a2apackages in the repo. - No native A2A: the OpenAI Agents SDK, the Claude Agent SDK and AI SDK core. The AI SDK lists a community
a2a-ai-provider. In all three, the practical approach is to wrap an A2A client call in a tool, which turns a remote agent back into Pattern 6.
The reference SDKs are a2a-sdk 1.2.2 on PyPI and @a2a-js/sdk 1.3.0 on npm.
Which framework supports which pattern natively#
Versions as of October 17, 2026. "Native" means a documented, named primitive; "via tools" means you write the delegation yourself, usually in a few lines.
| Framework | Agent-as-tool | Orchestrator / supervisor | Handoff | Swarm | Parallel fan-out | A2A |
|---|---|---|---|---|---|---|
| OpenAI Agents SDK (JS 0.20.0, Py 0.24.0) | Native: asTool() / as_tool() | Via tools (manager agent) | Native: handoffs, handoff() | Via handoffs (Swarm successor) | Via code (asyncio.gather) | No |
| LangChain + LangGraph (JS 1.5.x, Py 1.2.16) | Native: subagent wrapped in @tool | Native: subagents pattern [2] | Native: tool returning Command | Native: createSwarm | Native: several subagents per turn | Native: LangSmith /a2a/{assistant_id} |
| Mastra (1.78.0) | Via supervisor agents | Native: agents: {...} [1] | Via tools (guide pattern) | No [1] | Via workflows (Council pattern) | Native: A2AAgent (0.3 default, 1.0 opt-in) |
| Vercel AI SDK (7.0.143) | Native: subagent tool | Via tools (Orchestrator-Worker pattern) | No | No | Via code (Parallel Processing pattern) | Community: a2a-ai-provider |
| Claude Agent SDK (TS 0.3.302, Py 0.2.168) | Native: Agent tool | Native: agents option | No | No | Native: concurrent subagents (20 default) | No |
| Google ADK (Py 2.12.0, TS 2.3.0) | Native: AgentTool | Native: sub_agents coordinator | Native: 'chat' mode transfer | No | Native: Workflow, single_turn subagents [3] | Native (Experimental): RemoteA2aAgent, to_a2a |
| CrewAI (1.15.28) | Via tools | Native: Process.hierarchical | No | No | Via code | Native: A2AClientConfig / A2AServerConfig [4] |
| Microsoft Agent Framework (1.22.0) | Native: Agent.as_tool() | Native: MagenticBuilder, GroupChatBuilder | Native: HandoffBuilder | Native: HandoffBuilder mesh | Native: ConcurrentBuilder | Native: a2a, hosting-a2a |
Deprecations to watch:
- Mastra agent networks (
.network()) are deprecated in favor of supervisor agents. langgraph-supervisoris "no longer actively maintained"; LangChain documents a migration to subagents.- In
google-adk2.12.0, constructingSequentialAgentorParallelAgentemits aDeprecationWarning: "deprecated in favor of Workflow and will be removed in a future version. Workflow cannot yet be used as an LlmAgent sub-agent." ADK 2.0 moved to a graph-based Workflow runtime (Python GA May 19, 2026; Go June 30; TypeScript August 21). - CrewAI's
A2AConfigis deprecated and will be removed in v2.0.0.
Fairness note: each column reflects documented primitives, not quality. A "via tools" cell is not a gap so much as a design choice; the AI SDK, for example, deliberately keeps delegation as plain tools.
How to choose#
Walk these questions in order and stop at the first yes:
- Is the other agent another team's service, or written in another stack? Use A2A. Inside your own codebase, an in-process subagent is simpler and cheaper.
- Does the specialist need to talk to the user over several turns? Use a handoff. If topics drift unpredictably between several specialists, a swarm.
- Are the subtasks unknown until you see the input? Orchestrator-worker.
- Is the work going to flood the context window (large codebases, long logs, many tool calls)? Subagents, so only summaries come back.
- Must one agent own the final answer and apply guardrails once? Agent-as-tool, or a supervisor if it has to remember the conversation.
- None of the above? A single agent with the right tools. LangChain's own docs note that this often does the job.
Cost check. Every delegate-and-return hop is at least one extra model call. LangChain's pattern comparison counts 4 model calls for a one-shot request with subagents versus 3 for handoffs or a router, since the main agent has to process the subagent's result before it answers (LangChain multi-agent). That overhead buys control and a single point for guardrails; decide whether you need it.
Long-running orchestrations. Once a run spans minutes or hours, or crosses an A2A boundary with INPUT_REQUIRED pauses, persistence and retries matter more than the pattern. See durable AI agent frameworks compared.
FAQ#
What is the difference between a handoff and an agent-as-tool?#
With agent-as-tool, the caller gets the specialist's answer back and keeps control of the conversation. With a handoff, the specialist becomes the active agent and answers the user directly for the rest of the turn. ADK and the OpenAI Agents SDK both document this split, and both are implemented as tool calls under the hood.
Is a supervisor the same as a router?#
No. LangChain's docs describe a supervisor as a full agent that keeps conversation context and can combine several specialists, while a router is a single classification step that dispatches a request. A router is cheaper per request; a supervisor handles follow-ups and synthesis.
Do I need A2A if I already use MCP?#
Only if you need to call agents that you do not run in-process. MCP gives one agent more tools and resources; A2A lets separate agents, often owned by different teams, delegate tasks to each other with discovery, task states and streaming. The A2A docs put it as "MCP gives each agent depth, and A2A gives your system reach."
Which frameworks support A2A v1.0 today?#
Mastra (A2AAgent with protocolVersion: '1.0'; v0.3 is the default), LangSmith Agent Server (v1.0 JSON-RPC, without SubscribeToTask or push notifications yet), Google ADK (Experimental RemoteA2aAgent and to_a2a), CrewAI (A2AClientConfig and A2AServerConfig) and Microsoft Agent Framework (a2a and hosting-a2a packages). Check each one's protocol version against your counterpart, since a missing A2A-Version header means 0.3.
How do I stop subagents from spawning out of control?#
In the Claude Agent SDK, nesting defaults to 3 layers and 20 concurrent subagents, with no spend cap unless you set one. Set CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH (1 disables nesting), CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS and maxBudgetUsd (max_budget_usd in Python). In other frameworks, cap steps on each worker (for example stopWhen: stepCountIs(10) in the AI SDK) and pass the parent's abort signal through.
Sources#
- https://www.anthropic.com/engineering/building-effective-agents
- https://openai.github.io/openai-agents-python/multi_agent/
- https://openai.github.io/openai-agents-python/handoffs/
- https://openai.github.io/openai-agents-js/guides/tools/
- https://github.com/openai/swarm
- https://docs.langchain.com/oss/python/langchain/multi-agent
- https://docs.langchain.com/oss/python/langchain/multi-agent/subagents
- https://docs.langchain.com/oss/python/langchain/multi-agent/handoffs
- https://docs.langchain.com/langsmith/server-a2a
- https://www.npmjs.com/package/@langchain/langgraph-swarm
- https://mastra.ai/docs/subagents
- https://mastra.ai/docs/agents/networks
- https://mastra.ai/docs/connections/a2a
- https://mastra.ai/docs/guides/multi-agent-systems
- https://ai-sdk.dev/docs/agents/subagents
- https://ai-sdk.dev/docs/agents/workflows
- https://code.claude.com/docs/en/agent-sdk/subagents
- https://google.github.io/adk-docs/workflows/collaboration/
- https://google.github.io/adk-docs/2.0/
- https://google.github.io/adk-docs/a2a/quickstart-consuming/
- https://google.github.io/adk-docs/tools-custom/function-tools/
- https://docs.crewai.com/en/concepts/processes
- https://docs.crewai.com/en/learn/a2a-agent-delegation
- https://github.com/microsoft/agent-framework/tree/main/python/packages/orchestrations
- https://a2a-protocol.org/latest/specification/
- https://github.com/a2aproject/A2A/blob/main/CHANGELOG.md
- https://github.com/a2aproject/A2A/blob/main/specification/a2a.proto
- https://a2a-protocol.org/latest/topics/a2a-and-mcp/
Continue Reading#
- 7 AI Agent Orchestration Patterns Developers Should Know - the broader pattern vocabulary, including debate and harness designs
- Claude Code Subagents vs Agent Teams vs Workflows - how subagents show up in the Claude Code product
- Microsoft Agent Framework Developer Guide 2026 - a deeper look at the Sequential, Concurrent, Handoff, GroupChat and Magentic builders
- LlamaIndex in 2026: Workflows, Agents, and What Changed
- CrewAI Flows vs Crews in Late 2026: What Is New and When to Use Each
- Microsoft Agent Framework in October 2026: What Changed Since 1.0
- MCP vs A2A vs Function Calling: Which Layer Does What in 2026 - when agent-to-agent delegation needs a protocol and when a function call will do
- OpenAI Agents SDK in Late 2026: What Is New in Python and TypeScript - handoffs vs agents-as-tools in the current Python and TypeScript releases
Get the next deep dive like this in your inbox
One email a week on AI Agents and the rest of the AI dev stack. Free.
Read next on AI coding tools
7 AI Agent Orchestration Patterns Developers Should Know
Seven AI agent orchestration patterns for production, from single-agent baselines to multi-level hierarchies, each with a decision rule, sketch and tradeoffs.
10 min readMulti-Agent Systems: How to Orchestrate Multiple AI Agents in TypeScript
From swarms to pipelines - here are the patterns for coordinating multiple AI agents in TypeScript applications.
6 min readSubagents vs Agent Teams vs Workflows: Claude Code's Parallelism Primitives, Compared
Claude Code subagents vs agent teams vs workflows: who holds the plan, the hard limits from the docs, and which parallelism primitive fits your task.
9 min readNew here? Start with
Technical content at the intersection of AI and development. Building with AI agents, Claude Code, and modern dev tools - then showing you exactly how it works.







