3.1

Message

A Message is one turn in a conversation, and its role and three ids say which way it travels and which task it continues.

The planner sends code-reviewer a diff to review. The reviewer asks which base branch to compare against, the planner answers main, and the reviewer finishes the task with two findings.

When you finish this section, you can build a valid message and set its ids to continue a task.

messageId, role, and parts

Message: one unit of communication between the client and the server. It has eight fields, and only messageId, role, and parts are REQUIRED. The parts list must contain at least one element, and Part explains what a part holds.

Figure 3.1 sets three messages of that review side by side and names who sets each field.

Fig. 3.1who sets each field of a messagestructure
Who sets each field of a message A table of the eight Message fields across three messages of the review in 06-input-required.http, which appear one column at a time in the order they were sent. The planner first message 14a03569 has no contextId and no taskId. The code-reviewer question 46494a30 carries contextId 49c5a238 and taskId e5c95b09. The planner answer 096d3737 echoes both ids. Roles are ROLE_USER, ROLE_AGENT and ROLE_USER. metadata, extensions and referenceTaskIds are not set in any of them. A right column says who sets each field, and a last note says the server writes both ids into its stored copy of the first message. FIELD WHO SETS IT messageId minted by the sender for every message contextId minted by the server, echoed by the client taskId minted by the server, echoed to continue a task role the direction: client to server is USER parts the sender, at least one part metadata not set in any kit capture the sender: a JSON object extensions not set in any kit capture the sender: extension URIs referenceTaskIds not set in any kit capture the sender: related tasks REQUIRED in proto Message planner first message 14a03569 omitted omitted ROLE_USER text, raw code-reviewer its question 46494a30 49c5a238 e5c95b09 ROLE_AGENT text planner the answer 096d3737 49c5a238 e5c95b09 ROLE_USER text "main" The server writes contextId 49c5a238 and taskId e5c95b09 into its stored copy of the first message, which the planner sent without them.
A message carries an id its sender mints, while its contextId and taskId come from the server and the client echoes them to continue. Columns are three messages from the input-required capture, in the order they were sent. The right column names who sets each field. Ids shortened to 8 characters.

The sender of the message creates messageId. The kit refuses a message without one, as Errors shows. Spec samples in §11.2 and §6.3 leave messageId out, so never copy a sample as it stands (conflict D23).

Role: the direction a message travels. The value ROLE_USER marks a message from the client to the server, and ROLE_AGENT marks one from the server to the client. The planner is an agent, yet its messages to code-reviewer carry ROLE_USER, because it is the client there.

The glossary in §2.2 still writes the roles as "user" and "agent" (conflict D35). The spec names no error for a client that sends ROLE_AGENT, and the kit answers -32602, as the kit README lists.

contextId and taskId

A client message with neither id starts something new. The server's messages then carry the contextId it minted, plus the taskId of any task it creates. The direct reply in 02-message-reply.http carries a contextId and no taskId, because the reviewer answered without creating a task:

a server message with a context and no taskjsonFull log
  "result": {
    "message": {
      "messageId": "3b4b1206-4b6f-467f-b735-2d7e4f7f4bcd",
      "contextId": "dc423c62-d5f4-48e5-82c0-4963b9bc4334",
      "role": "ROLE_AGENT",
      "parts": [
        {
          "text": "I review unified diffs in Python, TypeScript and Go. Send the diff as a text/x-diff part and name the base branch."
        }
      ]
    }
  }
The result of the only exchange in the file, complete.

To continue a task, the client copies both ids from the task into its next message, as the planner's answer does:

the planner's answer, with both ids copied from the taskjsonFull log
    "message": {
      "messageId": "096d3737-42f9-4039-8320-a4737c2b3abe",
      "contextId": "49c5a238-4158-455d-8d63-b2cb39c0ecac",
      "taskId": "e5c95b09-6d4b-4b3b-8693-91e81472d12f",
      "role": "ROLE_USER",
      "parts": [
        {
          "text": "main"
        }
      ]
    }
The message of exchange 2, complete.

Three rules govern those two ids:

  • A client taskId must name a live task. A taskId that a client puts in a message must refer to a task that exists. An unknown id gets TaskNotFoundError, and CancelTask and terminal states covers a finished task.
  • The two ids must agree. An agent must reject a message whose contextId and taskId do not match. The spec names no error for this, and both the kit and the reference SDK answer -32602.
  • The context follows the task. When a message has only a taskId, the agent must take the contextId from that task. The kit does, and the table below says what each SDK does.

A message with a contextId and no taskId starts a new task in that conversation, as contextId shows.

What the SDKs do with the ids

Send Message operations are allowed to be idempotent, and an agent can use the messageId to find duplicate messages. So a retry of a send that timed out can reuse the same messageId. No SDK rejects a repeated id, and without a taskId each one creates another task for it.

SDKMessage with only taskIdSame messageId sent twice to one task
Python 1.2.2Does not read the task, and stamps a fresh contextId on the messageAccepted. The history skips a second copy, and the agent runs again
Go v2.6.0The agent context gets the task's contextId, and the stored message stays as sentAccepted. The history skips a second copy, and the agent runs again
Java v1.4.0.FinalThe agent context and its events get the task's contextId, and the stored message stays as sentAccepted. The message is appended to history again, and the agent runs again
JS v1.3.0The agent context and its copy of the message get the task's contextId, and the stored message stays as sentAccepted. The message is appended to history again, and the agent runs again
.NET v1.0.0-preview2The agent context gets the task's contextId, and the stored message stays as sentAccepted. The message is appended to history again, and the handler runs again
Rust a2a-server-lf-v0.5.1The executor context gets the task's contextId, and the stored message stays as sentAccepted. The message is appended to history again, and the executor runs again

Five SDKs infer the context for the agent and store the message as sent, and the reference SDK generates a new one. Send both ids, and the difference never reaches you.

Optional fields and the status message

No message in the kit's captures sets these three:

  • referenceTaskIds lists the ids of tasks that the message points to for more context.
  • extensions lists the URIs of the extensions that are present in the message or that added to it, and extensions covers them.
  • metadata is a free-form JSON object.

Status message: the one message a TaskStatus can hold, the agent's words inside a task. The reviewer's question was a status message, and the findings themselves arrive in the review.json artifact, as what A2A standardizes explains.

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