A2A (Agent2Agent)
Purple8 implements the A2A protocol v1.0 (JSON-RPC binding) in both directions:
- Server. Other agents can discover Purple8 and delegate tasks to it as a peer.
- Client. Remote A2A agents can be imported so their skills become tools. Those tools are then callable over MCP, REST and the console.
A2A complements MCP. MCP is how an agent uses tools; A2A is how agents delegate to each other.
Discovery: the Agent Card
curl http://localhost:8100/.well-known/agent-card.jsonThe card is public. Its contents:
- Skills. One skill per MCP namespace (16 namespaces covering 125 MCP tools), generated from the tool manifest. Human-only tools are excluded.
- Capabilities.
streaming: trueandpushNotifications: true.extendedAgentCardisfalse. - Caching. The card is served with
ETagandCache-Controlheaders, and conditional requests get304responses.
Calling Purple8 over JSON-RPC
All methods are POST /a2a with a bearer token or an API key. Send the header A2A-Version: 1.0.
curl -X POST http://localhost:8100/a2a \
-H "Authorization: Bearer $TOKEN" -H "A2A-Version: 1.0" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{
"message":{"messageId":"m1","role":"ROLE_USER",
"parts":[{"text":"What changed in the Q3 contracts?"}]}}}'| Method | Purpose |
|---|---|
SendMessage | Start or continue a task. It blocks until the task finishes unless configuration.returnImmediately is set. |
SendStreamingMessage | Same as SendMessage, but events are streamed over SSE. |
SubscribeToTask | Stream the events of an existing task over SSE. |
GetTask | Fetch a task. historyLength trims the history. |
ListTasks | List tasks with cursor paging. Filters: contextId, status, statusTimestampAfter. The response includes totalSize. |
CancelTask | Cancel a running task. |
Create/Get/List/DeleteTaskPushNotificationConfig | Manage webhooks for a task (see below). |
Errors use the spec codes -32001 to -32009.
How a skill is chosen
No LLM planning happens. The message is resolved deterministically, in this order:
metadata.skillnames a tool (for examplegraph.pagerank). The arguments come frommetadata.argumentsor from adatapart.- A
datapart carries{"skill": ..., "arguments": {...}}. - Plain text goes to
rag.hybrid_query.
If required arguments are missing, the task moves to TASK_STATE_INPUT_REQUIRED. To continue it, send another message with the same taskId.
Execution uses the caller's MCP path, so RBAC, the human-only gate and an API key's allowed_tools all apply. Tasks are stored as per-tenant A2ATask graph nodes, with history capped at 50 messages.
Streaming across workers
The SSE streams follow the task store as well as the in-process event bus. A task that is updated by a different worker process still reaches the subscriber. P8G_A2A_STREAM_POLL_S sets the poll interval (default 1.0).
Push notifications
You can attach a webhook when you start a task:
"configuration": {
"returnImmediately": true,
"taskPushNotificationConfig": {
"url": "https://hooks.example.com/a2a",
"token": "opaque-verifier",
"authentication": {"scheme": "Bearer", "credentials": "s3cret"}
}
}You can also manage webhooks on an existing task with CreateTaskPushNotificationConfig ({"taskId": ..., "url": ...}) and the other three *TaskPushNotificationConfig methods.
Delivery works like this:
- What is sent. Every status or artifact event is POSTed to each webhook as a
StreamResponsewithContent-Type: application/a2a+json. - Headers.
Authorization: <scheme> <credentials>,X-A2A-Notification-Token: <token>andA2A-Version: 1.0. - Ordering and retries. Deliveries are ordered per task and retried up to 3 times with exponential backoff. At most 32 run concurrently.
- Limits. Each task can have up to 10 configs.
credentialsis write-only and is never returned byGetorList. Delete is idempotent.
SSRF protection. The webhook URL is checked when the config is created, and checked again before every delivery attempt (this guards against DNS rebinding). Private, loopback, link-local and plain-HTTP targets are rejected.
| Env var | Default | Meaning |
|---|---|---|
P8G_A2A_PUSH_TIMEOUT_S | 15 | Per-delivery HTTP timeout |
P8G_A2A_PUSH_ALLOW_INSECURE | off | Allow private and plain-HTTP targets. Local development only. |
Using other agents as tools
To import a peer agent, use any of these:
- REST:
POST /registry/a2a-agents {"prefix": "billing", "card_url": "https://billing.example.com", "credential": "..."} - MCP: the
registry.register_a2a_agenttool, with the same arguments. - UI: MCP Console, then the A2A tab, then Peer agents.
Purple8 fetches the card and registers one tool per skill as {prefix}.{skill_id}. A call sends SendMessage and polls GetTask with backoff, bounded by the tool's timeout_s. It returns {state, text, artifacts, task_id, context_id}. The card URL and the JSON-RPC URL in the card are both SSRF-checked, and the card fetch does not follow redirects. Credentials are stored with envelope encryption.
Console
The A2A tab in the MCP Console shows three things:
- This agent's card and skills.
- Imported peers, with a test-message form.
- Inbound tasks, backed by
GET /a2a/tasksandGET /a2a/tasks/{id}.