Research date · May 6, 2026
Generated with AI

Agent-to-Agent Communication in AI Agent Frameworks

How agents in LangChain, LangGraph, CrewAI, Google ADK, and Microsoft Semantic Kernel find each other, expose their capabilities to the LLM, and talk — both embedded in the same process and distributed across services.

Four questions every Agent-to-Agent story has to answer

Whether two agents are running in the same process or across continents, the framework has to answer the same four questions before they can collaborate. The differences between LangChain, LangGraph, CrewAI, ADK, and Semantic Kernel are best understood as four different sets of answers.

Agent A (caller) LLM · tools · memory decides "I need help" Discovery explicit list · registry · AgentCard "what other agents exist?" 1 2 Agent B (callee) embedded — same process shared memory, fast calls 3 tool-call · graph edge · transfer process / network boundary Agent B' (remote) different process · different host different runtime · different team 4 A2A protocol · HTTP · JSON-RPC · gRPC · streaming tells A about B
1Discovery
How does Agent A know Agent B exists? Possibilities: an explicit list at construction time, a parent–child agent tree, a runtime registry, or a remote AgentCard fetched over HTTP.
2LLM exposure
Discovery is one thing — telling the LLM is another. Most frameworks surface peer agents as tools the LLM can call (handoff tools, delegate-work tools), or inject their descriptions into the system prompt. The LLM picks who to talk to.
3Embedded comms
When agents share a process: a function call, a shared graph state channel, an event bus, or a "transfer control" action. Microseconds, full type safety, full memory access.
4Distributed comms
When agents are separate services: HTTP requests, JSON-RPC / gRPC, optional streaming / push notifications, auth, retries. Most of the deck now ships with native support for the A2A protocol.

How each framework answers those four questions

For each card: a diagram showing the framework's Agent-to-Agent machinery (in-process on top, network below), the four-axis coverage, and a code-grounded explanation.

Lc

LangChain · agent-as-tool

Wrap any agent (or any Runnable) as a Tool. The other agent's LLM picks it up like any function call.
In-process · Python Agent A · Researcher AgentExecutor tools list: search_tool writer_agent (as a Tool) Tool wrapper name: "writer_agent" description: "writes blog posts from notes" runnable.as_tool() · or hand-rolled Agent B · Writer AgentExecutor a totally separate executor with its own tools + prompt .invoke({"input": ...}) → AgentFinish LLM picks tool tool result .invoke() AgentFinish Distributed · classic AgentExecutor doesn't have native A2A Roll your own: an HTTP tool that POSTs to the remote agent. Or migrate the workflow to LangGraph (deployable) and use RemoteGraph.
"Make Agent B look like a function. Hand it to Agent A. Done."

How it works

LangChain's classic AgentExecutor has no first-class concept of "another agent" — multi-agent collaboration is built by treating the second agent as a Tool. Two patterns are common:

  • Hand-rolled wrapper — a Tool whose _run method calls other_agent.invoke({"input": ...}) and returns the string output.
  • Runnable.as_tool() — any LangChain Runnable (including a compiled agent) can be converted to a Tool with .as_tool(name=..., description=...) (defined in langchain_core/runnables/base.py).

Agent A's LLM "discovers" Agent B exactly the same way it discovers any other tool: name + description in the tool list. It picks the call by emitting a tool_call. The framework dispatches synchronously, in-process.

Axis coverage

1 · Discovery

Manual: you build the tool list when constructing each agent.

2 · LLM exposure

Native — wrapped agent appears as a tool with name + description, indistinguishable from a normal function tool.

3 · Embedded

Synchronous in-process call. Fast, simple, no scheduling.

4 · Distributed

Not in the framework. You'd write an HTTP-tool yourself or migrate to LangGraph.

Protocol on the wire

None native. In-process Python calls only. For network access: roll your own HTTP tool that POSTs to the remote agent — auth, retries, streaming all on you.

Code anchor

libs/core/langchain_core/runnables/base.py · Runnable.as_tool()
libs/langchain/langchain_classic/agents/agent.py · AgentExecutor

Lg

LangGraph · subgraphs & RemoteGraph

Each agent compiles into a Runnable. Use it as a node in a supervisor graph — locally or via HTTP to a deployed graph.
Embedded · Supervisor StateGraph supervisor LLM with handoff tools: handoff_to_researcher handoff_to_writer researcher (subgraph) create_react_agent → Runnable writer (subgraph) create_react_agent → Runnable state.messages (channel) add_messages reducer checkpointer makes it durable all subgraphs read & append read/write Distributed · RemoteGraph speaks the LangGraph Server API parent graph add_node(remote) looks like a local node RemoteGraph(assistant_id, url=...) PregelProtocol over HTTP client streaming, checkpoints, interrupts: all preserved Remote graph (deployed) LangGraph Platform / self-hosted server /threads · /runs · /assistants endpoints HTTP
"Every agent compiles to a Runnable. Plug Runnables into a bigger graph — locally with subgraphs, remotely with RemoteGraph."

How it works

In-process. Each agent is built with create_react_agent (in langgraph.prebuilt — now deprecated in favour of create_agent in the langchain package), or any custom StateGraph, and compiled to a Runnable. A "supervisor" StateGraph adds those Runnables as nodes; conditional edges or handoff tools (in the langgraph-supervisor / langgraph-handoff add-ons) decide who runs next. All nodes share the graph's state channels — the messages channel with add_messages reducer is the universal "shared scratchpad."

Distributed. RemoteGraph(assistant_id, url=...) from langgraph/pregel/remote.py implements the same PregelProtocol as a local graph — but every method (invoke, stream, get_state) talks to a LangGraph Server over HTTP. You can drop a RemoteGraph directly into a parent graph as a sub-node; the call site doesn't know it's remote.

Axis coverage

1 · Discovery

Explicit at compile-time. The supervisor's edges or handoff-tool list defines which agents exist.

2 · LLM exposure

Handoff tools — one tool per worker, with auto-generated descriptions. Or create_react_agent(name=...) for use as a sub-node.

3 · Embedded

Shared state channels with reducers. Native streaming, parallel Send, durable checkpointer.

4 · Distributed

RemoteGraph over the LangGraph Server API. Same Pregel surface; HTTP under the hood.

Protocol on the wire

LangGraph Server API over HTTP — REST endpoints (/threads, /runs, /assistants) plus streaming. Auth via API key or custom headers. RemoteGraph exposes the same PregelProtocol as a local graph, so the call site is identical.

Code anchor

libs/langgraph/langgraph/pregel/remote.py · RemoteGraph
libs/prebuilt/langgraph/prebuilt/chat_agent_executor.py · create_react_agent(name=...)
(separate libs) langgraph-supervisor, langgraph-handoff

Cr

CrewAI · DelegateWorkTool + built-in A2A protocol

In-process: a manager LLM gets a delegate tool per coworker. Across the wire: a full A2A client/server module ships in core.
Embedded · Crew with Process.hierarchical Manager Agent LLM with auto-generated AgentTools = [DelegateWorkTool, AskQuestionTool] per coworker picks coworker by role DelegateWorkTool args task: str — what to do context: str — what we know coworker: str — by role/name → runs that agent's task Researcher role · goal · backstory Writer role · goal · backstory Distributed · crewai/a2a/* — built on the official a2a-python SDK Local Agent A2AConfig + wrapper a2a/wrapper.py injects delegate calls into the agent A2A Protocol Layer AgentCard fetch (URL → JSON) JSON-RPC · gRPC transports Auth: APIKey · HTTPDigest · OAuth Streaming · push notifications · polling Extensions (a2ui, custom) Remote Agent (any A2A server) Could be another CrewAI agent. Could be ADK. Could be your own. Discovery: AVAILABLE_AGENTS_TEMPLATE
"Hand each agent a role and a goal. The manager LLM gets one delegate-tool per coworker. Cross the wire? Same code, A2A under the hood."

How it works

In-process. A Crew with Process.hierarchical automatically wires the manager agent with AgentTools(agents=...) — that constructs a DelegateWorkTool and an AskQuestionTool for each coworker. The manager's LLM sees a single tool whose schema accepts (task, context, coworker) — coworkers are identified by their role string. Agents themselves are described to the manager via their role / goal / backstory triples.

Distributed. CrewAI ships a sizeable crewai/a2a/ module (auth, transport, wrapper, agent_card, extensions, updates) built on the official a2a-python SDK (you'll see from a2a.types import AgentCard throughout). Discovery: fetch_agent_card(url) pulls the remote agent's AgentCard. Transport: JSON-RPC over HTTPS or gRPC. Auth: APIKey, HTTP Digest, OAuth schemes. Updates: streaming, push notifications, polling — all built in.

Axis coverage

1 · Discovery

Local: list of agents in the Crew. Remote: AgentCard fetch by URL.

2 · LLM exposure

Local: DelegateWorkTool with role-based dispatch. Remote: prompt templates inject available remote agents into the prompt.

3 · Embedded

Synchronous task chaining; outputs propagate via _get_context().

4 · Distributed

Full A2A protocol, with multiple transports and auth schemes.

Protocol on the wire

A2A protocol, built on the official a2a-python SDK. Three transports negotiated at runtime: JSONRPC (default), gRPC, and HTTP+JSON — all enumerated in TransportType. Updates: streaming, push notifications, and polling for long-running calls. Auth schemes in a2a/auth/client_schemes.py: APIKey, HTTP Basic, HTTP Digest, Bearer Token, OAuth2 (client-credentials and authorization-code).

Code anchor

lib/crewai/.../tools/agent_tools/delegate_work_tool.py · DelegateWorkTool
lib/crewai/.../a2a/wrapper.py · A2A delegation injection
lib/crewai/.../a2a/utils/agent_card.py · fetch_agent_card
lib/crewai/.../a2a/utils/transport.py · JSON-RPC / gRPC

Ad

Google ADK · agent tree + RemoteA2aAgent

In-process: parent–child tree with transfer_to_agent. Across the wire: A2A protocol, AgentCard, Starlette server.
Embedded · agent tree (parent_agent ↔ sub_agents) root: LlmAgent sub_agents: [resA, resB] AutoFlow injects: transfer_to_agent tool agent_transfer.request_processor injects sub-agent metadata into the prompt transfer_to_agent(agent_name) action on Event → Runner switches active agent researcher name + description writer name + description Session shared event log agents append to it durable via SessionService Distributed · A2A protocol (a2a-python SDK) RemoteA2aAgent looks like a regular Agent from the parent's POV drop into sub_agents[…] A2A protocol agent_card: AgentCard | URL | path A2AClient over httpx part converters: GenAI ↔ A2A parts request interceptors · sessions @a2a_experimental Remote ADK agent (server) to_a2a(agent) wraps it as A2AStarletteApplication (FastAPI/Starlette) AgentCardBuilder auto-generates the card from name · description · skills
"Locally: a tree of agents and a built-in transfer_to_agent action. Remotely: an AgentCard, a Starlette server, and the same A2A protocol."

How it works

In-process. Every BaseAgent has a parent_agent and sub_agents: list[BaseAgent] field (agents/base_agent.py:124,135). When an LlmAgent uses AutoFlow, the agent_transfer.request_processor injects the sub-agents' names and descriptions into the prompt context, and the LLM gets a transfer_to_agent(agent_name) tool. Calling it emits a transfer_to_agent action; the Runner sees it and switches the active agent for the next turn. State is shared via the Session event log.

Distributed. RemoteA2aAgent (agents/remote_a2a_agent.py:109) takes an AgentCard, URL, or file path; resolves it; and exposes itself as a regular BaseAgent to the parent. Under the hood it uses A2AClient from the official a2a-python SDK. To publish an ADK agent: a2a/utils/agent_to_a2a.py wraps it in an A2AStarletteApplication; AgentCardBuilder auto-generates an AgentCard from the agent's metadata.

Axis coverage

1 · Discovery

Local: parent_agent/sub_agents tree. Remote: AgentCard from URL or file.

2 · LLM exposure

Auto-injected transfer_to_agent(agent_name) tool with names/descriptions of sub-agents.

3 · Embedded

Shared Session event log. Runner switches active agent on transfer action.

4 · Distributed

RemoteA2aAgent client + to_a2a() server, both on the official A2A SDK.

Protocol on the wire

A2A protocol over HTTP, built on the official a2a-python SDK. Server-side: A2AStarletteApplication (Starlette / FastAPI). Client-side: A2AClient on top of httpx. Pluggable request interceptors; sessions and part converters (GenAI ↔ A2A parts) are framework-managed. Currently @a2a_experimental.

Code anchor

src/google/adk/agents/base_agent.py · parent_agent, sub_agents
src/google/adk/agents/remote_a2a_agent.py · RemoteA2aAgent
src/google/adk/a2a/utils/agent_to_a2a.py · server wrapper
src/google/adk/a2a/utils/agent_card_builder.py · auto card generation
src/google/adk/tools/transfer_to_agent_tool.py · in-process transfer

Sk

Microsoft Semantic Kernel · five orchestration patterns

Sequential · Concurrent · GroupChat · Handoffs · Magentic. Agents talk through an actor-style runtime.
Embedded · pick an orchestration pattern, hand it a list of agents Sequential a → b → c pipeline of agents simple, deterministic Concurrent a, b, c in parallel all see the same input aggregate the answers GroupChat SelectionStrategy picks who speaks next TerminationStrategy stops Handoffs OrchestrationHandoffs: Agent A → {B, C, ...} handoff = function call Magentic Manager LLM plans a task ledger of agent capabilities All five run on the same actor-style runtime AgentActorBase (one per agent) subscribes to a TopicId CoreRuntime · message bus in_process default · pluggable ChatHistory broadcast every agent sees the conversation @kernel_function tools / handoff fns Distributed · runtime is pluggable, but no built-in remote-agent client For cross-process workflows, SK ships the separate Process Framework. Native A2A is on the roadmap. Today you'd wrap remote agents with a custom Agent subclass that does the HTTP call.
"Pick a recipe — Sequential, Concurrent, GroupChat, Handoffs, or Magentic. The runtime treats every agent as an actor on a shared message bus."

How it works

Five orchestration patterns. semantic_kernel/agents/orchestration/ ships sequential.py, concurrent.py, group_chat.py, handoffs.py, and magentic.py — each subclasses OrchestrationBase[TIn, TOut]. Discovery is explicit: every pattern takes a list of agents at construction.

Handoffs use an OrchestrationHandoffs dict ({source: {target: description, …}, …}) — for each source agent, the framework auto-generates a kernel function per target so the LLM can call "transfer to AgentB" by description. Magentic has a StandardMagenticManager LLM that produces a task ledger of agent capabilities and decides who runs each step. GroupChat uses a SelectionStrategy + TerminationStrategy — both can be kernel functions, so the "who's next" decision is itself an LLM call.

Runtime. Underneath, every agent is wrapped in a subclass of AgentActorBase (concrete subclasses per pattern, e.g. for handoffs and Magentic) that subscribes to a TopicId on the CoreRuntime message bus. The default runtime is InProcessRuntime; the architecture is pluggable but no out-of-the-box remote runtime ships in the agents module today.

Axis coverage

1 · Discovery

Explicit list passed to the orchestration constructor. OrchestrationHandoffs dict declares allowed paths.

2 · LLM exposure

Auto-generated kernel functions per handoff target. Magentic builds a task ledger from agent metadata.

3 · Embedded

Actor model on CoreRuntime, broadcast ChatHistory, broadcast queue.

4 · Distributed

No built-in remote-agent client in the agents module. Use the separate Process Framework or roll a custom remote Agent.

Protocol on the wire

None in agents/ today. The runtime is pluggable but only in_process ships out of the box. For cross-process orchestration, use the separate Process Framework (its own SK component) — or wrap a remote agent in a custom Agent subclass that does the HTTP itself.

Code anchor

python/semantic_kernel/agents/orchestration/{sequential,concurrent,group_chat,handoffs,magentic}.py
python/semantic_kernel/agents/runtime/{core,in_process}/
python/semantic_kernel/agents/group_chat/broadcast_queue.py

Side-by-side · the four Agent-to-Agent axes

Read down a column for one framework's whole Agent-to-Agent story; read across to see how each framework answers the same question.

LangChain LangGraph CrewAI Google ADK Semantic Kernel
1 · Discovery Manual tool list per agent Compile-time graph topology / handoff tools Crew(agents=[...]) · remote: AgentCard URL sub_agents tree · remote: AgentCard Constructor arg list · OrchestrationHandoffs dict
2 · LLM exposure Tool name + description (Runnable.as_tool) Handoff tools per worker · create_react_agent(name=...) DelegateWorkTool(task, context, coworker) Auto-injected transfer_to_agent(agent_name) tool Auto-generated kernel function per handoff · Magentic ledger
3 · Embedded comms Synchronous in-process call Shared messages channel + reducer Task chaining + tool result pass-through Shared Session; Runner switches active agent on transfer Actor model · CoreRuntime message bus · ChatHistory broadcast
4 · Distributed comms Not native — DIY HTTP tool RemoteGraph over LangGraph Server API (HTTP) Native A2A · JSONRPC / gRPC / HTTP+JSON · streaming / push / polling · auth Native A2A · RemoteA2aAgent + to_a2a() server No built-in remote-agent client; Process Framework for cross-process
A2A protocol support — LangGraph Server API (HTTP, framework-specific) Yes — a2a-python SDK in crewai/a2a/ Yes — a2a-python SDK in adk/a2a/ Not yet (in-flight)
Auth on the wire — API key / headers via get_client APIKey · HTTP Basic / Digest · Bearer · OAuth2 (a2a/auth/) Pluggable interceptors · auth schemes via SDK —
Streaming / async updates Callback handlers Native streaming over HTTP via RemoteGraph Streaming · push notifications · polling — all built in Native event streaming via the runner Streaming via invoke_stream at the agent level
Code anchor libs/core/.../runnables/base.py · as_tool libs/langgraph/langgraph/pregel/remote.py lib/crewai/.../a2a/ + tools/agent_tools/ src/google/adk/a2a/ + agents/remote_a2a_agent.py python/semantic_kernel/agents/orchestration/

Architect's cheat sheet

Six things to weigh when picking a framework for a multi-agent system.

DISCOVERY

1 · Static vs. dynamic discovery is an architectural choice

Most frameworks make you list agents at construction. Only true A2A clients (CrewAI, ADK) give you runtime discovery via AgentCard URLs — which is what you actually want for a service-mesh of agents.

EXPOSURE

2 · Tool-call exposure beats free-text chat

Frameworks that surface peer agents as typed tools (LangGraph handoff tools, ADK transfer_to_agent, CrewAI DelegateWorkTool, SK auto-gen handoff functions) are more reliable than ones that drop "Available agents: ..." into the prompt. Schema-typed delegation is just easier for the LLM to get right.

STATE

3 · Shared state ≠ shared understanding

LangGraph's messages channel and ADK's Session give every agent the full conversation. CrewAI passes only the previous task's output forward. SK's GroupChat broadcasts. Pick the model that matches your privacy / context-window / coordination tradeoff.

SAME-AS-LOCAL

4 · "Looks like a local agent" is a great test

The cleanest distributed stories are the ones where the remote agent has the same API as a local one. LangGraph (RemoteGraph = local StateGraph) and ADK (RemoteA2aAgent = local BaseAgent) nail this. CrewAI gets there via wrapper injection.

RISK

5 · Multi-agent doesn't mean multi-vendor — yet

In practice, almost every "multi-agent" deployment in 2026 is still single-framework. A2A protocol changes that, but slowly. For now, picking one framework and using its native multi-agent primitives will outperform a heterogeneous mesh on observability, debugging, and cost.

PROTOCOLS

6 · What each framework speaks on the wire

LangChain: in-process only — no native wire protocol.
LangGraph: LangGraph Server API over HTTP (REST + streaming) via RemoteGraph; auth via API key / headers.
CrewAI: A2A protocol — three transports negotiated at runtime (JSONRPC, gRPC, HTTP+JSON); streaming, push notifications, polling for long-running calls; rich auth (APIKey, HTTP Basic, HTTP Digest, Bearer Token, OAuth2).
Google ADK: A2A protocol over HTTP — server is an A2AStarletteApplication, client is A2AClient on httpx; pluggable request interceptors.
Semantic Kernel: nothing in agents/ today — for cross-process orchestration, use the separate Process Framework.