Skip to main content

A2A Module

Access external A2A-compatible agents through the AgentVisor A2A Gateway.

Overview

AgentVisor provides two A2A modules:

ModuleUse Case
langchainLangChain/LangGraph integration (recommended)
a2aLow-level API for direct control

All A2A operations are policy-checked by the host runtime. See A2A Gateway Guide for architecture details.

LangChain Integration

The recommended way to use A2A agents is via the LangChain adapter:

from agentvisor.langchain.a2a import get_a2a_tools

get_a2a_tools()

def get_a2a_tools(agent_name: str | None = None) -> list[StructuredTool]:
"""Get LangChain tools for A2A agents.

Args:
agent_name: Optional specific agent to get tools for.
If None, returns one tool per configured agent.

Returns:
List of LangChain StructuredTool instances.
"""

Example: ReAct Agent with A2A Tools

from agentvisor.langchain.a2a import get_a2a_tools
from langgraph.prebuilt import create_react_agent
from langchain_ollama import ChatOllama

# Get tools for all configured A2A agents
tools = get_a2a_tools()

# Create agent with A2A tools
model = ChatOllama(model="llama3.2")
graph = create_react_agent(model, tools)

Example: Specific Agent Skills

# Get tools for a specific agent's skills
research_tools = get_a2a_tools("research-agent")
code_tools = get_a2a_tools("code-agent")

# Combine tools
all_tools = research_tools + code_tools

Tool Naming

Tools are named based on the agent and skill:

# For generic agent tools (no specific agent name):
tools = get_a2a_tools()
# a2a_research-agent
# a2a_code-agent

# For specific agent skills:
tools = get_a2a_tools("research-agent")
# research-agent_summarize
# research-agent_search

Low-Level API

For direct control over A2A operations:

from agentvisor import a2a

Data Classes

The A2A module provides the following data classes:

AgentInfo

@dataclass
class AgentInfo:
"""Information about an available A2A agent."""
name: str # Unique agent identifier
url: str # Agent endpoint URL
description: str # Human-readable description
skills: list[Skill] # Available skills

Skill

@dataclass
class Skill:
"""A skill (capability) offered by an A2A agent."""
id: str # Skill identifier
name: str # Human-readable name
description: str # Skill description
input_schema: dict[str, Any] # JSON Schema for input
examples: list[str] # Example prompts/inputs for this skill

AgentCard

@dataclass
class AgentCard:
"""Full capability description of an A2A agent."""
name: str # Agent name
description: str # Agent description
url: str # Agent endpoint URL
version: str # Protocol version
capabilities: Capabilities
skills: list[Skill]

Capabilities

@dataclass
class Capabilities:
"""Agent capabilities."""
streaming: bool # Supports SSE streaming
push_notifications: bool # Supports push notifications

Message

@dataclass
class Message:
"""An A2A message."""
role: str # "user" or "agent"
parts: list[Part] # Message content
message_id: str | None
context_id: str | None
task_id: str | None
metadata: dict[str, Any] | None

Part

@dataclass
class Part:
"""A message content part."""
text: str | None
data: bytes | None
file: FilePart | None
metadata: dict[str, Any] | None

FilePart

@dataclass
class FilePart:
"""A file attachment in an A2A message."""
name: str | None
mime_type: str | None
bytes: bytes | None
uri: str | None

Task

@dataclass
class Task:
"""An A2A task (unit of work)."""
id: str
context_id: str | None
status: TaskStatus
history: list[Message] # Conversation history
artifacts: list[Artifact] # Output artifacts
metadata: dict[str, Any] | None

TaskStatus

@dataclass
class TaskStatus:
"""Current task state."""
state: str # working, input_required, auth_required, completed, failed, canceled, rejected
message: Message | None
timestamp: str | None

Artifact

@dataclass
class Artifact:
"""Output artifact from a task."""
name: str | None
description: str | None
parts: list[Part]
index: int
metadata: dict[str, Any] | None

TaskUpdate

@dataclass
class TaskUpdate:
"""A task update event."""
task: Task
event_type: str

list_agents()

def list_agents() -> list[AgentInfo]:
"""List available A2A agents configured in the gateway.

Returns:
List of AgentInfo objects describing available agents.
"""

Example

from agentvisor import a2a

agents = a2a.list_agents()
for agent in agents:
print(f"{agent.name}: {agent.description}")
for skill in agent.skills:
print(f" - {skill.name}: {skill.description}")

get_agent_card()

def get_agent_card(agent_name: str) -> AgentCard:
"""Get the Agent Card (capability description) for an agent.

Args:
agent_name: The name of the agent to query.

Returns:
AgentCard with full capability information.

Raises:
AuthorizationError: If access to this agent is denied by policy.
"""

Example

from agentvisor import a2a

card = a2a.get_agent_card("research-agent")
print(f"Version: {card.version}")
print(f"Streaming: {card.capabilities.streaming}")
for skill in card.skills:
print(f" - {skill.name}: {skill.description}")

send_task()

def send_task(
agent_name: str,
message: Message,
context_id: str | None = None,
metadata: dict[str, Any] | None = None,
) -> Task:
"""Send a task to an external A2A agent.

Args:
agent_name: The name of the agent to send the task to.
message: The message to send.
context_id: Optional context ID to continue a conversation.
metadata: Optional metadata to include with the request.

Returns:
Task object with the task ID and current status.

Raises:
AuthorizationError: If this action is denied by policy.
"""

Example

from agentvisor import a2a

# Create a simple text message
message = a2a.text_message("Summarize the quarterly report")

# Send the task
task = a2a.send_task("research-agent", message)
print(f"Task ID: {task.id}")
print(f"Status: {task.status.state}")

get_task()

def get_task(agent_name: str, task_id: str) -> Task:
"""Get the current status of a task.

Args:
agent_name: The name of the agent that owns the task.
task_id: The task ID to query.

Returns:
Task object with current status, history, and artifacts.

Raises:
AuthorizationError: If access to this task is denied by policy.
"""

Example

from agentvisor import a2a

task = a2a.get_task("research-agent", "task-123")
print(f"Status: {task.status.state}")

if task.status.state == "completed":
for artifact in task.artifacts:
print(f"Artifact: {artifact.name}")
for part in artifact.parts:
if part.text:
print(part.text)

cancel_task()

def cancel_task(agent_name: str, task_id: str) -> None:
"""Request cancellation of a task.

Args:
agent_name: The name of the agent that owns the task.
task_id: The task ID to cancel.

Raises:
AuthorizationError: If this action is denied by policy.
"""

Example

from agentvisor import a2a

# Request cancellation
a2a.cancel_task("research-agent", "task-123")

# Check if cancellation was processed
task = a2a.get_task("research-agent", "task-123")
print(f"Status: {task.status.state}") # May be "canceled" or still "working"

stream_task()

def stream_task(agent_name: str, task_id: str) -> TaskUpdateIterator:
"""Subscribe to real-time task updates.

Args:
agent_name: The name of the agent that owns the task.
task_id: The task ID to stream updates for.

Returns:
TaskUpdateIterator that yields TaskUpdate objects.
"""

Example

from agentvisor import a2a

# Stream updates for a task
for update in a2a.stream_task("research-agent", "task-123"):
print(f"Event: {update.event_type}")
print(f"State: {update.task.status.state}")

# Check for terminal states
if update.task.status.state in ("completed", "failed", "canceled"):
break

# Or manually control the iterator
stream = a2a.stream_task("research-agent", "task-123")
try:
update = next(stream)
print(f"Got update: {update.event_type}")
finally:
stream.close() # Always close when done early

text_message()

def text_message(text: str, role: str = "user") -> Message:
"""Create a simple text message.

Args:
text: The text content of the message.
role: The role of the sender ("user" or "agent"). Defaults to "user".

Returns:
A Message object with a single text Part.
"""

Example

from agentvisor import a2a

# Create a user message
msg = a2a.text_message("What is the weather today?")

# Send to an agent
task = a2a.send_task("weather-agent", msg)

Task States

Tasks transition through the following states:

StateDescription
workingTask is in progress
input_requiredTask needs additional input from the caller
auth_requiredTask requires authentication
completedTask finished successfully
failedTask failed with an error
canceledTask was canceled
rejectedTask was rejected by the agent

Error Handling

Authorization Errors

from agentvisor import a2a

try:
card = a2a.get_agent_card("restricted-agent")
except a2a.AuthorizationError as e:
print(f"Access denied: {e.reason}")

Task Failures

from agentvisor import a2a

task = a2a.send_task("agent", a2a.text_message("do something"))

# Poll until terminal state
while task.status.state == "working":
task = a2a.get_task("agent", task.id)

if task.status.state == "failed":
# Get error message from status
if task.status.message:
for part in task.status.message.parts:
if part.text:
print(f"Error: {part.text}")

Complete Example

from agentvisor import a2a

def process_document(document: str) -> str:
"""Send a document to the research agent for summarization."""

# List available agents
agents = a2a.list_agents()
print(f"Available agents: {[a.name for a in agents]}")

# Get agent capabilities
card = a2a.get_agent_card("research-agent")
print(f"Capabilities: streaming={card.capabilities.streaming}")

# Create and send task
message = a2a.text_message(f"Summarize this document:\n\n{document}")
task = a2a.send_task("research-agent", message)
print(f"Task started: {task.id}")

# Stream updates
for update in a2a.stream_task("research-agent", task.id):
print(f"State: {update.task.status.state}")

if update.task.status.state == "completed":
# Extract result from artifacts
for artifact in update.task.artifacts:
for part in artifact.parts:
if part.text:
return part.text

# Or from conversation history
if update.task.history:
last_msg = update.task.history[-1]
for part in last_msg.parts:
if part.text:
return part.text

elif update.task.status.state in ("failed", "rejected"):
raise RuntimeError(f"Task failed: {update.task.status.state}")

elif update.task.status.state == "canceled":
raise RuntimeError("Task was canceled")

return ""

Environment

The A2A module requires these environment variables (set automatically by AgentVisor):

VariableDescription
AGENTVISOR_AGENT_SOCKETPath to agent Unix socket
AGENTVISOR_THREAD_IDCurrent thread ID