Agent Series (11): A2A Protocol — How Agents Collaborate with Each Other Google researchers have developed the A2A (Agent-to-Agent) Protocol, a standard for enabling autonomous AI agents to discover and delegate tasks to one another. The protocol allows agents to publish machine-readable "AgentCards" describing their capabilities, enabling an orchestrator to discover agents by skill tags rather than hardcoding function calls. This replaces rigid, direct-function orchestration with a flexible system where agents pass `Task` objects through a defined lifecycle of submitted, working, and completed states. The previous article covered MCP: an Agent connects to tool services via a standard protocol. Tools are passive — they wait to be called, execute, return a result. But some scenarios require delegating to another Agent with autonomous decision-making, not just a tool: When these three Agents need to collaborate, how do they communicate? Who knows who exists? How do they hand off work? This is the problem A2A Agent-to-Agent Protocol solves. MCP: Agent ←→ Tools/Data vertical integration, Agent invokes tools A2A: Agent ←→ Agent horizontal collaboration, Agent delegates to Agent Each Agent publishes an AgentCard describing its capabilities: python from a2a.types import AgentCard, AgentSkill research card = AgentCard research card.name = "research-agent" research card.description = "Gathers factual background on technical topics" skill = AgentSkill skill.id = "research" skill.name = "Research" skill.description = "Collect key facts on a topic" skill.tags.extend "research", "facts" research card.skills.append skill Key AgentCard fields: name , description , skills each skill has tags for discovery . Think of it as the Agent equivalent of OpenAPI Spec — a machine-readable capability declaration that humans can also read. Agents don't pass function calls to each other — they pass Task objects: python from a2a.types import Task, TaskState, TaskStatus, Message, Part, Role task = Task task.id = str uuid.uuid4 task.status.state = TaskState.TASK STATE SUBMITTED Input: a User-role Message msg = Message msg.role = Role.ROLE USER part = Part ; part.text = "Should I use Python or Go?" msg.parts.append part task.history.append msg Task has a lifecycle: SUBMITTED → WORKING → COMPLETED / FAILED . On completion, the Agent appends the result as a ROLE AGENT Message. AgentRegistry stores all registered AgentCards and supports discovery by tag: python class AgentRegistry: def register self, card: AgentCard, handler: Callable Task , Task - None: self. agents card.name = AgentEntry card=card, handler=handler def discover self, tag: str - list AgentCard : """Return all agents whose skills include the given tag.""" ... def delegate self, agent name: str, input text: str - Task: """Create a Task and execute it via the registered handler.""" ... Without a protocol, the orchestrator calls three Python functions directly: php def direct orchestrator question: str - str: research = research agent fn question hard dependency analysis = analysis agent fn research hard dependency answer = writing agent fn analysis hard dependency return answer Real execution output: → calling research agent direct → calling analysis agent direct → calling writing agent direct Answer: Choose Python if you need rapid development with broad libraries. Select Go if performance and concurrency are critical... Works perfectly. The problem is structural: the orchestrator has three hardcoded function references. Replace any one Agent, and you must edit the orchestrator. If the orchestrator lives in a different service, that means a cross-service code change. Register three Agents, each with distinct skill tags: registry registered: research-agent registry registered: analysis-agent registry registered: writing-agent Discovery test: researchers = registry.discover "research" → Found: research-agent — Gathers factual background on technical topics writers = registry.discover "writing" → Found: writing-agent — Composes clear technical prose from analysis output The orchestrator never writes an agent name — it discovers by tag: php def a2a orchestrator question: str - str: researchers = registry.discover "research" t1 = registry.delegate researchers 0 .name, question analysts = registry.discover "analysis" t2 = registry.delegate analysts 0 .name, task output t1 writers = registry.discover "writing" t3 = registry.delegate writers 0 .name, task output t2 return task output t3 Real execution output: → delegating to research-agent discovered via tag → delegating to analysis-agent discovered via tag → delegating to writing-agent discovered via tag Answer: Choose Python for rapid development; Go for high-throughput performance... The critical difference: the orchestrator code contains no agent names. Register a writing-agent-v2 with the same writing tag, and the orchestrator discovers and uses it immediately — zero code changes. A2A's most powerful use case: the LLM reads the AgentCard catalog and decides which Agents to call and in what order . Show the LLM the agent catalog: Available agents: research-agent: Gathers factual background skills: Research research, facts analysis-agent: Analyzes research notes skills: Analysis analysis, tradeoffs writing-agent: Composes technical prose skills: Writing writing, prose LLM outputs an execution plan: "research-agent", "analysis-agent", "writing-agent" Execute the plan: Executing 3 agents: → delegating to research-agent → delegating to analysis-agent → delegating to writing-agent Final answer: Choose Python for rapid development; Go for high-throughput... This is A2A's end state: no pre-configured orchestrator pipeline. The LLM reads the task requirements and the AgentCard descriptions, then plans the collaboration chain at runtime . Dimension MCP A2A ────────────────────────────────────────────────────────────────────── Problem solved Agent ↔ Tool/Data Agent ↔ Agent Discovery list tools tool catalog discover Agent registry Unit of work Tool call sync Task async-ready Coupling Agent invokes tool directly Orchestrator delegates task Other end Passive tool service Autonomous Agent with logic Cross-service Tool is an independent proc Agent is an independent service Who decides Agent tool use Orchestrator delegation Complete four-way selection guide: Scenario Recommended ────────────────────────────────────────────────────────────────────── Same codebase, call target known Direct function call Agent needs external tools MCP tools as service Agent delegates to specialist Agents A2A agents as service Cross-org large-scale Agent network ANP decentralized discovery AgentCard Design description : one sentence about what the Agent is good at — the LLM reads it for routing decisions skill.tags : use semantically clear tags research , analysis , writing , not version numbers or internal IDs AgentCard should be machine-readable and human-readable think OpenAPI style Task Design Task.id : use UUID for tracking and idempotent retry history Message chain for context passing — don't concatenate everything into part.text ROLE USER input from ROLE AGENT output messages Registry and Discovery LLM-Driven Routing Five core takeaways: Up next: Agent Evaluation Framework — how to systematically test Agents, which metrics matter, and how to use DeepEval. Find more useful knowledge and interesting products on my Homepage