Implements Google Agent2Agent Protocol v1.0 with full fleet integration: ## Phase 1 - Agent Card & Discovery - Agent Card types with JSON serialization (camelCase, Part discrimination by key) - Card generation from YAML config (~/.hermes/agent_card.yaml) - Fleet registry with LocalFileRegistry + GiteaRegistry backends - Discovery by skill ID or tag ## Phase 2 - Task Delegation - Async A2A client with JSON-RPC SendMessage/GetTask/ListTasks/CancelTask - FastAPI server with pluggable task handlers (skill-routed) - CLI tool (bin/a2a_delegate.py) for fleet delegation - Broadcast to multiple agents in parallel ## Phase 3 - Security & Reliability - Bearer token + API key auth (configurable per agent) - Retry logic (max 3 retries, 30s timeout) - Audit logging for all inter-agent requests - Error handling per A2A spec (-32001 to -32009 codes) ## Test Coverage - 37 tests covering types, card building, registry, server integration - Auth (required + success), handler routing, error handling Files: - nexus/a2a/ (types.py, card.py, client.py, server.py, registry.py) - bin/a2a_delegate.py (CLI) - config/ (agent_card.example.yaml, fleet_agents.json) - docs/A2A_PROTOCOL.md - tests/test_a2a.py (37 tests, all passing)
7.1 KiB
7.1 KiB
A2A Protocol for Fleet-Wizard Delegation
Implements Google's Agent2Agent (A2A) Protocol v1.0 for the Timmy Foundation fleet.
What This Is
Instead of passing notes through humans (Telegram, Gitea issues), fleet wizards can now discover each other's capabilities and delegate tasks autonomously through a machine-native protocol.
┌─────────┐ A2A Protocol ┌─────────┐
│ Timmy │ ◄────────────────► │ Ezra │
│ (You) │ JSON-RPC / HTTP │ (CI/CD) │
└────┬────┘ └─────────┘
│ ╲ ╲
│ ╲ Agent Card Discovery ╲ Task Delegation
│ ╲ GET /agent.json ╲ POST /a2a/v1
▼ ▼ ▼
┌──────────────────────────────────────────┐
│ Fleet Registry │
│ config/fleet_agents.json │
└──────────────────────────────────────────┘
Components
| File | Purpose |
|---|---|
nexus/a2a/types.py |
A2A data types — Agent Card, Task, Message, Part, JSON-RPC |
nexus/a2a/card.py |
Agent Card generation from ~/.hermes/agent_card.yaml |
nexus/a2a/client.py |
Async client for sending tasks to other agents |
nexus/a2a/server.py |
FastAPI server for receiving A2A tasks |
nexus/a2a/registry.py |
Fleet agent discovery (local file + Gitea backends) |
bin/a2a_delegate.py |
CLI tool for fleet delegation |
config/agent_card.example.yaml |
Example agent card config |
config/fleet_agents.json |
Fleet registry with all wizards |
Quick Start
1. Configure Your Agent Card
cp config/agent_card.example.yaml ~/.hermes/agent_card.yaml
# Edit with your agent name, URL, skills, and auth
2. List Fleet Agents
python bin/a2a_delegate.py list
3. Discover Agents by Skill
python bin/a2a_delegate.py discover --skill ci-health
python bin/a2a_delegate.py discover --tag devops
4. Send a Task
python bin/a2a_delegate.py send --to ezra --task "Check CI pipeline health"
python bin/a2a_delegate.py send --to allegro --task "Analyze the codebase" --wait
5. Fetch an Agent Card
python bin/a2a_delegate.py card --agent ezra
Programmatic Usage
Client (Sending Tasks)
from nexus.a2a.client import A2AClient, A2AClientConfig
from nexus.a2a.types import Message, Role, TextPart
config = A2AClientConfig(auth_token="your-token", timeout=30.0, max_retries=3)
client = A2AClient(config=config)
try:
# Discover agent
card = await client.get_agent_card("https://ezra.example.com")
print(f"Found: {card.name} with {len(card.skills)} skills")
# Delegate task
task = await client.delegate(
"https://ezra.example.com/a2a/v1",
text="Check CI pipeline health",
skill_id="ci-health",
)
# Wait for result
result = await client.wait_for_completion(
"https://ezra.example.com/a2a/v1",
task.id,
)
print(f"Result: {result.artifacts[0].parts[0].text}")
# Audit log
for entry in client.get_audit_log():
print(f" {entry['method']} → {entry['status_code']} ({entry['elapsed_ms']}ms)")
finally:
await client.close()
Server (Receiving Tasks)
from nexus.a2a.server import A2AServer
from nexus.a2a.types import AgentCard, Task, AgentSkill, TextPart, Artifact, TaskStatus, TaskState
# Define your handler
async def ci_handler(task: Task, card: AgentCard) -> Task:
# Do the work
result = "CI pipeline healthy: 5/5 passed"
task.artifacts.append(
Artifact(parts=[TextPart(text=result)], name="ci_report")
)
task.status = TaskStatus(state=TaskState.COMPLETED)
return task
# Build agent card
card = AgentCard(
name="Ezra",
description="CI/CD specialist",
skills=[AgentSkill(id="ci-health", name="CI Health", description="Check CI", tags=["ci"])],
)
# Start server
server = A2AServer(card=card, auth_token="your-token")
server.register_handler("ci-health", ci_handler)
await server.start(host="0.0.0.0", port=8080)
Registry (Agent Discovery)
from nexus.a2a.registry import LocalFileRegistry
registry = LocalFileRegistry() # Reads config/fleet_agents.json
# List all agents
for agent in registry.list_agents():
print(f"{agent.name}: {agent.description}")
# Find agents by capability
ci_agents = registry.list_agents(skill="ci-health")
devops_agents = registry.list_agents(tag="devops")
# Get endpoint
url = registry.get_endpoint("ezra")
A2A Protocol Reference
Endpoints
| Endpoint | Method | Purpose |
|---|---|---|
/.well-known/agent-card.json |
GET | Agent Card discovery |
/agent.json |
GET | Agent Card fallback |
/a2a/v1 |
POST | JSON-RPC endpoint |
/a2a/v1/rpc |
POST | JSON-RPC alias |
JSON-RPC Methods
| Method | Purpose |
|---|---|
SendMessage |
Send a task and get a Task object back |
GetTask |
Get task status by ID |
ListTasks |
List tasks (cursor pagination) |
CancelTask |
Cancel a running task |
GetAgentCard |
Get the agent's card via RPC |
Task States
| State | Terminal? | Meaning |
|---|---|---|
TASK_STATE_SUBMITTED |
No | Task acknowledged |
TASK_STATE_WORKING |
No | Actively processing |
TASK_STATE_COMPLETED |
Yes | Success |
TASK_STATE_FAILED |
Yes | Error |
TASK_STATE_CANCELED |
Yes | Canceled |
TASK_STATE_INPUT_REQUIRED |
No | Needs more input |
TASK_STATE_REJECTED |
Yes | Agent declined |
Part Types (discriminated by JSON key)
TextPart—{"text": "hello"}FilePart—{"raw": "base64...", "mediaType": "image/png"}or{"url": "https://..."}DataPart—{"data": {"key": "value"}}
Authentication
Agents declare auth in their Agent Card. Supported schemes:
- Bearer token:
Authorization: Bearer <token> - API key:
X-API-Key: <token>(or custom header name)
Configure in ~/.hermes/agent_card.yaml:
auth:
scheme: "bearer"
token_env: "A2A_AUTH_TOKEN" # env var containing the token
Fleet Registry
The fleet registry (config/fleet_agents.json) lists all wizards and their capabilities. Agents can be registered via:
- Local file —
LocalFileRegistryreads/writes JSON directly - Gitea —
GiteaRegistrystores cards in a repo for distributed discovery
Testing
pytest tests/test_a2a.py -v
Covers:
- Type serialization roundtrips
- Agent Card building from YAML
- Registry operations (register, list, filter)
- Server integration (SendMessage, GetTask, ListTasks, CancelTask)
- Authentication (required, success)
- Custom handler routing
- Error handling
Phase Status
- Phase 1 — Agent Card & Discovery
- Phase 2 — Task Delegation
- Phase 3 — Security & Reliability