A2A Agent for VACs
This guide explains how to expose your Sunholo VAC (Virtual Agent Computer) functionality as an A2A (Agent-to-Agent) protocol agent, enabling your VACs to participate in multi-agent ecosystems and be discovered by other A2A-compatible systems.
Overview
The A2A agent functionality transforms your Flask-based VAC application into an A2A protocol agent, making your VACs discoverable and interactable via the standardized Agent-to-Agent protocol. This enables seamless integration with other AI agents and multi-agent workflows while maintaining compatibility with existing VAC endpoints.
What is A2A?
The Agent-to-Agent (A2A) protocol is an open standard initiated by Google Cloud that enables AI agents to communicate and collaborate across different platforms and frameworks. Key features include:
- Agent Discovery: Automatic capability discovery via Agent Cards
- Task Management: Standardized task lifecycle with states like "submitted", "working", "completed"
- Streaming Support: Real-time updates via Server-Sent Events (SSE)
- JSON-RPC 2.0: Standard communication protocol
- Multi-Platform: Works across clouds, platforms, and organizational boundaries
Enabling A2A Agent
To enable A2A agent functionality in your VAC, set the enable_a2a_agent
parameter when initializing VACRoutes
:
from flask import Flask
from sunholo.agents.flask import VACRoutes
app = Flask(__name__)
vac_routes = VACRoutes(
app,
stream_interpreter=your_stream_interpreter,
vac_interpreter=your_vac_interpreter, # Optional
enable_a2a_agent=True, # Enable A2A agent endpoints
a2a_vac_names=["vac1", "vac2"] # Optional: specify which VACs to expose
)
This will create the following A2A endpoints:
/.well-known/agent.json
- Agent Card discovery/a2a/tasks/send
- Create and execute tasks/a2a/tasks/sendSubscribe
- Create tasks with SSE streaming/a2a/tasks/get
- Get task status/a2a/tasks/cancel
- Cancel running tasks/a2a/tasks/pushNotification/set
- Push notification settings
Agent Card Discovery
The Agent Card at /.well-known/agent.json
provides automatic discovery of your VAC's capabilities:
{
"name": "Sunholo VAC Agent",
"description": "Multi-VAC agent providing access to Sunholo Virtual Agent Computers",
"url": "https://your-app.run.app",
"version": "1.0.0",
"capabilities": [
"task_management",
"streaming",
"conversation",
"document_retrieval"
],
"skills": [
{
"name": "vac_query_my_vac",
"description": "Send a query to My VAC and get a complete response using gemini-1.5-flash",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The question or instruction to send to the VAC"
},
"chat_history": {
"type": "array",
"description": "Previous conversation history"
}
},
"required": ["query"]
}
}
]
}
Available A2A Skills
The A2A agent automatically generates skills based on your VAC configurations:
1. VAC Query (vac_query_{vac_name})
Static queries that return complete responses.
Parameters:
query
(string, required): The question or instructionchat_history
(array, optional): Previous conversation historycontext
(object, optional): Additional context parameters
Response:
- Complete answer with source documents and metadata
2. VAC Stream (vac_stream_{vac_name})
Streaming conversations with real-time updates.
Parameters:
query
(string, required): The question or instructionchat_history
(array, optional): Previous conversation historystream_settings
(object, optional): Streaming configuration
Response:
- Task ID for tracking streaming progress
- Real-time updates via SSE
3. VAC Memory Search (vac_memory_search_{vac_name})
Search through the VAC's knowledge base (if memory is configured).
Parameters:
search_query
(string, required): The search querylimit
(integer, optional): Maximum results (default: 10)similarity_threshold
(number, optional): Minimum similarity score (default: 0.7)
Response:
- Array of search results with scores and metadata
Complete Example
#!/usr/bin/env python3
from flask import Flask
from sunholo.agents.flask import VACRoutes
# Your VAC interpreter functions
def my_stream_interpreter(question, vector_name, chat_history, **kwargs):
"""Streaming interpreter for real-time responses."""
yield f"Processing query for VAC '{vector_name}': {question}\n"
yield "Analyzing context...\n"
yield {"answer": f"Final answer: {question}"}
def my_vac_interpreter(question, vector_name, chat_history, **kwargs):
"""Static interpreter for complete responses."""
return {
"answer": f"Response from {vector_name}: {question}",
"source_documents": []
}
# Create Flask app
app = Flask(__name__)
# Initialize with A2A agent enabled
vac_routes = VACRoutes(
app,
stream_interpreter=my_stream_interpreter,
vac_interpreter=my_vac_interpreter,
enable_a2a_agent=True,
a2a_vac_names=["support_bot", "research_assistant"]
)
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
Task Management
A2A uses a task-based interaction model with the following lifecycle:
- Task Creation: Client sends task to
/a2a/tasks/send
- Processing: Task moves through states (submitted → working → completed)
- Updates: Real-time updates via
/a2a/tasks/sendSubscribe
(SSE) - Completion: Final result with artifacts and metadata
Task States
submitted
: Task created and queuedworking
: Task is being processedinput-required
: Task needs additional input (rare)completed
: Task finished successfullyfailed
: Task encountered an errorcanceled
: Task was canceled by request
Example Task Request
curl -X POST http://localhost:8080/a2a/tasks/send \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tasks/send",
"params": {
"skillName": "vac_query_support_bot",
"input": {
"query": "How do I reset my password?",
"chat_history": []
},
"clientMetadata": {
"source": "customer_portal"
}
},
"id": "1"
}'
Streaming with SSE
For real-time updates, use the SSE endpoint:
const eventSource = new EventSource('/a2a/tasks/sendSubscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
jsonrpc: "2.0",
method: "tasks/sendSubscribe",
params: {
skillName: "vac_stream_research_assistant",
input: {
query: "Explain quantum computing",
stream_settings: {
wait_time: 5,
timeout: 180
}
}
},
id: "2"
})
});
eventSource.onmessage = function(event) {
const update = JSON.parse(event.data);
if (update.type === 'task_update') {
console.log('Task progress:', update.data.progress);
if (update.data.state === 'completed') {
console.log('Final result:', update.data.artifacts);
eventSource.close();
}
}
};
Deploying to Google Cloud Run
The A2A agent is designed to work seamlessly with Google Cloud Run:
- Create Dockerfile:
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Install A2A support
RUN pip install a2a-python
# Copy application
COPY . .
# Run the app
CMD ["python", "app.py"]
- Build and deploy:
# Build container
gcloud builds submit --tag gcr.io/YOUR_PROJECT/vac-a2a-agent
# Deploy to Cloud Run
gcloud run deploy vac-a2a-agent \
--image gcr.io/YOUR_PROJECT/vac-a2a-agent \
--port 8080 \
--no-allow-unauthenticated \
--region us-central1
- Authentication: Cloud Run's IAM handles authentication automatically. A2A clients need the Cloud Run Invoker role.
A2A Client Integration
Other A2A agents can discover and interact with your VACs:
# Example A2A client code
import requests
# Discover agent capabilities
agent_card = requests.get('https://your-app.run.app/.well-known/agent.json').json()
print(f"Available skills: {[skill['name'] for skill in agent_card['skills']]}")
# Send a task
response = requests.post('https://your-app.run.app/a2a/tasks/send', json={
"jsonrpc": "2.0",
"method": "tasks/send",
"params": {
"skillName": "vac_query_support_bot",
"input": {
"query": "What are your business hours?"
}
},
"id": "client_123"
}, headers={'Authorization': 'Bearer YOUR_TOKEN'})
task_result = response.json()
Integration with Existing Endpoints
The A2A agent coexists with your existing VAC endpoints:
- Direct VAC:
/vac/<vector_name>
and/vac/streaming/<vector_name>
- MCP Server:
/mcp
(if enabled) - A2A Agent:
/.well-known/agent.json
and/a2a/*
All endpoints share the same interpreter functions, ensuring consistent behavior across protocols.
Configuration and Customization
VAC Selection
# Expose specific VACs
vac_routes = VACRoutes(
app,
stream_interpreter=my_interpreter,
enable_a2a_agent=True,
a2a_vac_names=["customer_support", "technical_docs"] # Only these VACs
)
# Auto-discover all VACs (default)
vac_routes = VACRoutes(
app,
stream_interpreter=my_interpreter,
enable_a2a_agent=True
# a2a_vac_names=None discovers all configured VACs
)
Agent Metadata
The agent card automatically uses metadata from your VAC configurations:
display_name
: Becomes the skill descriptionmodel
: Included in skill metadatamemory
: Determines if memory search skills are availableagent
: Included in capability descriptions