A2AClient

Python client for communicating with A2A-compatible agent servers

The A2AClient provides an async interface for communicating with A2A protocol servers over REST or JSON-RPC. This includes Agno AgentOS instances with A2A enabled and Google ADK agents. Other transports, such as gRPC, are not supported by this client.

Basic Usage

Install uv pip install 'agno[os,a2a]'. Start the A2A introduction server, which exposes my_agent at http://localhost:7777/a2a/agents/my_agent; the server needs its documented model key.

import asyncio

from agno.client.a2a import A2AClient


async def main():
    client = A2AClient("http://localhost:7777/a2a/agents/my_agent")
    result = await client.send_message(message="Hello!")
    print(result.content)


asyncio.run(main())

Later examples are fragments inside an async function like main() above.

Parameters

ParameterTypeDefaultDescription
base_urlstrRequiredBase URL of the A2A server. For Agno servers, include the full agent path (e.g., "http://localhost:7777/a2a/agents/my_agent")
timeoutint30Request timeout in seconds
protocolLiteral["rest", "json-rpc"]"rest"Protocol mode. Use "json-rpc" for Google ADK servers

Connecting to Different Servers

Agno AgentOS

For Agno servers with A2A interface enabled, include the full agent path in the URL:

from agno.client.a2a import A2AClient

# The URL includes the A2A path to the specific agent
client = A2AClient("http://localhost:7777/a2a/agents/my_agent")

result = await client.send_message(message="What can you help me with?")
print(result.content)

Google ADK

This fragment requires a separately running Google ADK server on port 8001. Set protocol="json-rpc":

from agno.client.a2a import A2AClient

# Google ADK uses JSON-RPC at the root endpoint
client = A2AClient("http://localhost:8001/", protocol="json-rpc")

result = await client.send_message(message="Tell me an interesting fact")
print(result.content)

Methods

send_message

Send a message to an A2A agent and wait for the complete response.

result = await client.send_message(
    message="What is the capital of France?",
    user_id="user-123",
    context_id="session-456",
)
print(result.content)
print(f"Task ID: {result.task_id}")
print(f"Context ID: {result.context_id}")

Parameters:

ParameterTypeDefaultDescription
messagestrRequiredThe text message to send
context_idOptional[str]NoneContext/session ID for multi-turn conversations
user_idOptional[str]NoneUser identifier
imagesOptional[List[Image]]NoneImages to include
audioOptional[List[Audio]]NoneAudio files to include
videosOptional[List[Video]]NoneVideos to include
filesOptional[List[File]]NoneFiles to include
metadataOptional[Dict[str, Any]]NoneAdditional metadata
headersOptional[Dict[str, str]]NoneHTTP headers to include in the request

Returns: TaskResult

Raises:

  • HTTPStatusError: If the server returns an HTTP error (4xx, 5xx)
  • RemoteServerUnavailableError: If connection fails or times out

stream_message

Stream a message response in real-time.

async for event in client.stream_message(
    message="Tell me a story",
    user_id="user-123",
):
    if event.is_content and event.content:
        print(event.content, end="", flush=True)

    if event.is_final:
        print("\n--- Stream complete ---")

Parameters: Same as send_message

Yields: StreamEvent

Raises:

  • HTTPStatusError: If the server returns an HTTP error (4xx, 5xx)
  • RemoteServerUnavailableError: If connection fails or times out

get_agent_card

Get the agent card for capability discovery.

card = client.get_agent_card()
if card:
    print(f"Agent: {card.name}")
    print(f"Description: {card.description}")
    print(f"Capabilities: {card.capabilities}")

Returns: AgentCard if available, None otherwise


aget_agent_card

Get the agent card for capability discovery asynchronously.

card = await client.aget_agent_card()
if card:
    print(f"Agent: {card.name}")
    print(f"Description: {card.description}")
    print(f"Capabilities: {card.capabilities}")

Returns: AgentCard if available, None otherwise

Response Types

TaskResult

Returned by send_message():

PropertyTypeDescription
task_idstrUnique task identifier
context_idstrContext/session ID for multi-turn conversations
statusstrTask status ("completed", "failed", "canceled")
contentstrResponse text content
artifactsList[Artifact]Any artifacts produced (files, images, etc.)
metadataOptional[Dict]Additional response metadata
is_completedboolTrue if task completed successfully
is_failedboolTrue if task failed
is_canceledboolTrue if task was canceled

StreamEvent

Yielded by stream_message():

PropertyTypeDescription
event_typestrEvent type ("content", "reasoning", "working", "completed", "failed", "canceled", "task")
contentOptional[str]Text content (for content events)
task_idOptional[str]Task identifier
context_idOptional[str]Context/session ID
metadataOptional[Dict]Event metadata
is_finalboolTrue if this is the final event
is_contentboolTrue if this is a content event with text
is_startedboolChecks for started; the current parser emits working instead. Use event.event_type == "working" for that state.
is_completedboolTrue if this is a task completed event
is_tool_callboolChecks legacy tool_call_started/tool_call_completed types, which the current parser does not emit.

Artifact

Represents files, images, or other artifacts from a task:

PropertyTypeDescription
artifact_idstrUnique artifact identifier
nameOptional[str]Artifact name
descriptionOptional[str]Artifact description
mime_typeOptional[str]MIME type of the artifact
uriOptional[str]URI to access the artifact
contentOptional[bytes]Raw content (if available)

AgentCard

Describes the capabilities of an A2A agent:

PropertyTypeDescription
namestrAgent name
urlstrAgent URL
descriptionOptional[str]Agent description
versionOptional[str]Agent version
capabilitiesSDK annotation: List[str]; actual standard card: mappingPassed through from the server. AgentOS returns a capability object such as {"streaming": True}; do not assume a list.
metadataOptional[Dict]Additional metadata

Multi-Turn Conversations

Reuse context_id to address the same conversation. Remembering prior turns also requires persistence and history on the server: for an Agno agent, configure a database and add_history_to_context=True. The minimal introduction server must be extended with those settings before trying this memory example:

from agno.client.a2a import A2AClient

client = A2AClient("http://localhost:7777/a2a/agents/my_agent")

# First message - no context_id
result1 = await client.send_message(
    message="My name is Alice and I love Python.",
)
print(f"Agent: {result1.content}")

# Get context_id from response
context_id = result1.context_id

# Follow-up message - include context_id
result2 = await client.send_message(
    message="What is my name?",
    context_id=context_id,
)
print(f"Agent: {result2.content}")  # Requires server-side persistence and history

Error Handling

from agno.client.a2a import A2AClient
from agno.exceptions import RemoteServerUnavailableError
from httpx import HTTPStatusError

client = A2AClient("http://localhost:7777/a2a/agents/my_agent")

try:
    result = await client.send_message(message="Hello")
except RemoteServerUnavailableError as e:
    print(f"Server unavailable: {e.message}")
    print(f"URL: {e.base_url}")
except HTTPStatusError as e:
    print(f"HTTP error: {e.response.status_code}")