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.