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.
curl https://api.rekall.ai/api/v1/a2a/agents/agent_abc123/card \-H "Authorization: Bearer rk_live_abc123"
{"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.
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" }]}}}'
{"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).
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.
{"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.
{"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 status | A2A task state |
|---|---|
pending | submitted |
running | working |
paused (HITL) | input-required |
completed | completed |
failed / timeout | failed |
cancelled | canceled |
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.
