Skip to main content
A 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 namesTASK_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 as message:send, with the response streamed: a synchronous skill emits one message event; a share emits 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, default 1) and closes the stream at a terminal state or disconnect. The same owner-confined TaskNotFound masking applies. Subscribing to a task that is already terminal answers with UnsupportedOperation (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 with GET /a2a/v1/tasks/{id} instead.
There is no 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 be https (http is permitted in Dev only).
  • At most a2a.max_push_configs_per_task (default 4) configs per task.
  • A config carrying credentials in its authentication payload is rejected at decode — the server never stores credentials for dialing the endpoint. The optional validation token is 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 after a2a.task_retention_days (default 30), and an agent-scoped erasure removes the agent’s task rows (and their push configs) along with everything else.