Message и Part переносят разговор, а не task status
A2A-сообщение устроено просто, но с важным ограничением. У Message есть роль, messageId, parts и контекстные ссылки. Part может нести text, file или структурированные data. А вот статус задачи и artifacts живут в других объектах - и это не случайность: разговор намеренно не смешивается с результатом работы, чтобы одно можно было менять, не задевая другое.
Типизированное пользовательское сообщение из клиентского демо выглядит так.
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 campaignreferenceTaskIds- Явные ссылки на предыдущие 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, - и у них полноценный жизненный цикл.