share hands back a Task. This page covers what you can do with it:
read it, cancel it, stream its updates — and store push-notification configs against it.
Task reads and cancel
Every task operation is owner-confined: you can only ever see or act on your own tasks. A
foreign or absent task id answers with an indistinguishable masked
TaskNotFound — the response
for “not yours” and “does not exist” is identical, so a task id can never be used as an existence
oracle across agents or tenants. Only a TASK_STATE_SUBMITTED task is cancelable: canceling a
TASK_STATE_WORKING or already-terminal task answers with the canonical TaskNotCancelable
(400), and the task row is unchanged.
Task states are sent as their ProtoJSON enum names —
TASK_STATE_COMPLETED, not completed
— as A2A v1.0.1 §5.5 requires. The same goes for message roles: ROLE_USER inbound, ROLE_AGENT
on everything the server writes. For compatibility with clients written against earlier builds of
this surface, an inbound "role": "user" is still accepted; every other legacy spelling is not.Streaming (SSE)
Two routes stream over Server-Sent Events:POST /a2a/v1/message:stream— the same deterministic dispatch asmessage:send, with the response streamed: a synchronous skill emits one message event; ashareemits the task’s status updates as they happen, ending with the terminal event.POST /a2a/v1/tasks/{id}:subscribe— subscribe to an existing task’s updates. The server polls the task row on a bounded interval (a2a.subscribe_poll_secs, default1) and closes the stream at a terminal state or disconnect. The same owner-confinedTaskNotFoundmasking applies. Subscribing to a task that is already terminal answers withUnsupportedOperation(400) rather than opening a stream — per A2A v1.0.1, the operation is only for tasks that are not yet terminal. Read a finished task withGET /a2a/v1/tasks/{id}instead.
final flag on a status-update event. A stream ends when the task reaches a terminal
state: read terminality from status.state and treat the stream closing as the end of the
sequence.
Open streams are capped per principal: a2a.max_streams_per_principal (default 8). A caller over
the cap is rate-limited until one of its streams closes.
Push-notification configs: storage only
A2A defines per-task push-notification configs, and HyphaeDB stores and manages them:
This is storage and CRUD only — the server does not deliver push notifications today. Outbound
delivery is the job of agent activation, a proposed and unbuilt component; until it ships, a stored
config is a durable record of where notifications should eventually go, nothing more.
The stored rows are treated as future egress destinations, which is why the write path is strict:
- Config
urls must behttps(httpis permitted in Dev only). - At most
a2a.max_push_configs_per_task(default4) configs per task. - A config carrying credentials in its
authenticationpayload is rejected at decode — the server never stores credentials for dialing the endpoint. The optional validationtokenis stored, echoed only to its owner, and never logged or audited. - Every config mutation is owner-confined (the same masked
TaskNotFound) and audited synchronously, fail-closed — see the audit log.
Task lifecycle and retention
Task rows are thin — status and references, never memory content. They ride the standard data lifecycle: terminal rows are GC-cut aftera2a.task_retention_days (default 30), and an agent-scoped erasure removes the agent’s task rows
(and their push configs) along with everything else.