4.5

SendStreamingMessage and SubscribeToTask

A stream is a live view of one task, so a dropped connection costs you the frames sent while you were away and never the task itself.

The planner asks test-runner to run the full suite of payments-api at commit 9f3c2e1 and watches the log arrive on a stream. Four frames in, the client closes the connection. The suite keeps running, and the planner still wants the rest of the log.

When you finish this section, you can open a stream with either operation, tell when it has ended, and resume it after a drop.

What a stream carries

Stream: an HTTP response of type text/event-stream whose body is a series of Server-Sent Events. Every event carries one StreamResponse, which holds exactly one of task, message, statusUpdate, or artifactUpdate.

Two operations open a stream: SendStreamingMessage sends a message and watches the work it starts, and SubscribeToTask watches a task that already exists.

the first stream, closed by the client after four framessseFull log
HTTP/1.1 200 OK
Content-Type: text/event-stream

data: {"jsonrpc": "2.0", "id": 1, "result": {"task": {… "state": "TASK_STATE_SUBMITTED", …

data: {"jsonrpc": "2.0", "id": 1, "result": {"statusUpdate": {… "state": "TASK_STATE_WORKING", …

data: {"jsonrpc": "2.0", "id": 1, "result": {"artifactUpdate": {… "name": "test-log.txt", …

data: {"jsonrpc": "2.0", "id": 1, "result": {"artifactUpdate": {… "append": true}}}

# the client closes the connection after 4 events
Each frame is cut inside its line. The last line is the kit's own note in the transcript, written when the client drops the connection.

The streaming guide calls each payload a JSON-RPC 2.0 Response object, usually a SendStreamingMessageResponse, a type the proto does not define (conflict D33). The migration guide names the update members taskStatusUpdate and taskArtifactUpdate, where the proto and these frames say statusUpdate and artifactUpdate (conflict D9). Match on the proto names.

The specification defines no event: types, id: lines, retry: value, or keep-alive. The reference SDK sends keep-alive comments and marks a late error with event: error, so skip comments and check each payload for error.

Order and the close

Every implementation must deliver events in the order it created them, and it must send each event to every open stream of the task. None of those streams owns the task:

Version 1.0 has no end marker, because it removed the boolean final from TaskStatusUpdateEvent. A task stream must close when the task reaches a terminal state, and so does a SubscribeToTask stream. After a message frame the stream closes at once.

At interrupted states, §11.7 and the streaming guide close the stream, while §7.6.1 asks agents to keep it open for TASK_STATE_AUTH_REQUIRED (conflict D3). Interrupted states lists where each SDK closes, so a close by itself means only that reading has ended.

A dropped connection and SubscribeToTask

Fig. 4.7a stream dropped after four frames and picked back upsequence
A dropped stream and its subscription Three lifelines: planner, test-runner and its task store. The planner opens SendStreamingMessage and receives four frames: the task in TASK_STATE_SUBMITTED, the working status, and two artifactUpdate chunks of test-log.txt. The planner then closes the connection. test-runner appends chunks 03 and 04 to the stored task while no stream is open, so no client sees them. The planner calls SubscribeToTask, test-runner reads the stored task, and the first frame is a Task snapshot whose test-log.txt already holds four parts. Frames 2 to 5 carry chunks 05 to 08, frame 6 the completed status, and the stream closes. A second SubscribeToTask on the finished task fails with -32004 UnsupportedOperationError. planner client agent test-runner localhost:41241 task store inside test-runner 1 SendStreamingMessage run the full suite at 9f3c2e1 2 frame 1 · task SUBMITTED task 25239d4c 3 frame 2 · statusUpdate WORKING 4 frame 3 · artifactUpdate test_suite_01, names test-log.txt 5 frame 4 · artifactUpdate test_suite_02, append: true client closes the connection 6 append test_suite_03 no stream is open 7 append test_suite_04 nobody receives a frame 8 SubscribeToTask 25239d4c 9 stored task log parts 01 to 04 so far 10 frame 1 · task WORKING snapshot: test-log.txt, 4 parts 11 frames 2 to 5 · artifactUpdate test_suite_05 to 08, lastChunk on 08 12 frame 6 · statusUpdate COMPLETED 96 passed, then the stream closes 13 SubscribeToTask 25239d4c again, after the end 14 -32004 UnsupportedOperationError the task is TASK_STATE_COMPLETED
A dropped stream loses the frames sent while it was closed, and SubscribeToTask resumes with a Task snapshot that already holds their content. Read top to bottom. Steps 1 to 5 are the first stream, and only the writes nobody saw are drawn to the task store. Frame numbers count per stream.

After frame 4 the agent appends chunks 03 and 04 to test-log.txt, and no open connection carries their artifactUpdate events, as figure 4.7 shows. The events are gone, and the stored task holds both chunks.

SubscribeToTask: the operation that opens a new stream on an existing task, given its id. Its first frame is a snapshot: the first event of the stream must be a Task object.

the first frame after SubscribeToTask, a full Task snapshotjsonFull log
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "task": {
      "id": "25239d4c-798b-4754-be06-d1f94d0645b1",
…
      "status": {
        "state": "TASK_STATE_WORKING",
…
      "artifacts": [
        {
          "artifactId": "test-log",
          "name": "test-log.txt",
          "parts": [
            {
              "text": "tests/test_suite_01.py ........\n"
…
              "text": "tests/test_suite_04.py ........\n"
The status message and the history are cut, and so are parts 02 and 03 of the log.

Frames 2 to 5 append chunks 05 to 08, and frame 6 carries TASK_STATE_COMPLETED with 96 passed. Nothing is replayed, and the specification defines no cursor. So replace your partial test-log.txt with the snapshot's artifact, then append the frames that follow.

The snapshot holds only the current status, so a client that reconnects can miss some status updates, and results belong in artifacts.

A second SubscribeToTask after the task completes fails with UnsupportedOperationError, -32004, as plain JSON with no stream. So stop reconnecting and read the final task with GetTask, as GetTask and ListTasks shows.

The subscribe verb over HTTP+JSON

The HTTP+JSON route is /tasks/{id}:subscribe, and the specification names two verbs for it. The proto binds it to GET, and the method table and the URL list show POST (conflict D1). On 2026-09-29 the project's steering committee chose GET in PR #2068, which matches the proto. That change is unreleased on branch dev-1.1, and PR #1891 for POST stays open on main. The SDKs split three ways:

SDKServer acceptsClient sends
a2a-python 1.2.2GET and POSTPOST
a2a-goGET and POSTPOST
a2a-javaPOSTPOST
a2a-jsGET and POSTPOST
a2a-dotnetPOSTPOST
a2a-rsGET and POSTGET

A POST reaches all six servers, and a GET reaches four, so send POST as five of the six clients do. A server should accept both.

A2A protocol 1.0.1 · 3303592 · 2026-05-28 · Edition 2026.10