Skip to main content
Version: Next

A2A Outbound Adapter Example

Note: Use a unique agent name and seed when starting SingleA2AAdapter or MultiA2AAdapter. The uAgent address is derived from {name}_seed when you omit seed. Reusing a common name collides with other local agents.

Inbound vs outbound: this page is outbound — a uAgent that calls out to A2A HTTP specialists and exposes them on Agentverse via mailbox/chat. For the reverse (A2A clients calling into Agentverse uAgents), see the A2A Inbound Adapter Example.

The A2A Outbound Adapter connects uAgents to A2A HTTP servers. You can:

  • Run specialist A2A servers (a2a_servers) and a coordinating uAgent in one process
  • Route chat from ASI:One / Agentverse to those specialists
  • Discover specialists via A2A Agent Cards (/.well-known/agent-card.json)

Whether you wrap one specialist (SingleA2AAdapter) or several (MultiA2AAdapter), the adapter handles chat protocol, health checks, and HTTP forwarding.


Prerequisites


Features

Agent management

  • Configure specialists with A2AAgentConfig and start them with a2a_servers
  • Health-check Agent Cards at runtime (MultiA2AAdapter)
  • Chat protocol on the uAgent (mailbox) plus A2A HTTP on separate ports

Routing (MultiA2AAdapter only)

The routing_strategy argument accepts two values. Anything else falls through to keyword matching:

routing_strategyWhat the code does
keyword_match (default)Score specialties / keywords / skills. priority is a score multiplier, not a separate strategy. If llm_api_key is set, _route_by_keywords tries an LLM pick first, then falls back to keyword scores.
round_robinRotate through healthy agents.
any other string (including "llm" or "priority_based")Not a dedicated path — same as keyword_match.

There is no priority_based strategy string. Setting routing_strategy="llm" does not skip keyword scoring; LLM selection runs inside keyword routing when a key is present.

Reliability

  • Health checking and Agent Card discovery
  • Optional fallback_executor when no specialist matches
  • Timeouts on A2A HTTP calls

Installation

Use a virtual environment. Pin both the adapter extra and a2a-sdk below 1.0. Adapter 0.6.2 imports A2AStarletteApplication from a2a.server.apps. That module was removed in a2a-sdk 1.x (v0.3 → v1.0 migration), so an unpinned install that resolves a2a-sdk==1.1.2 fails with ModuleNotFoundError: a2a.server has no attribute apps.

macOS / Linux:

python3 -m venv .venv
source .venv/bin/activate
pip install "uagents==0.25.5" "uagents-adapter[a2a-outbound]==0.6.2" "a2a-sdk[all,sql,sqlite]>=0.2.11,<1.0"

Windows (PowerShell):

python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install "uagents==0.25.5" "uagents-adapter[a2a-outbound]==0.6.2" "a2a-sdk[all,sql,sqlite]>=0.2.11,<1.0"

Verified combo: uagents==0.25.5 + uagents-adapter[a2a-outbound]==0.6.2 + a2a-sdk 0.3.x (>=0.2.11,<1.0). After install, from a2a.server.apps import A2AStarletteApplication and from uagents_adapter import SingleA2AAdapter should succeed.

The extra name is a2a-outbound (not a2a-outbond and not a2a). A typo extra does not install a2a-sdk.


Class reference

There is no A2AAdapter class and no asi_api_key constructor argument. Export these from uagents_adapter (or uagents_adapter.a2a_outbound): SingleA2AAdapter, MultiA2AAdapter, A2AAgentConfig, a2a_servers.

A2AAgentConfig

Dataclass fields only (invalid kwargs such as a2a_port or executor_class raise TypeError):

ParameterTypeDefaultDescription
namestrrequiredSpecialist name; must match a key in the executors dict passed to a2a_servers
descriptionstrrequiredHuman-readable summary
urlstrrequiredBase URL used for health checks (for example http://localhost:10020)
portintrequiredPort for this specialist’s A2A HTTP server
specialtieslist[str]requiredDomains used for tags and keyword generation
skillslist[str] | Noneauto from specialtiesSkill ids
exampleslist[str] | NoneautoSample prompts
keywordslist[str] | NoneautoRouting keywords
priorityint1Keyword-score multiplier (higher prefers this agent)

Executors are not fields on the config. Pass a separate dict[str, AgentExecutor] to a2a_servers.

config_example.py
from uagents_adapter import A2AAgentConfig

A2AAgentConfig(
name="research_specialist",
description="AI research specialist for research and analysis",
url="http://localhost:10020",
port=10020,
specialties=["research", "analysis", "fact-finding", "summarization"],
priority=2,
)

SingleA2AAdapter

Wraps one AgentExecutor. Starts its own A2A HTTP server on a2a_port (default 9999) and a uAgent on port (default 8000). Chat is forwarded to http://localhost:{a2a_port}.

ParameterTypeDefaultDescription
agent_executorAgentExecutorrequiredExecutor that handles A2A tasks
namestrrequireduAgent name; default seed is {name}_seed
descriptionstrrequireduAgent description
timeoutint90HTTP timeout (seconds)
portint8000uAgent HTTP port
a2a_portint9999Adapter’s A2A HTTP port (chat target)
mailboxboolTrueAgentverse mailbox
seedstr | None{name}_seeduAgent seed
agent_portslist[int] | None[]Optional extra ports

MultiA2AAdapter

Coordinates multiple A2AAgentConfig entries. llm_api_key is keyword-only and required (no default). Pass "" to skip LLM routing. Model default is asi1-mini (not "asi1"). Default base_url is https://api.asi1.ai/v1/chat/completions.

ParameterTypeDefaultDescription
namestrrequiredCoordinator uAgent name
descriptionstrrequiredCoordinator description
llm_api_keystrrequired (keyword-only)ASI1 (or compatible) API key; empty string disables LLM pick
portint8000uAgent port
timeoutint90HTTP timeout
mailboxboolTrueAgentverse mailbox
seedstr | None{name}_seeduAgent seed
agent_configslist[A2AAgentConfig] | None[]Specialists to discover
fallback_executorAgentExecutor | NoneNoneUsed when routing finds no agent
routing_strategystr"keyword_match"keyword_match or round_robin only
modelstr"asi1-mini"Chat completions model
base_urlstrASI1 chat URLLLM HTTP endpoint

Methods: add_agent_config(config), run().

multi_coordinator.py
import os
from uagents_adapter import MultiA2AAdapter

coordinator = MultiA2AAdapter(
name="multi-specialist-coordinator",
description="Routes queries to A2A specialists",
llm_api_key=os.environ["ASI1_API_KEY"],
base_url="https://api.asi1.ai/v1/chat/completions",
model="asi1-mini",
port=8200,
mailbox=True,
agent_configs=agent_configs, # list[A2AAgentConfig]
routing_strategy="keyword_match",
)
coordinator.run()

a2a_servers(agent_configs, executors)

Starts one uvicorn thread per config. Each server uses config.port and executors[config.name]. This is independent of SingleA2AAdapter.a2a_port.


Why three ports in the Brave demo

A “single agent” Brave sample still starts two A2A HTTP servers plus one uAgent:

ProcessPort (this tutorial)Role
Specialist from a2a_servers10020A2A Agent Card for the Brave executor (A2AAgentConfig.port)
SingleA2AAdapter A2A HTTP9999Chat target. The uAgent POSTs here (a2a_port default)
uAgent8200Chat protocol + mailbox (SingleA2AAdapter.port)

Agentverse / ASI:One talk to the uAgent (mailbox / inspector on 8200). That uAgent does not call port 10020 for chat; it calls 9999. Port 10020 is the specialist card you can inspect or call as a raw A2A client.


Agent Card vs Agentverse

  • A2A Agent Card is an HTTP discovery document. Prefer http://localhost:PORT/.well-known/agent-card.json (replace PORT with 10020 or 9999). The adapter health-check uses that path first, then falls back to legacy /.well-known/agent.json.
  • Agentverse sees the uAgent after mailbox/Almanac registration. It does not scrape the A2A card to list the agent. Inspector URLs use the uAgent address and port 8200, not the A2A card URL.

File structure (Brave example)

Upstream folder: braveagent

braveagent/
function.py # orchestrator (same logic as main.py below)
brave/
agent.py # BraveSearchAgentExecutor
requirements.txt # see install pins below; do not use extra [a2a]
readme.md
.env # you create this

The GitHub tree currently has no main.py. Save the sample below as main.py, or run python function.py if you use the repo file as-is. Keep the filename you run in sync with these docs.

Recommended requirements.txt (the published example file still lists invalid uagents-adapter[a2a] and a broken a2a-sdk line — do not copy those):

requirements.txt
uagents==0.25.5
uagents-adapter[a2a-outbound]==0.6.2
a2a-sdk[all,sql,sqlite]>=0.2.11,<1.0
python-dotenv>=1.0.0

Full system example: main.py

This matches the Brave orchestrator: A2AAgentConfig + a2a_servers + SingleA2AAdapter. Coordinator name is brave-search-coordinator (same as the example repo). Address is unique to your seed; do not copy inspector addresses from old screenshots.

main.py
from typing import Any, Dict, List

from uagents_adapter import A2AAgentConfig, SingleA2AAdapter, a2a_servers

from brave.agent import BraveSearchAgentExecutor

COORDINATOR_NAME = "brave-search-coordinator"
SPECIALIST_NAME = "brave_search_specialist"
SPECIALIST_PORT = 10020
UAGENT_PORT = 8200

class BraveSearchAgent:
def __init__(self) -> None:
self.coordinator = None
self.agent_configs: List[A2AAgentConfig] = []
self.executors: Dict[str, Any] = {}
self.running = False

def setup_agents(self) -> None:
print("Setting up Brave Search Agent")
self.agent_configs = [
A2AAgentConfig(
name=SPECIALIST_NAME,
description="AI Agent for web and news search using Brave Search API",
url=f"http://localhost:{SPECIALIST_PORT}",
port=SPECIALIST_PORT,
specialties=[
"web search",
"news",
"information retrieval",
"local business",
"site-specific lookup",
],
priority=3,
)
]
self.executors = {SPECIALIST_NAME: BraveSearchAgentExecutor()}
print("Brave Search Agent configuration created")

def start_individual_a2a_servers(self) -> None:
print("Starting Brave Search specialist server...")
a2a_servers(self.agent_configs, self.executors)
print("Brave Search specialist server started")

def create_coordinator(self) -> SingleA2AAdapter:
print("Creating Brave Coordinator...")
brave_executor = self.executors.get(SPECIALIST_NAME)
if brave_executor is None:
raise ValueError("BraveSearchAgentExecutor not found in executors dictionary.")
self.coordinator = SingleA2AAdapter(
agent_executor=brave_executor,
name=COORDINATOR_NAME,
description="Coordinator for routing Brave Search queries",
port=UAGENT_PORT,
mailbox=True,
)
print("Brave Coordinator created")
return self.coordinator

def start_system(self) -> None:
print("Starting Brave Search System")
try:
self.setup_agents()
self.start_individual_a2a_servers()
coordinator = self.create_coordinator()
self.running = True
print(f"Starting Brave coordinator on port {coordinator.port}...")
print(
f"Specialist Agent Card: http://localhost:{SPECIALIST_PORT}/.well-known/agent-card.json"
)
coordinator.run()
except KeyboardInterrupt:
print("Shutting down Brave Search system...")
self.running = False

def main() -> None:
BraveSearchAgent().start_system()

if __name__ == "__main__":
main()

Getting started

1. Clone the examples repo

git clone https://github.com/fetchai/innovation-lab-examples.git
cd innovation-lab-examples/a2a-uAgents-Integration/a2a-Outbound-Communication/braveagent

Copy the main.py listing above into this folder (or run python function.py).

2. Install dependencies

Do not run pip install "uagents-adapter[a2a]" or [a2a-outbond]. Use the Installation commands (or the recommended requirements.txt).

python3 -m venv .venv
source .venv/bin/activate
pip install "uagents==0.25.5" "uagents-adapter[a2a-outbound]==0.6.2" "a2a-sdk[all,sql,sqlite]>=0.2.11,<1.0" python-dotenv

3. Environment variables

Never commit real keys. Create .env in braveagent/:

.env.example
BRAVE_API_KEY=your_brave_api_key_here
AGENTVERSE_API_KEY=your_agentverse_api_key_here

BraveSearchAgentExecutor needs BRAVE_API_KEY. mailbox=True needs Agentverse credentials (AGENTVERSE_API_KEY) so the uAgent can obtain a mailbox token and register on Almanac. For MultiA2AAdapter, also set ASI1_API_KEY and pass it as llm_api_key.

Load .env if your executor does not already:

load_env.py
from dotenv import load_dotenv

load_dotenv()

4. Run

python main.py

Expected listeners: specialist 10020, adapter A2A 9999, uAgent 8200.

5. Manifests and inspector

  • Specialist card: http://localhost:10020/.well-known/agent-card.json (legacy fallback: .../agent.json)
  • Adapter A2A card: http://localhost:9999/.well-known/agent-card.json
  • Inspector: the process prints an Agentverse inspect URL with uri=http://127.0.0.1:8200 and address= set to your uAgent address. That address changes if name or seed changes.

6. Chat

Brave Search agent in Agentverse inspector

Use Chat with agent in the inspector (for example Find pizza restaurants near Central Park) or ASI:One against the mailbox uAgent.


Architecture

A2A outbound adapter architecture
  1. User sends chat to the uAgent.
  2. SingleA2AAdapter posts to its A2A server (a2a_port, default 9999). MultiA2AAdapter health-checks cards and forwards to the chosen specialist url.
  3. The executor returns text (or payment artifacts); the adapter sends a chat reply and an acknowledgement.

Expected output

When running the example, you should see output similar to:

🚀 Starting Brave Search System
🔧 Setting up Brave Search Agent
✅ Brave Search Agent configuration created
🔄 Starting Brave Search server...
🚀 Starting brave_search_specialist on port 10020
INFO: Started server process [78780]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:10020 (Press CTRL+C to quit)
⏳ Initializing servers...
✅ All A2A servers started!
✅ Brave Search server started!
🤖 Creating Brave Coordinator...
✅ Brave Coordinator created!
🎯 Starting Brave coordinator on port 8200...
AgentCard manifest URL: http://localhost:10020/.well-known/agent.json
🚀 Starting A2A Adapter for 'brave_coordin'
📡 A2A Server will run on port 9999
🤖 uAgent will run on port 8200
INFO: Started server process [78780]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:9999 (Press CTRL+C to quit)
INFO: [brave_coordin]: Starting agent with address: agent1qfs8ujwza9enrq926cncxttt2qglmwa3svp09dgn63wfkulzhjv56lh8tr9
INFO: [brave_coordin]: 🚀 A2A uAgent started at address: agent1qfs8ujwza9enrq926cncxttt2qglmwa3svp09dgn63wfkulzhjv56lh8tr9
INFO: [brave_coordin]: 🔗 A2A Server running on port: 9999
INFO: [brave_coordin]: Agent inspector available at https://agentverse.ai/inspect/?uri=http%3A//127.0.0.1%3A8200&address=agent1qfs8ujwza9enrq926cncxttt2qglmwa3svp09dgn63wfkulzhjv56lh8tr9
INFO: [brave_coordin]: Starting server on http://0.0.0.0:8200 (Press CTRL+C to quit)
INFO: [brave_coordin]: Starting mailbox client for https://agentverse.ai
INFO: [brave_coordin]: Manifest published successfully: AgentChatProtocol
INFO: [uagents.registration]: Registration on Almanac API successful
INFO: [uagents.registration]: Almanac contract registration is up to date!
INFO: [brave_coordin]: Mailbox access token acquired
INFO: [brave_coordin]: 📩 Received message from agent1qvj7rlfmwqq95unelgs2hnfd5w8swwr6endw9664a26lpfk8fftjydlkhyv: Find pizza restaurants near Central Park
INFO:httpx:HTTP Request: POST http://localhost:9999/ "HTTP/1.1 200 OK"
INFO: [brave_coordin]: 🤖 A2A Response: 🌐 Brave Search Agent - General Search
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔍 Query: Find pizza r...
INFO: [brave_coordin]: 📤 Sent response back to agent1qvj7rlfmwqq95unelgs2hnfd5w8swwr6endw9664a26lpfk8fftjydlkhyv
INFO: [brave_coordin]: ✅ Sent acknowledgment for message accdd830-7726-498b-8df9-4159ac3a17cd
Brave Search agent chat in inspector

Troubleshooting

SymptomCauseFix
pip extra a2a-outbond / a2aExtra does not existInstall [a2a-outbound]==0.6.2
ModuleNotFoundError: a2a.server.appsa2a-sdk 1.xPin a2a-sdk[all,sql,sqlite]>=0.2.11,<1.0
TypeError on A2AAgentConfiga2a_port / executor_classUse only documented fields; pass executors to a2a_servers
NameError: A2AAdapter / missing asi_api_keyFictional classUse SingleA2AAdapter or MultiA2AAdapter + llm_api_key
python main.py file not foundRepo ships function.pySave main.py from this page or run python function.py
Mailbox / Almanac errorsMissing Agentverse keySet AGENTVERSE_API_KEY
Brave executor errorsMissing search keySet BRAVE_API_KEY
Address not matching a screenshotDifferent name/seedExpected; use the inspector URL from your logs
Port already in use10020, 9999, or 8200 takenStop the other process or change port / a2a_port

References

This pattern shows how outbound adapters expose A2A specialists through uAgent chat and mailbox.

Happy building!