Agent-to-Agent Communication
Agents coordinate through discovery, capability descriptions, task delegation and hand-off, choosing between shared state and message passing; A2A-style protocols cover agent↔agent the way MCP covers agent↔tool.
The primitives
Whatever the topology, agents need a few primitives. Discovery: how does agent A learn that agent B exists and what it can do? Capability description: a machine-readable "card" (name, skills, input/output modes, auth requirements, endpoint). Delegation: sending a task with an id, input, and constraints. Hand-off: transferring ownership of an ongoing task or conversation. Status/streaming: progress events while a long task runs.
A2A-style protocols standardise these across vendors: an agent publishes a capability card at a well-known URL; a client sends a task; the server agent returns task state (submitted, working, input-required, completed, failed) and streams artifacts. The details differ between protocols; the primitives do not.
- A2A vs MCP: A2A is agent↔agent — tasks, multi-turn, long-running, opaque reasoning on the other side. MCP is agent↔tool — single calls with schemas, synchronous results, no reasoning on the tool side.
- An agent can be both: it consumes MCP servers for its tools and exposes an A2A-style card so other agents can delegate to it.
- Neither protocol decides *whether* to delegate; that remains the calling agent's judgment and your eval's job.
Shared state vs message passing
Shared state (a blackboard: one document, database row or state object all agents read and write) is simple to reason about and lets any agent see everything. It couples agents to a schema, needs locking or versioning for concurrent writes, and turns every write into potential context for every reader — including injected content.
Message passing (each agent only sees messages addressed to it, typically via a queue) decouples agents, makes traces linear per agent, and bounds what each sees. It needs explicit routing, ordering guarantees and dead-letter handling, and "why did agent C do that" requires correlating messages across queues.
Rule of thumb: shared state for tightly coupled agents on one task in one process (a workflow graph's state object); message passing for independent agents, different processes or teams, and anything long-running.
1type TaskMessage = {2 taskId: string // idempotency key for the receiving agent3 from: string; to: string4 skill: 'summarize' | 'extract-entities'5 input: { text: string }6 constraints: { maxTokens: number; deadlineMs: number }7 replyTo: string // queue name for the result/status events8}9 10await queue.publish('agents.extractor', {11 taskId: crypto.randomUUID(), from: 'planner', to: 'extractor',12 skill: 'extract-entities', input: { text: doc }, constraints: { maxTokens: 4000, deadlineMs: 20_000 },13 replyTo: 'agents.planner.inbox',14} satisfies TaskMessage)Streaming to the UI
Users cannot wait 40 s for silence. Agents should emit a typed event stream — run.started, tool.called, tool.result, text.delta, handoff, approval.required, run.finished — that the UI renders progressively and that doubles as the trace. Server-sent events or WebSockets carry it; the important design choice is that events are structured, not free text, so the UI can render an approval button or a tool card (Human-in-the-Loop Overview).
The same event stream is what an orchestrating agent consumes from a delegated agent: progress, partial artifacts, and input-required states that need to bubble up to a human.
Key points
- Primitives: discovery, capability card, delegation, hand-off, status streaming.
- A2A-style = agent↔agent tasks; MCP = agent↔tool calls. An agent can do both.
- Shared state is simple but coupled; message passing is decoupled but needs routing and ordering.
- Every task carries an id (idempotency), constraints (budget, deadline) and a reply channel.
- Emit structured events for UI and trace; free-text progress is useless to both.
When to use — and when not to
- Agents owned by different teams or running in different processes.
- Long-running tasks that need progress and
input-requiredstates. - You want to swap a delegated agent without changing the caller.
- Agents in one process on one task — a workflow state object is enough.
- Only one agent exists; a protocol between one party is overhead.
- Latency-critical paths where a queue hop is unaffordable.
Failure modes
- Messages without task ids → duplicate work after retries.
- Shared state written by an agent that read injected content, poisoning all readers.
- Unbounded queues with no deadlines; tasks run long after the user left.
- Free-text status that neither the UI nor the trace can interpret.
- Capability cards that overstate skills; the caller delegates to an agent that cannot do the job.
Tradeoffs
Message passing improves per-agent debuggability but cross-agent correlation needs trace ids on every message.