Skip to content
Blog

Agent Discovery with A2A: How Peers Find Each Other

Implement agent discovery using the A2A protocol. Let agents publish capabilities, discover peers, and collaborate across organizations.

Published on September 8, 2026

AI Assistant

When agents need to collaborate, they first need to find each other. The Agent-to-Agent (A2A) protocol provides a standardized way for agents to publish their capabilities, discover peers, and communicate across organizational boundaries. Think of it as DNS for agents.

Why Agent Discovery Matters

In a single-organization setup, you can hardcode agent endpoints. But in the A2A economy:

  • Agents are distributed across multiple organizations
  • Capabilities change as agents are updated
  • New agents come online dynamically
  • Trust relationships vary between peers

Discovery solves these problems by providing a dynamic, standards-based way for agents to find and connect with each other.

The A2A Discovery Model

Agent Cards

Every agent publishes an Agent Card — a machine-readable description of its capabilities:

{
  "name": "Research Assistant",
  "description": "A research agent that finds and summarizes academic papers",
  "url": "https://research.example.com/a2a",
  "version": "1.0.0",
  "capabilities": {
    "streaming": true,
    "pushNotifications": false,
    "stateTransitionHistory": true
  },
  "skills": [
    {
      "name": "paper_search",
      "description": "Search for academic papers by topic, author, or keyword",
      "inputModes": ["text"],
      "outputModes": ["text", "application/json"]
    },
    {
      "name": "paper_summary",
      "description": "Generate a summary of an academic paper",
      "inputModes": ["text", "application/pdf"],
      "outputModes": ["text"]
    },
    {
      "name": "citation_analysis",
      "description": "Analyze citation networks and impact metrics",
      "inputModes": ["text"],
      "outputModes": ["application/json"]
    }
  ],
  "authentication": {
    "schemes": ["bearer"]
  }
}

Discovery Server

A central registry where agents publish their cards:

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class AgentCard(BaseModel):
    name: str
    description: str
    url: str
    version: str
    capabilities: dict
    skills: list[dict]
    authentication: dict

class DiscoveryServer:
    def __init__(self):
        self.agents: dict[str, AgentCard] = {}
    
    def register(self, agent_id: str, card: AgentCard):
        """Register an agent's capabilities."""
        self.agents[agent_id] = card
    
    def discover(self, skill: str = None, keyword: str = None) -> list[dict]:
        """Find agents matching criteria."""
        results = []
        
        for agent_id, card in self.agents.items():
            match = True
            
            if skill:
                match = match and any(
                    s["name"] == skill for s in card.skills
                )
            
            if keyword:
                match = match and (
                    keyword.lower() in card.description.lower() or
                    keyword.lower() in card.name.lower()
                )
            
            if match:
                results.append({
                    "agent_id": agent_id,
                    "card": card.dict()
                })
        
        return results

discovery = DiscoveryServer()

@app.post("/register/{agent_id}")
async def register_agent(agent_id: str, card: AgentCard):
    discovery.register(agent_id, card)
    return {"status": "registered", "agent_id": agent_id}

@app.get("/discover")
async def discover_agents(skill: str = None, keyword: str = None):
    return {"agents": discovery.discover(skill, keyword)}

Implementing Agent Discovery

Agent Registration

import httpx
from datetime import datetime

class A2AAgent:
    def __init__(self, config: dict):
        self.config = config
        self.card = self._build_card()
    
    def _build_card(self) -> dict:
        return {
            "name": self.config["name"],
            "description": self.config["description"],
            "url": self.config["a2a_endpoint"],
            "version": self.config["version"],
            "capabilities": {
                "streaming": self.config.get("streaming", True),
                "pushNotifications": self.config.get("push_notifications", False),
                "stateTransitionHistory": True,
            },
            "skills": self.config["skills"],
            "authentication": {
                "schemes": ["bearer"]
            }
        }
    
    async def register_with_discovery(self, discovery_url: str):
        """Register this agent with the discovery server."""
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{discovery_url}/register/{self.config['agent_id']}",
                json=self.card
            )
            return response.json()
    
    async def discover_peer(
        self,
        discovery_url: str,
        skill: str = None
    ) -> list[dict]:
        """Find agents with specific capabilities."""
        params = {}
        if skill:
            params["skill"] = skill
        
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{discovery_url}/discover",
                params=params
            )
            return response.json()["agents"]

Peer-to-Peer Communication

class A2AClient:
    def __init__(self):
        self.session = httpx.AsyncClient()
    
    async def send_task(
        self,
        agent_url: str,
        task: dict,
        auth_token: str
    ) -> dict:
        """Send a task to a discovered agent."""
        headers = {"Authorization": f"Bearer {auth_token}"}
        
        response = await self.session.post(
            f"{agent_url}/tasks/send",
            json={
                "jsonrpc": "2.0",
                "method": "tasks/send",
                "params": {
                    "id": str(uuid.uuid4()),
                    "message": {
                        "role": "user",
                        "parts": [
                            {"type": "text", "text": task["instruction"]}
                        ]
                    }
                }
            },
            headers=headers
        )
        
        return response.json()
    
    async def stream_task(
        self,
        agent_url: str,
        task: dict,
        auth_token: str
    ):
        """Stream results from a discovered agent."""
        headers = {"Authorization": f"Bearer {auth_token}"}
        
        async with self.session.stream(
            "POST",
            f"{agent_url}/tasks/sendSubscribe",
            json={
                "jsonrpc": "2.0",
                "method": "tasks/sendSubscribe",
                "params": {
                    "id": str(uuid.uuid4()),
                    "message": {
                        "role": "user",
                        "parts": [
                            {"type": "text", "text": task["instruction"]}
                        ]
                    }
                }
            },
            headers=headers
        ) as response:
            async for line in response.aiter_lines():
                if line.startswith("data: "):
                    yield json.loads(line[6:])

Multi-Agent Collaboration Pattern

class AgentCollaborator:
    def __init__(self, discovery_url: str, auth_manager):
        self.discovery_url = discovery_url
        self.auth = auth_manager
        self.client = A2AClient()
    
    async def collaborate(
        self,
        task: str,
        required_skills: list[str]
    ) -> dict:
        """Find agents and collaborate on a task."""
        # Discover agents with required skills
        available_agents = []
        
        for skill in required_skills:
            agents = await self._find_agents_with_skill(skill)
            available_agents.extend(agents)
        
        # Remove duplicates
        unique_agents = {a["agent_id"]: a for a in available_agents}
        
        # Get auth tokens for each agent
        tasks = []
        for agent_id, agent_info in unique_agents.items():
            token = await self.auth.get_token(agent_id)
            
            task = {
                "agent_id": agent_id,
                "agent_url": agent_info["card"]["url"],
                "auth_token": token,
                "instruction": f"Help with this task: {task}",
            }
            tasks.append(task)
        
        # Execute tasks in parallel
        results = await asyncio.gather(*[
            self.client.send_task(
                t["agent_url"],
                {"instruction": t["instruction"]},
                t["auth_token"]
            )
            for t in tasks
        ])
        
        # Synthesize results
        return self._synthesize_results(results)
    
    async def _find_agents_with_skill(self, skill: str) -> list[dict]:
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{self.discovery_url}/discover",
                params={"skill": skill}
            )
            return response.json()["agents"]
    
    def _synthesize_results(self, results: list[dict]) -> dict:
        # Combine results from multiple agents
        combined = {
            "agent_results": results,
            "synthesis": self._merge_findings(results)
        }
        return combined

Trust and Authentication

class TrustManager:
    def __init__(self):
        self.trusted_agents: dict[str, dict] = {}
        self.revoked_agents: set[str] = set()
    
    def trust_agent(self, agent_id: str, public_key: str, metadata: dict):
        """Register a trusted agent."""
        self.trusted_agents[agent_id] = {
            "public_key": public_key,
            "metadata": metadata,
            "trusted_at": datetime.now().isoformat()
        }
    
    def is_trusted(self, agent_id: str) -> bool:
        """Check if an agent is trusted."""
        return (
            agent_id in self.trusted_agents and
            agent_id not in self.revoked_agents
        )
    
    def verify_signature(self, agent_id: str, message: str, signature: str) -> bool:
        """Verify an agent's message signature."""
        if not self.is_trusted(agent_id):
            return False
        
        agent = self.trusted_agents[agent_id]
        # Verify using the agent's public key
        return verify_signature(agent["public_key"], message, signature)

Fault Tolerance

class ResilientDiscovery:
    def __init__(self, discovery_servers: list[str]):
        self.servers = discovery_servers
        self.cache = {}
    
    async def discover_with_fallback(
        self,
        skill: str = None
    ) -> list[dict]:
        """Try multiple discovery servers."""
        for server in self.servers:
            try:
                result = await self._query_server(server, skill)
                self.cache[skill or "all"] = {
                    "results": result,
                    "timestamp": datetime.now()
                }
                return result
            except Exception as e:
                logger.warning(f"Discovery server {server} failed: {e}")
                continue
        
        # Use cache if all servers fail
        cached = self.cache.get(skill or "all")
        if cached and (datetime.now() - cached["timestamp"]).seconds < 300:
            logger.info("Using cached discovery results")
            return cached["results"]
        
        raise Exception("All discovery servers unavailable")

Conclusion

Agent discovery via A2A transforms isolated agents into a collaborative ecosystem. By publishing Agent Cards, agents declare their capabilities. By querying discovery servers, agents find the right peers. Combined with authentication and fault tolerance, this enables robust cross-organization agent collaboration. Start with a simple discovery server, add trust management as you scale, and always cache results for resilience.