A2A Protocol Adapter

Expose Rekall agents to external agents over the open A2A (Agent2Agent) protocol. An A2A task is a Rekall run — backed by execution memory, so tasks are durable, resumable, and auditable like every other run.

Overview

A2A is the emerging open standard (Linux Foundation) for task delegation between independently-built agents. Rekall implements the server side: an Agent Card per agent for discovery, and a JSON-RPC 2.0 endpoint supporting message/send, message/stream (SSE), tasks/get, tasks/cancel, tasks/resubscribe, and tasks/pushNotificationConfig/set|get. Live updates ride Rekall's run-event bus (Redis pub/sub), so streams work across split API/worker deployments.

Authentication

The adapter uses the same auth rails as the rest of the API — a Rekall API key (rk_…) or agent token (at_…) as a Bearer token. message/send requires agent:spawn, tasks/get requires agent:read, and tasks/cancel requires agent:manage.

Agent Card

The discovery document an A2A client fetches to learn what the agent does and where to send tasks.

GET /v1/a2a/agents/:id/card
curl https://api.rekall.ai/api/v1/a2a/agents/agent_abc123/card \
-H "Authorization: Bearer rk_live_abc123"
Response
{
"protocolVersion": "0.2",
"name": "Incident Summariser",
"description": "Summarises deployment incidents",
"url": "https://api.rekall.ai/api/v1/a2a/agents/agent_abc123",
"version": "1.0.0",
"capabilities": { "streaming": false, "pushNotifications": false, "stateTransitionHistory": false },
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"securitySchemes": { "bearer": { "type": "http", "scheme": "bearer" } },
"security": [{ "bearer": [] }],
"skills": [
{
"id": "execute-task",
"name": "Incident Summariser: execute task",
"description": "Summarises deployment incidents",
"tags": ["memory", "rekall", "husk"]
}
]
}

message/send

Sends a message to the agent, creating a task. For Husk agents the task executes the multi-step run loop asynchronously; poll tasks/get for the result.

POST /v1/a2a/agents/:id (JSON-RPC 2.0)
curl -X POST https://api.rekall.ai/api/v1/a2a/agents/agent_abc123 \
-H "Authorization: Bearer rk_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"kind": "message",
"role": "user",
"messageId": "msg-001",
"parts": [{ "kind": "text", "text": "Summarise this week's incidents" }]
}
}
}'
Response — an A2A Task (the task id is the Rekall run id)
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"kind": "task",
"id": "run_xyz789",
"contextId": "agent_abc123",
"status": { "state": "working", "timestamp": "2026-07-03T10:15:01.000Z" }
}
}

message/stream (SSE)

Same params as message/send, but the response is a Server-Sent Events stream. Each data: line is a JSON-RPC response whose result is the initial Task, then status-update frames (including live agent progress messages from the run loop), an artifact-update with the output on completion, and a terminal frame with final: true, after which the stream closes. Heartbeat comments keep proxies from timing the connection out; streams are capped at 10 minutes (fall back to tasks/get after).

Stream frames (SSE)
data: {"jsonrpc":"2.0","id":1,"result":{"kind":"task","id":"run_xyz789","contextId":"agent_abc123","status":{"state":"working","timestamp":"…"}}}
data: {"jsonrpc":"2.0","id":1,"result":{"kind":"status-update","taskId":"run_xyz789","status":{"state":"working","message":{"parts":[{"kind":"text","text":"[RECALL] Found 3 relevant memories"}]},"timestamp":"…"},"final":false}}
data: {"jsonrpc":"2.0","id":1,"result":{"kind":"artifact-update","taskId":"run_xyz789","artifact":{"artifactId":"run_xyz789-response","name":"response","parts":[{"kind":"text","text":"Two incidents, both resolved…"}]},"lastChunk":true}}
data: {"jsonrpc":"2.0","id":1,"result":{"kind":"status-update","taskId":"run_xyz789","status":{"state":"completed","timestamp":"…"},"final":true}}

tasks/resubscribe (params { "id": "run_xyz789" }) re-attaches a dropped stream to a live task; on an already-terminal task it returns the final Task frame and closes immediately.

Push Notifications

Register a URL per task with tasks/pushNotificationConfig/set (or inline on send via configuration.pushNotificationConfig) and Rekall POSTs the full Task object to it on every status change, with retry/backoff. Your token is echoed in the X-A2A-Notification-Token header so your receiver can authenticate the notification.

tasks/pushNotificationConfig/set params
{
"taskId": "run_xyz789",
"pushNotificationConfig": {
"url": "https://client.example.com/a2a-notifications",
"token": "your-opaque-token"
}
}

SSRF protection

Push URLs are validated at registration and again at delivery time (DNS rebinding): https only, no embedded credentials, and any URL resolving to loopback, RFC1918/CGNAT, link-local (including the cloud metadata endpoint), or IPv6 unique-local/mapped ranges is refused with JSON-RPC error -32602.

tasks/get

Polls a task. A completed task carries the agent's response as a text artifact.

Request params: { "id": "run_xyz789" } — completed response
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"kind": "task",
"id": "run_xyz789",
"contextId": "agent_abc123",
"status": { "state": "completed", "timestamp": "2026-07-03T10:15:31.000Z" },
"artifacts": [
{
"artifactId": "run_xyz789-response",
"name": "response",
"parts": [{ "kind": "text", "text": "Two incidents this week, both resolved…" }]
}
]
}
}

tasks/cancel

Cancels a task that is still submitted/working/input-required; terminal tasks return JSON-RPC error -32002.

Task State Mapping

Rekall run statusA2A task state
pendingsubmitted
runningworking
paused (HITL)input-required
completedcompleted
failed / timeoutfailed
cancelledcanceled

Why this composes well

Because a task is a run, everything Rekall gives runs applies to A2A tasks: durable execution memory, per-step checkpoints, kill/resume, the validation gate (HITL pauses surface as input-required), and the thought stream for post-hoc audit.

Wake-on-Message (internal messaging)

For agents inside the same Rekall workspace, the native mailbox (POST /v1/agent-constellations/:id/messages) is the lighter rail — messages are durable, recallable memory. Setting wake_on_message: true on an agent (PATCH the agent, or the toggle on its dashboard page) makes an incoming message create a run (triggeredBy: message) so the mailbox actually gets acted on. Loop guard: a wake never fires while the agent already has a pending/running run, so two wake-enabled agents can't ping-pong runs.

Rekall
rekall