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.
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:
"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."
}
]
}
}To continue a task, the client copies both ids from the task into its next message, as the planner's answer does:
"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"
}
]
}Three rules govern those two ids:
- A client
taskIdmust name a live task. AtaskIdthat a client puts in a message must refer to a task that exists. An unknown id getsTaskNotFoundError, and CancelTask and terminal states covers a finished task. - The two ids must agree. An agent must reject a message whose
contextIdandtaskIddo 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 thecontextIdfrom 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.
| SDK | Message with only taskId | Same messageId sent twice to one task |
|---|---|---|
| Python 1.2.2 | Does not read the task, and stamps a fresh contextId on the message | Accepted. The history skips a second copy, and the agent runs again |
| Go v2.6.0 | The agent context gets the task's contextId, and the stored message stays as sent | Accepted. The history skips a second copy, and the agent runs again |
| Java v1.4.0.Final | The agent context and its events get the task's contextId, and the stored message stays as sent | Accepted. The message is appended to history again, and the agent runs again |
| JS v1.3.0 | The agent context and its copy of the message get the task's contextId, and the stored message stays as sent | Accepted. The message is appended to history again, and the agent runs again |
| .NET v1.0.0-preview2 | The agent context gets the task's contextId, and the stored message stays as sent | Accepted. The message is appended to history again, and the handler runs again |
| Rust a2a-server-lf-v0.5.1 | The executor context gets the task's contextId, and the stored message stays as sent | Accepted. 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:
referenceTaskIdslists the ids of tasks that the message points to for more context.extensionslists the URIs of the extensions that are present in the message or that added to it, and extensions covers them.metadatais 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