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.
Content-Type: text/event-stream
{"jsonrpc": "2.0", "id": 1, "result": {"task": {… "state": "TASK_STATE_SUBMITTED", …
{"jsonrpc": "2.0", "id": 1, "result": {"statusUpdate": {… "state": "TASK_STATE_WORKING", …
{"jsonrpc": "2.0", "id": 1, "result": {"artifactUpdate": {… "name": "test-log.txt", …
{"jsonrpc": "2.0", "id": 1, "result": {"artifactUpdate": {… "append": true}}}
# the client closes the connection after 4 eventsThe 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
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.
{
"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"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:
| SDK | Server accepts | Client sends |
|---|---|---|
| a2a-python 1.2.2 | GET and POST | POST |
| a2a-go | GET and POST | POST |
| a2a-java | POST | POST |
| a2a-js | GET and POST | POST |
| a2a-dotnet | POST | POST |
| a2a-rs | GET and POST | GET |
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