Chapter 23

Message and Part Carry the Conversation, Not Task Status

An A2A message is simply built, but with an important constraint. A Message has a role, a messageId, parts, and context references. A Part may carry text, a file, or structured data. But the task's status and artifacts live in other objects - and that's not accidental: the conversation deliberately doesn't mix with the result of the work, so one can be changed without touching the other.

A typed user message from the client demo looks like this.

TypeScript
function userMessage(text: string): Message {
  return {
    messageId: randomUUID(),
    contextId: "",
    taskId: "",
    role: Role.ROLE_USER,
    parts: [{
      content: { $case: "text", value: text },
      metadata: undefined,
      filename: "",
      mediaType: "text/plain"
    }],
    metadata: undefined,
    extensions: [],
    referenceTaskIds: []
  };
}

What lives where is convenient to keep in one table - it also guards against the temptation to cram task status into a message.

Object · Purpose · Example

  • Message - Communication between roles; "Check the release web-portal@2.4.0"
  • Part text - Human text; text/plain
  • Part data - A structured payload; A release reference or report JSON
  • Part file - A file/URI/bytes by the binding; A log or a spec
  • contextId - A group of related interactions; One release campaign
  • referenceTaskIds - Explicit references to previous tasks; Compare two reviews

The split here works for composition. A Message is communication between roles. A Part text is human text, a Part data is a structured payload, a Part file is a file by the binding's rules. contextId groups related interactions into one release campaign, referenceTaskIds explicitly references previous tasks. None of these fields holds status - for that there is the Task.

A separate subtlety is format negotiation. The client states the accepted output modes, and the Agent Card declares the supported ones. The server must not send an arbitrary binary blob to a client that only agreed to text/markdown.

contextId is opaque. The client must not guess its structure or use it as an authorization proof. The server may set an expiration and must not mix the contexts of different principals - otherwise grouping conversations becomes a leak channel.

Messages carry the conversation. The result of the work and its state are carried by two other objects - Task and Artifact - and they have a full lifecycle.

Links