Событие как единица, а не запрос и ответ
Обычная интеграция агента в интерфейс — это вызов, который через какое-то время возвращает текст, плюс самодельный поток частичных кусков поверх него. AG-UI переворачивает: единицей общения является событие, а сообщение собирается из событий на стороне интерфейса.
Категории событий разведены по назначению: жизненный цикл прогона, текстовые сообщения, вызовы инструментов, состояние, активность, подагенты и специальные. Транспорт не фиксирован — годится всё, по чему события можно доставить: server-sent events, веб-сокеты, вебхуки.
Границы прогона обязательны. Прогон открывается RunStarted и закрывается ровно одним из двух — RunFinished при успехе либо RunError при отказе; шаги внутри (StepStarted / StepFinished) необязательны и могут повторяться. Обязательность границ — не формальность: она даёт интерфейсу точку, в которой можно включить индикатор, и точку, в которой его обязательно выключат, в том числе при отказе.
Состояние: снимок заменяет, дельта дополняет
Состояние агента синхронизируется парой событий с явно разными правилами применения:
StateSnapshot— полное состояние; интерфейс заменяет им своё представление, а не сливает с прежним;StateDelta— частичное изменение, приезжающее как JSON Patch;MessagesSnapshot— то же для истории сообщений.
Схема «редкий снимок, частые дельты» решает две задачи разом: снимок — точка синхронизации, возвращающая интерфейс к известному состоянию, дельты — дешёвый поток между снимками. Правило «снимок заменяет, а не сливается» здесь важнее, чем кажется: слияние снимка со старым состоянием оставило бы в интерфейсе поля, которых у агента больше нет, — и расхождение осталось бы навсегда, потому что следующие дельты его не тронут (State Centric Execution — то же различение канонического состояния и производного представления внутри системы).
Остановка ради человека — исход прогона, а не звонок из процедуры
Самое переносимое место протокола. RunFinished несёт необязательный исход в виде объединения вариантов, и один из вариантов — interrupt с непустым списком открытых прерываний. Возобновление устроено так: клиент начинает новый прогон, во входе которого лежит массив resume, адресующий каждое открытое прерывание.
Три следствия, каждое полезное само по себе:
- ожидание человека — это завершение, а не зависший вызов. Прогон закрыт корректно, ресурсы освобождены, а ожидание живёт снаружи как состояние;
- адресуется каждое открытое прерывание, а не «последнее», — то есть протокол допускает несколько одновременных вопросов и требует ответить на все;
- старый производитель, ничего не знающий об исходах, остаётся совместимым: отсутствие поля трактуется как обычное завершение.
Это ровно та форма, которую Human in the Loop описывает как «подтверждение — хранимое состояние, а не звонок в середине процедуры», только зафиксированная в протоколе, а не оставленная на реализацию.
Метаданные приезжают позже того, к чему относятся
У всех событий общий набор полей: тип, необязательная отметка времени, rawEvent и metadata.
metadata — открытый по ключам объект, объявленный один раз на базовом событии, поэтому его несёт каждый тип. Потребитель сливает метаданные события в собираемое сообщение по ключам, и последняя запись побеждает. Назначение названо прямо и оно неочевидно: так производитель может прислать расход токенов на последнем событии сообщения, не зная его в начале. Приём общий — телеметрию, известную только по завершении, не приходится ни угадывать, ни слать отдельным каналом (Agent CostControl).
rawEvent хранит исходное событие, если оно было преобразовано по дороге. Это сохранение происхождения на транспортном уровне: перевод во внешний словарь не уничтожает оригинал, и разбор расхождения остаётся возможным (Agent Audit Log).
subagentRunId есть у большинства событий и указывает, какой подагент их произвёл; событие без него принадлежит родителю. То есть атрибуция вывода — часть протокола, а не догадка интерфейса по тексту (Multi Agent Patterns).
Отдельно — parentRunId у RunStarted: указатель на предыдущий прогон в том же потоке, дающий ветвление и перемещение по истории. Документация описывает получающуюся структуру как журнал, в который только дописывают (LangGraph Time Travel — тот же приём внутри графа).
Генеративный интерфейс
Последняя из встроенных возможностей: ответ приходит не только прозой, но и структурными сообщениями, из которых интерфейс собирает элементы — форму, карточку, таблицу, кнопку подтверждения. Прикладной смысл тот же, что у плотности подачи в ревью: элемент интерфейса передаёт то же самое быстрее, чем абзац текста, и, в отличие от абзаца, допускает действие в ответ (Parallel Agent Dispatch — ускорение восприятия результата как отдельный рычаг).
Цена — связность: набор элементов, которые агент может попросить нарисовать, становится частью контракта между ним и приложением, и менять его в одностороннем порядке нельзя.
Место в стеке протоколов
Четыре открытых протокола отвечают на четыре разных вопроса, и путать их не стоит:
| Протокол | Вопрос |
|---|---|
| MCP | что агент умеет: инструменты, данные, контекст |
| Agent2Agent Protocol | как агенты разговаривают друг с другом |
| Agent Client Protocol | кто пускает агента в среду и опосредует его действия |
| AG-UI | как то, что делает агент, попадает к человеку в интерфейс |
Сами авторы AG-UI описывают своё место так же и называют протокол дополняющим, а не конкурирующим. Практически это подтверждается перечнем поддержанных каркасов: LangGraph, CrewAI, Mastra, Pydantic AI, LlamaIndex, Agno, Google ADK, Microsoft Agent Framework, AWS Strands.
Связано с
- Agent Client Protocol — соседний слой: там среда и права, здесь интерфейс и человек
- Agent Harness — тот же поток событий изнутри обвязки, включая приём «слой перевода вместо внутренностей наружу»
- Human in the Loop — прерывание как состояние, а не как блокирующий вызов
- State Centric Execution — снимок против дельты внутри системы, здесь то же на границе с интерфейсом
- Multi Agent Patterns — атрибуция вывода подагенту, вынесенная в поле события
- Agent Observability — почему поток, на котором держится интерфейс, нельзя выключить ради экономии
- MCP, Agent2Agent Protocol — остальные слои стека