A B C D E F G H I J K L M N O P Q R S T V W Y Z А Б В Г Е И К М О П С Т Ц

A2A

A2A

 

A2A (Agent2Agent Protocol) это открытый протокол, по которому ИИ-агенты общаются друг с другом: находят подходящего партнёра, передают ему задачу и забирают результат. Он отвечает не за доступ агента к базе или внешнему API. Тема другая: как две независимые агентные системы договариваются о работе. Разные фреймворки и разные вендоры этому не мешают.

 

Что такое A2A и какую задачу закрывает протокол

Протокол A2A относится к классу протоколов межагентного взаимодействия и работает поверх обычного HTTP. Разработку начала Google. В июне 2025 года проект передан в Linux Foundation. К 2026 году спецификация дошла до стабильной версии 1.0, а вокруг неё собралось больше 150 организаций, среди них Microsoft, Salesforce и ServiceNow. Формально это набор из трёх слоёв: канонической модели данных, абстрактных операций и конкретных транспортных биндингов.

Задача, которую решает протокол, скучная и очень практическая. Когда в компании появляется десяток агентов от разных команд, связывать их попарно самописными интеграциями бессмысленно. Каждый новый агент умножает количество связей. A2A задаёт общий словарь, чтобы агент мог обратиться к незнакомому агенту так же, как браузер обращается к незнакомому сайту.

Ключевых принципов у протокола несколько, и они объясняют большинство его решений.

  • Непрозрачность исполнения. Агенты работают друг с другом по объявленным возможностям и обмениваются только сообщениями. Внутренние промпты, память, инструменты и цепочки рассуждений наружу не отдаются.
  • Повторное использование стандартов. HTTP, JSON-RPC 2.0, Server-Sent Events, OAuth 2.0. Ничего нового изобретать не пришлось.
  • Асинхронность по умолчанию. Задача может выполняться минуты, часы и дни, в том числе с участием человека, и протокол это учитывает.
  • Независимость от модальности. В обмене участвуют текст, файлы и структурированные данные, а не только строки.

Из этих принципов вырастает вся остальная механика, начиная с того, как агент вообще узнаёт о существовании другого агента.

 

Архитектура A2A и роль Agent Card

В обмене участвуют три стороны. Пользователь ставит цель. Клиентский агент (A2A Client) действует от его имени и шлёт запросы. Удалённый агент (A2A Server, Remote Agent) выставляет наружу HTTP-эндпоинт и выполняет работу. Для клиента удалённый агент остаётся чёрным ящиком: видно, что он умеет и что вернул, но не видно, как именно он это сделал.

A2A: клиентский агент получает Agent Card удалённого агента, выбирает интерфейс и делегирует ему задачу

 

Точка входа во всю эту конструкцию одна и называется Agent Card.

 

Как устроена Agent Card

Agent Card это JSON-документ, визитка агента. Рекомендованный способ публикации по спецификации это well-known адрес /.well-known/agent-card.json на домене агента, по правилам RFC 8615. Кроме well-known адреса спецификация упоминает курируемые реестры и прямую конфигурацию клиента, но стандартного API для реестров пока нет. Вот реальная карточка агента из демо к этой статье, отданная сервером на GET-запрос.

# вывод прогона, протокол A2A 1.0, a2a-sdk 1.1.2, 12.08.2026
{
    "name": "BigDataSchool Course Agent",
    "description": "Консультирует по курсам учебного центра BigDataSchool",
    "supportedInterfaces": [
        {
            "url": "http://127.0.0.1:41241/a2a/jsonrpc/",
            "protocolBinding": "JSONRPC",
            "protocolVersion": "1.0"
        }
    ],
    "version": "1.0.0",
    "capabilities": {
        "streaming": true
    },
    "defaultInputModes": [
        "text/plain"
    ],
    "defaultOutputModes": [
        "text/plain"
    ],
    "skills": [
        {
            "id": "course_info",
            "name": "Справка по курсу",
            "description": "Отвечает, о чём курс и сколько он длится",
            "tags": [
                "курсы",
                "обучение"
            ],
            "examples": [
                "Про что курс AGENT?",
                "Сколько длится MLOPS?"
            ]
        }
    ]
}

По карточке клиент понимает четыре вещи: кто этот агент, куда стучаться, что он поддерживает и что умеет. Навыки (skills) описывают конкретные умения с примерами запросов. Блок capabilities объявляет опциональные возможности вроде потоковой выдачи и пуш-уведомлений. В supportedInterfaces перечислены доступные транспорты с адресами. Карточку можно подписать: в версии 1.0 появилась структура AgentCardSignature с JWS по RFC 7515, чтобы клиент мог убедиться, что визитка не подделана по дороге.

 

Три транспортных биндинга протокола

Спецификация описывает операции отдельно от способа их передачи, а затем даёт три готовых биндинга: JSON-RPC 2.0 поверх HTTP, gRPC и HTTP+JSON в стиле REST. Требование функциональной эквивалентности означает, что через любой из них доступны все операции с одинаковой семантикой. Выбор транспорта это вопрос инфраструктуры, а не возможностей. Версия протокола и запрошенные расширения передаются служебными параметрами A2A-Version и A2A-Extensions, для HTTP это обычные заголовки.

 

ИИ-агенты для оптимизации бизнес-процессов

Код курса
AGENT
Ближайшая дата курса
26 октября, 2026
Продолжительность
24 ак.часов
Стоимость обучения
66 000

 

Принцип работы и жизненный цикл задачи

Разговор начинается с операции SendMessage. Клиент отправляет сообщение (Message) с ролью и списком частей (Part), а агент отвечает одним из двух способов. Если запрос простой, возвращается просто ответное сообщение. Если работа требует отслеживания, создаётся задача (Task) с собственным идентификатором и жизненным циклом.

Задача проходит через состояния, имена которых в версии 1.0 пишутся заглавными буквами: TASK_STATE_SUBMITTED, TASK_STATE_WORKING, прерывающие TASK_STATE_INPUT_REQUIRED и TASK_STATE_AUTH_REQUIRED и терминальные TASK_STATE_COMPLETED, TASK_STATE_FAILED, TASK_STATE_CANCELED, TASK_STATE_REJECTED. Достигнув терминального состояния, задача становится неизменяемой: перезапустить её нельзя, уточнение оформляется новой задачей в том же контексте.

A2A, жизненный цикл задачи Task и переходы между состояниями протокола

 

Связность разговора держится на двух идентификаторах. Идентификатор задачи taskId генерирует только сервер, клиент своих значений не придумывает. Идентификатор контекста contextId объединяет несколько задач и сообщений в одну логическую сессию, и именно по нему агент собирает историю переписки. Результат работы приезжает не текстом в чате, а артефактом (Artifact) с именем и собственным идентификатором, поэтому его можно запросить позже отдельно от переписки.

Помимо отправки сообщений протокол описывает ещё несколько операций. GetTask возвращает текущее состояние, ListTasks с курсорной пагинацией показывает список задач, CancelTask пытается отменить работу, SubscribeToTask подписывает на обновления. Отдельно стоят четыре операции управления конфигурациями пуш-уведомлений и GetExtendedAgentCard для расширенной карточки после аутентификации.

 

Три способа получать обновления по задаче

Долгие задачи создают очевидную проблему: клиент не должен висеть на открытом соединении сутки. Протокол предлагает три механизма на выбор.

  • Опрос. Клиент периодически дёргает GetTask и смотрит статус. Просто и работает везде.
  • Потоковая выдача. Операция SendStreamingMessage держит поток событий через Server-Sent Events и присылает обновления статуса и куски артефактов по мере готовности.
  • Пуш-уведомления. Агент сам делает HTTP POST на вебхук клиента, когда происходит значимое событие. Вариант для задач, которые считаются часами.

Важная деталь. Потоковая выдача и пуш-уведомления опциональны. Клиент обязан проверить их поддержку в блоке capabilities, иначе получит ошибку.

 

Чем A2A отличается от MCP

Эти два протокола постоянно ставят рядом, хотя решают разные задачи. Model Context Protocol подключает агента к инструментам и данным, то есть работает вертикально, вниз. A2A связывает агента с другим агентом, то есть работает горизонтально. Подробный разбор соседнего протокола есть в Wiki-статье про Model Context Protocol. Практический взгляд на его устройство даёт материал блога что такое MCP-протокол и почему он важен для LLM.

Критерий MCP A2A
Что соединяет Агента с инструментом или источником данных Агента с другим агентом
Единица работы Вызов инструмента, чтение ресурса Задача с жизненным циклом
Модель партнёра Прозрачная, схема инструмента известна Непрозрачная, известны только объявленные навыки
Обнаружение Список инструментов от сервера Agent Card по well-known адресу или из реестра
Длительные операции Не основной сценарий Состояния задачи, поток событий, пуш-уведомления

На практике оба протокола живут в одном агенте. Через MCP он берёт инструменты. Через A2A отдаёт свои умения соседям и сам просит у них помощи.

 

Разработка и внедрение ML-решений

Код курса
MLOPS
Ближайшая дата курса
9 ноября, 2026
Продолжительность
24 ак.часов
Стоимость обучения
66 000

 

Что изменилось в версии 1.0 и где спотыкаются на практике

Версия 1.0 это не косметическое обновление, а перекладка протокола на proto-first основу. Нормативным источником стал файл спецификации в формате Protocol Buffers, а канонической сериализацией ProtoJSON. Отсюда растут остальные изменения.

  • Значения перечислений переименованы. Вместо строк вида completed теперь TASK_STATE_COMPLETED, вместо user теперь ROLE_USER.
  • Часть сообщения стала плоской. Обёртки TextPart, FilePart и DataPart убраны, содержимое кладётся прямо в Part через поля text, raw, url и data. Поле-дискриминатор kind удалено.
  • Адрес агента переехал. Вместо одного поля url в карточке появился список supportedInterfaces из объектов AgentInterface с транспортом, версией протокола и адресом.
  • Появились подписанные карточки и мультиарендность. Один эндпоинт теперь может обслуживать несколько агентов, а карточку можно проверить криптографически.

Понимание этих изменений экономит время при чтении чужих примеров. Половина туториалов в сети написана под версию 0.3 и на 1.0 просто не запустится.

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

  • Первая. Если в AgentInterface не указать версию протокола явно, карточка объявляется как 0.3. Ручной запрос без заголовка A2A-Version тогда падает с ошибкой -32009 про неподдерживаемую версию.
  • Вторая. Клиент из SDK по умолчанию ждёт ответ недолго, а локальная модель думает десятки секунд. Без своего HTTP-клиента с увеличенным таймаутом прогон падает с A2AClientTimeoutError.
  • Третья про порядок событий. Объект Task обязан уйти в поток первым, иначе SDK бросает InvalidAgentResponseError. Смешивать сообщения с событиями задачи в одном потоке тоже нельзя.

 

Когда A2A нужен, а когда избыточен

Протокол хорошо ложится на вполне конкретные сценарии и плохо на все остальные.

  • Разные владельцы. Агенты написаны разными командами или вендорами, общего кода и общей памяти у них нет.
  • Длинные задачи. Работа считается минутами и часами, требует согласования с человеком или дозапроса данных.
  • Специализация. Один агент собирает ответ, привлекая узких специалистов, и не хочет знать, как они устроены внутри.
  • Внешняя экспозиция. Агента нужно выставить наружу как сервис, с аутентификацией и понятным контрактом.

Если же все агенты живут в одном процессе и одном репозитории, A2A добавит HTTP-слой и сериализацию там, где хватило бы вызова функции. Для оркестрации внутри одного приложения ближе графовые фреймворки, например LangGraph. Собирать агентные системы целиком учат на курсе ИИ-агенты для оптимизации бизнес-процессов.

 

Практика с двумя агентами на a2a-sdk

По традиции весь код используемый в статье выкладываем на наш GitHub репозиторий
A2A (Agent2Agent Protocol) from airflow import DAG from airflow.operators.bash import BashOperator from datetime import datetime with DAG( dag_id="spark_submit_demo", start_date=datetime(2025, 1, 1), schedule="@daily", catchup=False ) as dag: run = BashOperator( task_id="run_job", bash_command="spark-submit app.py" ) GitHub code example A2A (Agent2Agent Protocol)

 

Демо состоит из двух файлов. Первый, course_agent.py, поднимает удалённого агента. Он публикует карточку, обслуживает JSON-RPC биндинг и отвечает по каталогу курсов, спрашивая модель qwen2.5:7b через Ollama.

# a2a-sdk 1.1.2 (протокол A2A 1.0), Python 3.12.13, Ollama 0.32.9, модель qwen2.5:7b
"""Удалённый агент A2A: отвечает на вопросы по курсам BigDataSchool.

Публикует Agent Card по адресу /.well-known/agent-card.json и обслуживает
JSON-RPC биндинг протокола A2A на /a2a/jsonrpc/.
"""

import uvicorn
from ollama import AsyncClient
from starlette.applications import Starlette

from a2a.helpers import (
    get_message_text,
    new_task_from_user_message,
    new_text_artifact_update_event,
    new_text_status_update_event,
)
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes
from a2a.server.tasks import InMemoryTaskStore
from a2a.types import (
    AgentCapabilities,
    AgentCard,
    AgentInterface,
    AgentSkill,
    TaskState,
)

HOST = "127.0.0.1"
PORT = 41241
MODEL = "qwen2.5:7b"

# Мини-каталог, который агент держит у себя. Клиентскому агенту он не виден:
# в A2A удалённый агент остаётся чёрным ящиком.
CATALOG = """
AGENT - ИИ-агенты для оптимизации бизнес-процессов, 2 дня, про агентные системы и LLM.
MLOPS - Разработка и внедрение ML-решений, 3 дня, про промышленный цикл ML.
KAFKA - Apache Kafka: администрирование кластера, 3 дня, про эксплуатацию брокера.
"""

SYSTEM_PROMPT = (
    "Ты консультант учебного центра. Отвечай строго по каталогу ниже, "
    "на русском языке, одним абзацем не длиннее 40 слов. "
    "Если курса в каталоге нет, так и скажи.\nКаталог:" + CATALOG
)

# Agent Card: паспорт агента, по которому клиент понимает, что агент умеет
# и куда стучаться. В версии 1.0 адрес живёт в supported_interfaces.
AGENT_CARD = AgentCard(
    name="BigDataSchool Course Agent",
    description="Консультирует по курсам учебного центра BigDataSchool",
    version="1.0.0",
    supported_interfaces=[
        AgentInterface(
            protocol_binding="JSONRPC",
            protocol_version="1.0",
            url=f"http://{HOST}:{PORT}/a2a/jsonrpc/",
        )
    ],
    default_input_modes=["text/plain"],
    default_output_modes=["text/plain"],
    capabilities=AgentCapabilities(streaming=True),
    skills=[
        AgentSkill(
            id="course_info",
            name="Справка по курсу",
            description="Отвечает, о чём курс и сколько он длится",
            tags=["курсы", "обучение"],
            examples=["Про что курс AGENT?", "Сколько длится MLOPS?"],
        )
    ],
)


class CourseAgentExecutor(AgentExecutor):
    """Исполнитель задачи: переводит запрос A2A в вызов локальной LLM."""

    def __init__(self) -> None:
        self._llm = AsyncClient()

    async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
        # Шаг 1. Заводим Task. По правилам A2A 1.0 объект Task обязан уйти
        # в очередь событий первым, иначе SDK бросит InvalidAgentResponseError.
        task = context.current_task or new_task_from_user_message(context.message)
        await event_queue.enqueue_event(task)

        # Шаг 2. Сообщаем клиенту, что взяли работу в обработку.
        await event_queue.enqueue_event(
            new_text_status_update_event(
                task_id=task.id,
                context_id=task.context_id,
                state=TaskState.TASK_STATE_WORKING,
                text="Смотрю каталог курсов",
            )
        )

        # Шаг 3. Собственно работа. Что тут внутри, клиенту знать не нужно.
        question = get_message_text(context.message)
        response = await self._llm.chat(
            model=MODEL,
            messages=[
                {"role": "system", "content": SYSTEM_PROMPT},
                {"role": "user", "content": question},
            ],
            options={"temperature": 0.1},
        )
        answer = response["message"]["content"].strip()

        # Шаг 4. Результат работы уезжает артефактом, а не просто текстом
        # в чате: артефакт привязан к задаче и его можно запросить позже.
        await event_queue.enqueue_event(
            new_text_artifact_update_event(
                task_id=task.id,
                context_id=task.context_id,
                name="course_answer",
                text=answer,
                last_chunk=True,
            )
        )

        # Шаг 5. Терминальное состояние закрывает жизненный цикл задачи.
        await event_queue.enqueue_event(
            new_text_status_update_event(
                task_id=task.id,
                context_id=task.context_id,
                state=TaskState.TASK_STATE_COMPLETED,
                text="Готово",
            )
        )

    async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
        # Отмена в демо не поддержана: спецификация разрешает вернуть ошибку.
        raise NotImplementedError("Отмена задачи в этом агенте не реализована")


def build_app() -> Starlette:
    handler = DefaultRequestHandler(
        agent_executor=CourseAgentExecutor(),
        task_store=InMemoryTaskStore(),
        agent_card=AGENT_CARD,
    )
    routes = create_agent_card_routes(AGENT_CARD)
    routes += create_jsonrpc_routes(handler, rpc_url="/a2a/jsonrpc/")
    return Starlette(routes=routes)


if __name__ == "__main__":
    uvicorn.run(build_app(), host=HOST, port=PORT, log_level="warning")

Второй файл, client_agent.py, играет роль клиентского агента. Он скачивает карточку по well-known адресу, собирает по ней клиента и делегирует задачу, разбирая приходящий поток событий.

# a2a-sdk 1.1.2 (протокол A2A 1.0), Python 3.12.13, прогон против course_agent.py
"""Клиентский агент A2A: находит удалённого агента по Agent Card и делегирует ему задачу."""

import asyncio

import httpx

from a2a.client import A2ACardResolver, ClientConfig, create_client
from a2a.helpers import get_stream_response_text, new_text_message
from a2a.types import Role, SendMessageRequest, TaskState

AGENT_BASE_URL = "http://127.0.0.1:41241"
QUESTION = "Про что курс AGENT и сколько он длится?"


async def main() -> None:
    async with httpx.AsyncClient(timeout=120) as http:
        # Шаг 1. Discovery: карточка агента лежит по well-known адресу
        # /.well-known/agent-card.json, скачивается обычным GET-запросом.
        resolver = A2ACardResolver(httpx_client=http, base_url=AGENT_BASE_URL)
        card = await resolver.get_agent_card()

        print("=== Agent Card ===")
        print("name:", card.name)
        print("version:", card.version)
        for iface in card.supported_interfaces:
            print("interface:", iface.protocol_binding, iface.url)
        print("streaming:", card.capabilities.streaming)
        for skill in card.skills:
            print("skill:", skill.id, "|", skill.name)

        # Шаг 2. Клиент собирается по карточке: транспорт и адрес берутся из неё,
        # руками URL метода никто не склеивает.
        client = await create_client(card, ClientConfig(httpx_client=http))

        # Шаг 3. Отправка сообщения. Ответом приходит поток событий: сначала Task,
        # затем обновления статуса и артефакты.
        request = SendMessageRequest(
            message=new_text_message(QUESTION, role=Role.ROLE_USER)
        )
        print("\n=== Поток событий ===")
        answer = ""
        async for chunk in client.send_message(request):
            if chunk.HasField("task"):
                print(f"task: id={chunk.task.id} state={TaskState.Name(chunk.task.status.state)}")
            elif chunk.HasField("status_update"):
                state = TaskState.Name(chunk.status_update.status.state)
                print(f"status_update: {state} | {get_stream_response_text(chunk)}")
            elif chunk.HasField("artifact_update"):
                answer = get_stream_response_text(chunk)
                print(f"artifact_update: name={chunk.artifact_update.artifact.name}")
            elif chunk.HasField("message"):
                print("message:", get_stream_response_text(chunk))

        print("\n=== Ответ удалённого агента ===")
        print(answer)


if __name__ == "__main__":
    asyncio.run(main())

Тот же агент вызывается и без SDK, голым HTTP-запросом. Это удобно, чтобы увидеть формат протокола своими глазами.

# прогон 12.08.2026, протокол A2A 1.0, заголовок версии обязателен
curl -s -X POST http://127.0.0.1:41241/a2a/jsonrpc/ \
  -H "Content-Type: application/json" \
  -H "A2A-Version: 1.0" \
  -d '{"jsonrpc":"2.0","id":"req-1","method":"SendMessage","params":{"message":{"role":"ROLE_USER","messageId":"msg-1","parts":[{"text":"Сколько длится курс MLOPS?"}]}}}'

Вывод клиентского агента укладывает механику протокола в четыре строки. Задача создана, перешла в работу, отдала артефакт и закрылась.

вывод клиентского агента с потоком событий задачи и ответом удалённого агента на модели qwen2.5:7b

 

Заключение

A2A даёт агентным системам то, чего им не хватало: общий контракт на делегирование работы между независимыми агентами. Три вещи стоит запомнить. Точка входа это Agent Card по well-known адресу. Единица работы это задача с неизменяемым финальным состоянием и артефактами. Партнёр всегда остаётся чёрным ящиком. Это не ограничение, а осознанный выбор: агенты разных вендоров работают вместе, не раскрывая внутренностей.

 

Референсные ссылки