Суть
В привычных паттернах кто-то главный решает, кому передать работу: планировщик, координатор, следующий узел графа. Событийная архитектура убирает это звено. Агенты подключены к шине (Kafka, RabbitMQ), каждый слушает интересующие его типы событий и сам решает, вмешиваться или нет. Результат работы — тоже событие, на которое может отреагировать другой агент.
Выигрыш — в отсутствии единой точки отказа и в естественной масштабируемости: добавить нового агента — значит подписать его на поток, а не переписать граф.
Где это оправдано
Домен узкий, но характерный: реактивные задачи с непредсказуемым моментом наступления. Мониторинг безопасности в реальном времени, транзакционный фрод-мониторинг: здесь момент срабатывания неизвестен заранее, а события идут потоком, а не по запросу пользователя.
Для задач с явным началом и концом («проанализируй этот документ») событийная модель только добавляет сложности — там уместнее fan-out или последовательный пайплайн из Multi Agent Patterns.
Два обязательных требования
В факультативе они сформулированы как жёсткие: без них паттерн разваливается в проде.
Идемпотентность. Агент обязан безопасно переваривать дубликаты событий. Шина гарантирует доставку «хотя бы один раз», а не «ровно один раз» — повтор придёт рано или поздно. Если обработка события списывает деньги или пишет в базу без ключа идемпотентности, повтор превращается в инцидент.
Dead-Letter Queue. События, на которых сломался парсинг ответа модели, уходят в отдельную очередь для ручного разбора. Без DLQ битое событие либо теряется молча, либо бесконечно возвращается в основную очередь и отравляет поток.
Главная слабость: потеря трассировки
Уязвимость паттерна — потеря контекста при трейсинге. В синхронной цепочке traceparent передаётся HTTP-заголовком почти бесплатно (см. W3C Trace Context). В шине событий заголовка нет: идентификатор нужно класть в метаданные сообщения и восстанавливать контекст на стороне подписчика вручную, причём в каждом агенте.
Стоит этим пренебречь — и расследование инцидента превращается в сопоставление меток времени из разных логов. Для системы, где ветвление задумано как норма, это дорого вдвойне.
Идемпотентность и очередь недоставленных
Ключ идемпотентности при недетерминированном ответе. Кажется, что идемпотентность невозможна, раз модель на тот же вход отвечает по-разному. На деле ключ ставится не на ответ, а на событие: идентификатор входящего события плюс версия обработчика. Тогда повторная доставка того же события не запускает вторую генерацию, а возвращает уже сохранённый результат — недетерминированность модели перестаёт иметь значение, потому что генерация происходит ровно один раз. Версия обработчика в ключе нужна, чтобы после осознанного изменения промпта событие можно было обработать заново, а не получить старый ответ навсегда.
Что делать с DLQ. Развилка решается тем, устарело ли событие. События, описывающие факт (заказ создан, документ загружен), переигрывать после фикса можно и нужно — факт не протух. События, описывающие намерение в моменте (пользователь ждёт ответа в чате), переигрывать бессмысленно: получатель ушёл, и повторная обработка создаст ответ в пустоту, а иногда и побочный эффект. Практическая политика — размечать события этим признаком при публикации, а не решать судьбу очереди задним числом; и в любом случае переигрывать с новой версией обработчика в ключе идемпотентности, иначе сработает дедупликация и ничего не произойдёт.
Связано с
- EDA For AI — та же шина, но со стороны потока данных: буфер перед GPU, Fat Events, Event Sourcing как основа объяснимости
- Multi Agent Patterns — паттерны с оркестратором, от которых событийная модель отказывается
- Multi Agent Systems — место паттерна в общей картине MAS
- W3C Trace Context — почему проброс идентификатора здесь становится ручной работой
- Agent Failure Modes — молча потерянное событие не отражается ни в одной метрике
- Hybrid Orchestration — противоположный полюс: максимум детерминизма вместо максимума автономии