1.2

One task, request by request

One streamed run of test-runner shows every piece of the protocol in order, from the card read to the moment the server closes the stream.

Your planner wants the unit tests of payments-api run at commit 9f3c2e1, and it wants to watch the log grow while they run. It already holds the card of test-runner, whose capabilities include "streaming": true, so it sends the request with SendStreamingMessage.

This section follows that run frame by frame, from the streaming capture, and figure 1.3 shows it. When you finish this section, you can read any task stream and say what each frame changed in the stored task.

Fig. 1.3one streamed task from the card read to the closesequence
One streamed task from the card read to the close Two lifelines, planner and test-runner at localhost:41241. The planner reads the agent card, sends SendStreamingMessage with a new messageId and no taskId, and gets HTTP 200 with text/event-stream. Nine numbered stream frames follow: the task 728ba084 in TASK_STATE_SUBMITTED, a statusUpdate to TASK_STATE_WORKING, five artifactUpdate frames that build test-log.txt, the last with lastChunk, one artifactUpdate for summary.json, and a statusUpdate to TASK_STATE_COMPLETED. Only frames 1, 2 and 9 carry a timestamp, at 09:00:01.750Z, 09:00:02.000Z and 09:00:02.250Z. Then the server closes the stream, with no final flag. planner client agent test-runner localhost:41241 KIT CLOCK GET /.well-known/agent-card.json AgentCard, capabilities.streaming: true SendStreamingMessage messageId 5bc8fbbc, no taskId HTTP 200, text/event-stream 1 task · TASK_STATE_SUBMITTED task 728ba084, history holds the request 09:00:01.750Z 2 statusUpdate · TASK_STATE_WORKING Checking out 9f3c2e1 and starting the suite 09:00:02.000Z 3 artifactUpdate · test-log.txt first chunk: collected 12 items 4 artifactUpdate · append 5 artifactUpdate · append 6 artifactUpdate · append 7 artifactUpdate · append, lastChunk 8 artifactUpdate · summary.json, lastChunk 9 statusUpdate · TASK_STATE_COMPLETED 1 failed, 11 passed 09:00:02.250Z frames 3 to 8 carry no timestamp the server closes the stream after frame 9, with no final flag
A streamed task opens with the task itself, reports its work as status and artifact frames, and ends when the server closes the stream after the terminal status. Read top to bottom. Unnumbered rows come before the stream, and each numbered row is one data frame, numbered as in the streaming capture. Timestamps on the right come from the kit clock, and the task id is shortened to 8 characters.

Before the first frame

Unless the card sets capabilities.streaming to true, the agent must refuse SendStreamingMessage with UnsupportedOperationError, as discovery explains. The request itself is short:

the request that opens the streamhttpFull log
POST /a2a/jsonrpc HTTP/1.1
Host: localhost:41241
Content-Type: application/json
A2A-Version: 1.0
Accept: text/event-stream
…
  "method": "SendStreamingMessage",
  "params": {
    "message": {
      "messageId": "5bc8fbbc-bde5-4099-8164-d8399f767c45",
      "role": "ROLE_USER",
      "parts": [
        {
          "text": "Run the unit tests for payments-api at commit 9f3c2e1"
        }
      ]
…
The first lines of the JSON-RPC envelope and its closing brackets are cut.

The client minted the messageId itself, and it sent no taskId and no contextId, so the agent creates both. The response head is HTTP/1.1 200 OK with Content-Type: text/event-stream. Each event after it is one data: line that holds a complete JSON-RPC response, with the request's id of 1.

Frame 1: the task appears

A task stream has a fixed opening: the stream must begin with the Task object. This one is already in TASK_STATE_SUBMITTED:

frame 1, the new taskjsonFull log
"task": {
  "id": "728ba084-97eb-422b-b94b-b0fe9153ce2c",
  "contextId": "c026046c-6890-4830-9618-8fffa35cb080",
  "status": {
    "state": "TASK_STATE_SUBMITTED",
    "timestamp": "2026-10-06T09:00:01.750Z"
  },
  "history": [
    {
      "messageId": "5bc8fbbc-bde5-4099-8164-d8399f767c45",
      "contextId": "c026046c-6890-4830-9618-8fffa35cb080",
      "taskId": "728ba084-97eb-422b-b94b-b0fe9153ce2c",
      "role": "ROLE_USER",
      …
The JSON-RPC envelope, the message parts, and the closing brackets are cut.

The server minted task 728ba084 and context c026046c, and it wrote both ids into its copy of the planner's message. From here on, the planner matches every frame to those two ids. The kit fills the optional timestamp of this first status, and the SDK leaves it out, as the kit README lists.

Frame 2: the work starts

Frame 2 is a statusUpdate that moves the task to TASK_STATE_WORKING, with the status message "Checking out 9f3c2e1 and starting the suite". Every status event names its task and its context, because taskId, contextId, and status are all REQUIRED.

Frames 3 to 8: the results arrive in pieces

The next six frames are artifactUpdate events, a type with no timestamp field. Frames 3 to 7 build one artifact, test-log, one line of the log at a time:

  • Frame 3 carries the artifactId test-log, the name test-log.txt, and the first part. It has no append, so it starts the artifact.
  • Frames 4, 5, and 6 repeat the artifactId, leave out the name, and set append: true.
  • Frame 7 sets both append: true and lastChunk: true, so the log is complete.
  • Frame 8 starts a second artifact, summary, in one chunk: a data part with the media type application/json, and lastChunk: true.

A lastChunk closes one artifact and nothing more: frame 8 carries its own while the task keeps running. Artifact and chunks shows how a client joins the chunks, and what the specification leaves open.

Frame 9 and the close

Frame 9 is the last statusUpdate. It moves the task to TASK_STATE_COMPLETED at 09:00:02.250Z, with the status message "1 failed, 11 passed". The task completed although a test failed, because the failure is part of the result.

Then the server closes the connection, and no frame announces it, because version 1.0 removed the final flag. The rule is that the stream must close when the task reaches a terminal state. At an interrupted state, the specification contradicts itself about whether a stream closes (conflict D3). Interrupted states sets out when a stream closes, and its figure 4.4 follows a stream that stays open.

The task after the stream

After the close, the task remains in the store, and a client reads it back with GetTask. That operation reads the final state of a task after a push notification or after a stream has ended. GetTask and ListTasks covers what it returns.

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