Глава 23

Message и Part переносят разговор, а не task status

A2A-сообщение устроено просто, но с важным ограничением. У Message есть роль, messageId, parts и контекстные ссылки. Part может нести text, file или структурированные data. А вот статус задачи и artifacts живут в других объектах - и это не случайность: разговор намеренно не смешивается с результатом работы, чтобы одно можно было менять, не задевая другое.

Типизированное пользовательское сообщение из клиентского демо выглядит так.

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: []
  };
}

Что где живёт, удобно держать в одной таблице - она же защищает от соблазна впихнуть статус задачи в сообщение.

Объект · Назначение · Пример

  • Message - Коммуникация между ролями; "Проверь релиз web-portal@2.4.0"
  • Part text - Человеческий текст; text/plain
  • Part data - Структурированный payload; Release reference или report JSON
  • Part file - Файл/URI/bytes по binding; Лог или спецификация
  • contextId - Группа связанных interactions; Один release campaign
  • referenceTaskIds - Явные ссылки на предыдущие tasks; Сравнить два review

Разделение здесь работает на композицию. Message - это коммуникация между ролями. Part text - человеческий текст, Part data - структурированный payload, Part file - файл по правилам binding. contextId группирует связанные interactions в один release campaign, referenceTaskIds явно ссылается на предыдущие задачи. Ни одно из этих полей не хранит статус - для него есть Task.

Отдельная тонкость - согласование форматов. Клиент указывает принимаемые output modes, а Agent Card объявляет поддерживаемые. Сервер не должен присылать произвольный binary blob клиенту, который согласился только на text/markdown.

contextId является opaque. Клиент не должен угадывать его структуру или использовать как authorization proof. Сервер может устанавливать expiration и обязан не смешивать контексты разных principals - иначе группировка разговоров станет каналом утечки.

Сообщения переносят разговор. А результат работы и её состояние переносят два других объекта - Task и Artifact, - и у них полноценный жизненный цикл.

Ссылки