Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions python/01-learn/16-a2a-protocol/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# A2A Protocol — Agent-to-Agent Communication

This tutorial shows how to use the **A2A (Agent-to-Agent) Protocol** to expose Strands agents as network services and connect them together. You'll learn to wrap agents as servers, invoke them remotely, discover their capabilities, stream responses, and compose remote agents as tools.

## Tutorial Details

| Information | Details |
|----------------------|--------------------------------------------------------------------------|
| **Strands Features** | `A2AServer`, `A2AAgent`, `A2AClientToolProvider`, agent cards, streaming |
| **Agent Pattern** | Multi-agent via network protocol (server ↔ client) |
| **Tools** | `calculator` from strands-agents-tools |
| **Model** | Amazon Nova Lite on Amazon Bedrock |

## How It Works

1. **A2AServer** wraps any Strands `Agent` and serves it over HTTP with an auto-generated agent card
2. **A2AAgent** acts as a client that discovers and invokes remote agents
3. The agent card at `/.well-known/agent-card.json` advertises capabilities, skills, and streaming support
4. **A2AClientToolProvider** wraps remote agents as callable tools for an orchestrator agent

## Prerequisites

- Python 3.10 or later
- AWS account with [Amazon Bedrock](https://aws.amazon.com/bedrock/) access configured
- Basic familiarity with Strands Agents — see [01-first-agent](../01-first-agent/) if needed

## Tutorial Structure

```
16-a2a-protocol/
├── README.md
├── requirements.txt
└── a2a-protocol.ipynb
```

| File | Description |
|------|-------------|
| [a2a-protocol.ipynb](./a2a-protocol.ipynb) | Step-by-step notebook covering server setup, client invocation, agent card discovery, streaming, and tool composition |

## What You'll Learn

- **A2AServer**: wrap a Strands agent and serve it over HTTP
- **A2AAgent**: invoke a remote agent and get results
- **Agent card discovery**: inspect capabilities, skills (auto-derived from tools), and streaming support
- **Streaming patterns**: consume `A2AStreamEvent` for real-time responses
- **A2AClientToolProvider**: compose remote agents as tools for an orchestrator agent

## Installation

Install the required dependencies:

```bash
pip install -r requirements.txt
```

Then open [a2a-protocol.ipynb](./a2a-protocol.ipynb) from the `16-a2a-protocol/` directory.

## Related Tutorials

- [10-agents-as-tools](../10-agents-as-tools/) — composing agents locally (A2A extends this over the network)
- [04-streaming](../04-streaming/) — streaming fundamentals that A2A builds on
- [11-swarm](../11-swarm/) — alternative multi-agent pattern using swarms
334 changes: 334 additions & 0 deletions python/01-learn/16-a2a-protocol/a2a-protocol.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 16 — A2A Protocol: Agent-to-Agent Communication\n",
"\n",
"The **A2A (Agent-to-Agent) Protocol** lets you expose any Strands agent as a network service and invoke it from other agents or clients. This enables multi-agent architectures where agents run as independent services and communicate over HTTP.\n",
"\n",
"In this tutorial you will:\n",
"1. Wrap a Strands agent as an **A2A server**\n",
"2. Invoke it remotely with **A2AAgent**\n",
"3. Discover its capabilities via the **agent card**\n",
"4. Stream responses in real time\n",
"5. Compose remote agents as **tools** for an orchestrator"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Prerequisites\n",
"\n",
"You need AWS credentials configured for Amazon Bedrock access. See [01-first-agent](../01-first-agent/) if you haven't set this up."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%pip install -U 'strands-agents[a2a]' 'strands-agents-tools[a2a_client]' strands-agents-tools -q"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Section 1: Introduction — What is A2A?\n",
"\n",
"When agents need to collaborate, they can run as separate services and talk to each other over the network. The A2A protocol provides:\n",
"\n",
"- **A2AServer** — wraps any `Agent` and serves it over HTTP\n",
"- **Agent cards** — JSON metadata at `/.well-known/agent-card.json` describing capabilities\n",
"- **A2AAgent** — a client that discovers and invokes remote agents\n",
"- **A2AClientToolProvider** — wraps remote agents as tools for other agents\n",
"\n",
"This means you can build a calculator agent, a research agent, and a writing agent as independent services, then have an orchestrator agent call whichever one it needs."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Section 2: A2AServer — Wrapping an Agent as a Service\n",
"\n",
"Let's create a simple calculator agent and serve it over A2A. The server automatically generates an agent card from the agent's `name` and `description`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from strands import Agent, tool\n",
"from strands.models import BedrockModel\n",
"\n",
"\n",
"@tool\n",
"def calculator(expression: str) -> str:\n",
" \"\"\"Evaluate a mathematical expression and return the result.\n",
"\n",
" Args:\n",
" expression: A mathematical expression to evaluate (e.g., '2 + 2', '10 ** 6').\n",
" \"\"\"\n",
" try:\n",
" result = eval(expression, {\"__builtins__\": {}}, {})\n",
" return str(result)\n",
" except Exception as e:\n",
" return f\"Error: {e}\"\n",
"\n",
"\n",
"model = BedrockModel(model_id=\"us.amazon.nova-lite-v1:0\")\n",
"\n",
"calculator_agent = Agent(\n",
" model=model,\n",
" tools=[calculator],\n",
" name=\"Calculator Agent\",\n",
" description=\"An agent that evaluates mathematical expressions.\",\n",
" callback_handler=None,\n",
")\n",
"\n",
"print(f\"Agent created: {calculator_agent.name}\")\n",
"print(f\"Tools: {[t.__name__ if hasattr(t, '__name__') else str(t) for t in calculator_agent.tool_names]}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now start the A2A server in a background thread so we can continue using this notebook:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import threading\n",
"import time\n",
"\n",
"from strands.multiagent.a2a import A2AServer\n",
"\n",
"a2a_server = A2AServer(agent=calculator_agent, port=9000, enable_a2a_compliant_streaming=True)\n",
"\n",
"server_thread = threading.Thread(target=a2a_server.serve, daemon=True)\n",
"server_thread.start()\n",
"time.sleep(2) # wait for server to start\n",
"\n",
"print(\"A2A server running at http://127.0.0.1:9000\")\n",
"print(\"Agent card available at http://127.0.0.1:9000/.well-known/agent-card.json\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Section 3: A2AAgent — Invoking a Remote Agent\n",
"\n",
"`A2AAgent` is the client side. Point it at the server endpoint and call it just like a local agent — it returns an `AgentResult`."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from strands.agent.a2a_agent import A2AAgent\n",
"\n",
"a2a_client = A2AAgent(endpoint=\"http://127.0.0.1:9000\")\n",
"\n",
"result = a2a_client(\"What is 10 ** 6?\")\n",
"print(\"Response:\", result.message)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The remote agent received the message, used its `calculator` tool, and returned the result — all over HTTP. Let's try a more complex expression:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"result = a2a_client(\"Calculate the square root of 144 and then multiply it by 7\")\n",
"print(\"Response:\", result.message)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Section 4: Agent Card Discovery\n",
"\n",
"Every A2A server publishes an **agent card** — a JSON document describing the agent's name, description, capabilities, and skills. Skills are auto-derived from the agent's tools.\n",
"\n",
"Let's fetch and inspect it:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import json\n",
"import urllib.request\n",
"\n",
"card_url = \"http://127.0.0.1:9000/.well-known/agent-card.json\"\n",
"with urllib.request.urlopen(card_url) as resp:\n",
" agent_card = json.loads(resp.read())\n",
"\n",
"print(\"=== Agent Card ===\")\n",
"print(json.dumps(agent_card, indent=2))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Key fields in the agent card:\n",
"- **name** / **description** — from the `Agent` constructor\n",
"- **skills** — auto-generated from the agent's tools (each tool becomes a skill)\n",
"- **capabilities** — indicates streaming support and other features\n",
"\n",
"This is how clients discover what a remote agent can do before invoking it."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Section 5: Streaming Patterns\n",
"\n",
"For long-running tasks, you can stream responses as they're generated. The `stream_async` method yields `A2AStreamEvent` objects:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"\n",
"\n",
"async def stream_example():\n",
" print(\"Streaming response:\")\n",
" print(\"-\" * 40)\n",
" async for event in a2a_client.stream_async(\n",
" \"Explain step by step how to calculate 25 * 48\"\n",
" ):\n",
" if \"data\" in event:\n",
" print(event[\"data\"], end=\"\", flush=True)\n",
" print(\"\\n\" + \"-\" * 40)\n",
" print(\"Stream complete.\")\n",
"\n",
"\n",
"await stream_example()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Streaming is enabled by default on the server. Each event contains a chunk of the response text, letting you display results progressively in a UI or CLI."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Section 6: A2AClientToolProvider — Remote Agents as Tools\n",
"\n",
"The most powerful pattern: wrap remote A2A agents as **tools** that another agent can call. `A2AClientToolProvider` discovers agents from their URLs and exposes them as callable tools.\n",
"\n",
"This lets you build an orchestrator that delegates to specialized remote agents:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from strands_tools.a2a_client import A2AClientToolProvider\n",
"\n",
"# Discover remote agents and wrap them as tools\n",
"provider = A2AClientToolProvider(known_agent_urls=[\"http://127.0.0.1:9000\"])\n",
"\n",
"print(f\"Discovered {len(provider.tools)} remote agent tool(s):\")\n",
"for t in provider.tools:\n",
" name = t.__name__ if hasattr(t, \"__name__\") else str(t)\n",
" print(f\" - {name}\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create an orchestrator agent that uses the remote calculator as a tool\n",
"orchestrator = Agent(\n",
" model=model,\n",
" tools=provider.tools,\n",
" system_prompt=\"You are a helpful assistant. Use the available tools to answer questions.\",\n",
" callback_handler=None,\n",
")\n",
"\n",
"result = orchestrator(\"What is 123 * 456?\")\n",
"print(\"Orchestrator response:\", result.message)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The orchestrator doesn't know how to do math itself — it delegates to the remote Calculator Agent via A2A, gets the result, and presents it to the user."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## What You Learned\n",
"\n",
"- **A2AServer** wraps any Strands agent and serves it over HTTP with an auto-generated agent card\n",
"- **A2AAgent** invokes remote agents and returns `AgentResult` just like local calls\n",
"- **Agent cards** at `/.well-known/agent-card.json` advertise capabilities and skills\n",
"- **Streaming** with `stream_async` delivers responses progressively\n",
"- **A2AClientToolProvider** wraps remote agents as tools, enabling orchestrator patterns\n",
"\n",
"### Next Steps\n",
"\n",
"- Deploy A2A servers as containers or Lambda functions (see [02-deploy](../../02-deploy/))\n",
"- Combine with [11-swarm](../11-swarm/) for dynamic multi-agent teams\n",
"- Add [05-guardrails](../05-guardrails/) to your A2A agents for content filtering"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.10.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
Loading
Loading