Агент повторяет запрос к недоступной зависимости, и очередь одинаковых операций растёт быстрее, чем её успевают разбирать. Ниже — как остановить повторы и вернуться к зависимости контролируемо, не создавая дублей.
Circuit breaker для ИИ-агента — механизм, который при устойчивых сбоях зависимости перестаёт отправлять запросы и возвращается к ней только контролируемой пробой. Он опирается на карточку зависимости: класс операции, ключ идемпотентности, окно ошибок, политику повтора и владельца. Тайм-аут после отправки считается неизвестным результатом, а не ошибкой, и не повторяется автоматически.

Сцена, в которой агент застревает
Условный ИИ-продавец в мессенджере отправляет заказ в условную систему учёта. В 11:40 та отвечает тайм-аутом, но заказ на самом деле создан. Агент считает попытку неудачной и повторяет запрос каждые 20 секунд. За следующие полчаса агент делает ещё 90 попыток с интервалом 20 секунд, менеджер видит три из них и не понимает, какой настоящий.
Механизм проблемы простой: агент не отличает «сервис недоступен» от «ответ потерялся по дороге». Для безопасного повтора нужно знать контракт операции и её фактический исход; одного тайм-аута недостаточно. Пока это различие не зафиксировано в коде и в рабочем объекте, любая защита от сбоев превращается в ускоритель дублей. Circuit breaker (предохранитель) решает не задачу «повторить поумнее», а задачу «перестать долбить зависимость и вернуться к ней контролируемо». Это разные вещи, и путаница между ними стоит дороже, чем сам сбой.
Если ваша задача — настроить остановку повторов, на странице соответствующего направления 404ai можно посмотреть опубликованные возможности и оставить описание текущего процесса. Конкретные подключения, исходные данные и критерии приёмки согласуем отдельно.
Почему повтор — не лечение, а симптом
Классический предохранитель из документации Microsoft описывает три состояния: закрыт (запросы идут), открыт (запросы сразу отклоняются), полуоткрыт (пропускается пробный запрос). Логика простая, но работает она только тогда, когда у нас есть чем измерить «зависимость больна». Обычно берут долю ошибок за окно времени. И тут начинается первая развилка: что считать ошибкой.
Тайм-аут, ответ 500 и «соединение сброшено» — это разные вещи с точки зрения повтора. Тайм-аут после отправки запроса не говорит, выполнилась ли операция на той стороне. Ответ 500 означает внутреннюю ошибку сервера и не говорит, выполнена ли операция. Сброс соединения — вообще лотерея. Если все три считать одинаково, предохранитель будет открываться и закрываться по случайному сигналу, а очередь продолжит копиться в моменты, когда он закрыт.
Поэтому первое, что нужно сделать до всякой настройки порогов, — разметить операции по признаку идемпотентности. Создание заказа, отправка сообщения клиенту, списание — не идемпотентны. Чтение статуса, получение справочника — идемпотентны. Для первых повтор допустим только с ключом идемпотентности, который зависимость умеет распознавать. Если такого механизма нет, автоматический повтор потенциально опасной записи останавливают до проверки исхода.
Предохранитель зависимости: карточка зависимости, а не флаг «включено»
Предохранитель, который живёт в виде булева флага в конфиге, бесполезен, потому что не помнит, почему открылся и что делать дальше. Нужен рабочий объект — карточка зависимости. У неё есть имя, адрес, тип операции (идемпотентная/нет), текущее состояние, время последнего перехода, причина перехода и владелец, который отвечает за ручное вмешательство.
| Поле карточки | Зачем нужно | Пример условного значения |
|---|---|---|
| Идентификатор зависимости | Отличать сбой одной системы от другой | billing-api |
| Класс операции | Решает, можно ли повторять | create_invoice — не идемпотентна |
| Состояние | Закрыт / открыт / полуоткрыт / ручная блокировка | открыт |
| Причина | Что именно триггернуло переход | 5 тайм-аутов за 60 с |
| Окно наблюдения | На чём считается доля ошибок | 60 с, минимум 10 запросов |
| Политика повтора | Сколько и как часто | не повторять без ключа |
| Владелец | Кто разбирает ручную блокировку | дежурный по интеграциям |
Карточка — это не дашборд ради дашборда. Это контракт: пока в ней не заполнены класс операции и политика повтора, предохранитель не имеет права открываться автоматически, потому что не знает, что делать с уже отправленными запросами. Пустая карточка честнее, чем настроенный наугад порог.
Эпизод первый: тайм-аут после выполнения
Условный интегратор подключил агента к условной CRM клиента. Агент создаёт сделку и сразу пишет клиенту «ваша заявка принята». В 15:02 CRM отвечает тайм-аутом через 30 секунд, хотя сделку создала. Агент по умолчанию повторяет запрос — и через минуту в CRM две сделки на одного клиента. Клиент получает два подтверждения.
Что должно было произойти. Перед отправкой агент генерирует ключ идемпотентности и кладёт его в карточку операции. При тайм-ауте операция переходит в состояние «результат неизвестен», а не «ошибка». Дальше агент не повторяет создание, а запрашивает статус по ключу — это идемпотентное чтение. Если CRM поддерживает такой запрос, дубль исключён. Если не поддерживает, агент останавливается и передаёт операцию человеку с явной пометкой «возможно, выполнено, проверьте».
Ключевое здесь — не техническая деталь, а правило: тайм-аут после отправки никогда не трактуется как «не выполнено». Это правило должно быть записано в карточке зависимости, а не в голове разработчика. Иначе следующий сотрудник, который будет править код, вернёт повтор «для надёжности».
Где остановиться и что считать пробой
Когда предохранитель открыт, новые запросы не уходят. Это защищает зависимость от лавины, но создаёт другую проблему: очередь. Запросы, которые пришли в открытое состояние, нельзя просто выбрасывать — часть из них клиентские действия. Их нужно либо явно отклонить с понятным сообщением, либо отложить с ограничением по возрасту.
Отложенная очередь — опасная штука. Если зависимость лежала час, а в очереди 200 заказов, то в момент восстановления они все уйдут разом и положат её снова. Поэтому в карточке нужен параметр «максимальный возраст отложенной операции». Всё, что старше, не отправляется автоматически, а уходит на ручной разбор. Это неприятно, но честнее, чем тихо отправить клиенту устаревшее подтверждение через два часа.
Проба в полуоткрытом состоянии — отдельная ловушка. Документация предлагает пропускать один запрос, чтобы проверить, ожил ли сервис. Но если этот запрос — создание заказа, проба меняет клиентские данные. Правило: пробным запросом может быть только идемпотентное чтение или операция с ключом идемпотентности. Проверять «жив ли биллинг» через выставление счёта — плохая идея, даже если технически возможно.
Эпизод второй: сервис ожил частично
Условный отдел продаж использует агента, который пишет первым в мессенджерах по согласованному процессу. Зависимость — условный шлюз сообщений. В 09:15 шлюз начинает отвечать 503 на часть запросов: отправка в один канал проходит, в другой — нет. Предохранитель настроен на общую долю ошибок и после порога открывается целиком. В итоге перестают уходить и те сообщения, которые шлюз бы принял.
Это типичная ошибка: предохранитель на всю зависимость, хотя сбой частичный. Решение — карточка на уровне маршрута, а не сервиса. Если у шлюза разные каналы с разной стабильностью, у каждого свой предохранитель. Иначе один сломанный канал блокирует здоровые.
Второй нюанс: частичное восстановление. Допустим, в условном примере шлюз отвечает успешно на 70% запросов. Формально доля ошибок ниже порога, предохранитель закрыт, но 30% клиентов не получают сообщение. Здесь помогает не предохранитель, а отдельный счётчик «недоставленные» с явной пометкой в интерфейсе сотрудника. Предохранитель защищает зависимость, а не гарантирует доставку. Смешивать эти роли — значит обещать себе то, чего механизм не делает.
Предохранитель зависимости на экране сотрудника
Менеджеру не нужны состояния «закрыт/полуоткрыт». Ему нужно понимать три вещи: какие действия клиентов сейчас не проходят, что с ними будет и что он может сделать руками. Поэтому интерфейс строится вокруг операций, а не вокруг зависимостей.
| Что видит сотрудник | Источник | Действие |
|---|---|---|
| «5 заявок ждут отправки в CRM» | Очередь отложенных | Повторить вручную / отклонить |
| «Отправка в канал X приостановлена» | Карточка маршрута | Посмотреть причину |
| «Заявка №… возможно создана, проверьте» | Операция с неизвестным результатом | Открыть в CRM и подтвердить |
| «Автоповтор остановлен до 16:00» | Ручная блокировка | Снять блокировку (с подтверждением) |
Важно, чтобы кнопка «повторить» не была доступна для операций с неизвестным результатом без предварительной проверки. Иначе сотрудник в спешке нажмёт её и создаст тот самый дубль, от которого защищались. Кнопка должна вести не к повтору, а к экрану сверки: показать ключ идемпотентности, время отправки и предложить проверить статус.
Ручная блокировка — отдельное состояние, не пересекающееся с автоматическим. Если дежурный закрыл зависимость вручную, автоматическая проба не должна её открывать. Иначе через пять минут после решения «не трогаем до утра» система сама начнёт слать пробные запросы. В карточке должно быть поле «кто и до какого времени заблокировал», и оно имеет приоритет над автоматом.
Разные зависимости — разные предохранители
Соблазн сделать один общий предохранитель на «внешние API» велик, но он смешивает несмешиваемое. Условный агент может зависеть от модели, от базы знаний, от мессенджера и от CRM. Падение модели не означает, что CRM недоступна. Если предохранитель общий, сбой одного компонента останавливает всё.
Правило простое: предохранитель на границе, где меняется поведение при отказе. Если при недоступности базы знаний агент может ответить общими словами — это одна граница. Если при недоступности CRM он обязан остановиться — другая. Границы определяются не архитектурой сервисов, а тем, что агент делает при отказе.
Отсюда следует неприятный вывод: количество предохранителей равно количеству разных стратегий отказа, а не количеству внешних систем. Иногда у одной системы две стратегии (чтение можно деградировать, запись нельзя) — значит, две карточки.
Предохранитель зависимости: исключения и проверка условий
Первое исключение — зависимость без ключа идемпотентности. Такие встречаются, и тогда единственный безопасный вариант — не повторять автоматически вообще. Предохранитель в этом случае работает как выключатель: при сбое операция уходит в ручной разбор. Это медленно, но предсказуемо.
Второе — операция, результат которой нельзя проверить постфактум. Например, отправка сообщения в канал без истории доставки. Если ответ потерялся, узнать, дошло ли, невозможно. Здесь помогает только предварительная запись намерения в собственный журнал: агент фиксирует «собираюсь отправить X клиенту Y в Z», и при неопределённости сотрудник видит намерение, а не факт. Журнал намерений — не гарантия, но он отделяет «мы не знаем» от «мы не пытались».
Третье — каскад. Зависимость A вызывает B, B вызывает C. Предохранитель на A открывается, но запросы уже ушли в B и C. Нужно, чтобы отмена распространялась вниз. В браузерном контексте для этого есть AbortController: он прекращает ожидание ответа на клиенте, но не гарантирует, что сервер не получил и не выполнил запрос. То есть отмена — это про экономию ресурсов, а не про откат. Это различие стоит держать в голове, когда настраиваешь тайм-ауты.
Четвёртое — асинхронный ответ. Документация Microsoft по async request-reply описывает схему, где клиент получает не результат сразу, а идентификатор и потом опрашивает статус. Для предохранителя это подарок: операция создания отделена от операции чтения статуса, и повтор чтения безопасен. Но это работает только если зависимость реально поддерживает такой паттерн. Приписывать его системе, которая отвечает синхронно, нельзя.
Как проверить, что предохранитель не врёт
Проверка начинается не с нагрузки, а с вопроса: что произойдёт, если зависимость ответит тайм-аутом после выполнения. Это тест-сценарий, который нужно прогнать на стенде с подставным сервисом. Подставной сервис умеет: отвечать успешно, отвечать 500, отвечать тайм-аутом после записи, отвечать частично. Прогон по каждому сценарию показывает, не появляются ли дубли и не теряются ли операции.
| Сценарий подставного сервиса | Ожидаемое поведение агента | Что проверяем |
|---|---|---|
| Успех | Операция завершена, ключ закрыт | Нет лишних повторов |
| 500 до записи | Повтор по политике | Счётчик ошибок растёт |
| Тайм-аут после записи | Статус «неизвестно», без повтора | Нет дубля |
| Частичный успех | Предохранитель по маршруту | Здоровые маршруты работают |
| Долгая недоступность | Очередь с ограничением возраста | Старое не уходит автоматом |
Второй тест — на восстановление. После открытия предохранителя подставной сервис начинает отвечать успешно. Проверяем, что проба идёт только идемпотентным запросом и что после успешной пробы состояние меняется на закрытое, а не остаётся полуоткрытым навсегда. Третий тест — ручная блокировка: ставим её, ждём, пока автомат попытается открыться, и убеждаемся, что ручное решение не перебито.
Отдельно стоит проверить журнал. По каждой операции с неизвестным результатом в журнале должна быть запись с ключом, временем и владельцем. Если журнал пуст, значит, агент где-то обошёл карточку — и это надо найти до инцидента, а не после.
Практикум: собрать карточку для одной операции
Возьмём условную операцию «отправить коммерческое предложение клиенту через мессенджер». Задача — настроить остановку повторов так, чтобы клиент не получил три одинаковых письма, а менеджер понимал, что происходит.
Шаг первый: определить класс. Отправка сообщения не идемпотентна, если шлюз не поддерживает ключ. Проверяем по документации шлюза — не по нашим ожиданиям. Если ключ поддерживается, фиксируем это в карточке; если нет — политика повтора «запрещён».
Шаг второй: определить границу. Предохранитель на маршрут «мессенджер-канал», а не на весь мессенджер, потому что каналы могут падать по отдельности.
Шаг третий: задать окно и порог. Условно: 5 ошибок за 60 секунд при минимуме 10 запросов. Числа — предмет проверки на стенде, а не догма.
Шаг четвёртый: задать поведение очереди. Максимальный возраст отложенной отправки — условно 15 минут. Старше — ручной разбор с пометкой «устарело».
Шаг пятый: описать интерфейс. Менеджер видит список отложенных и список «неизвестный результат». Кнопка повтора недоступна для второго списка без сверки.
Шаг шестой: прогнать сценарии из таблицы выше на подставном шлюзе. Зафиксировать результат в карточке: дата, кто проверял, что сломалось.
Что делать с уже накопленной очередью
Если сбой уже случился и очередь есть, предохранитель её не разберёт. Нужен отдельный проход: отсортировать операции по возрасту и по классу. Свежие идемпотентные — можно отправить. Свежие неидемпотентные с ключом — отправить с ключом. Старые — на ручной разбор. Операции с неизвестным результатом — на сверку, а не на повтор.
Это скучная ручная работа, и её стоит ограничить по объёму: если в очереди больше условных 50 операций, разбор идёт партиями с фиксацией, чтобы не потерять ни одну. Автоматический «разгребатор» очереди опасен тем, что повторяет ровно ту ошибку, от которой защищались: отправляет всё подряд, как только зависимость ожила.
Где заканчивается предохранитель
Предохранитель не решает проблему доступности зависимости и не заменяет мониторинг. Он лишь ограничивает ущерб от сбоя: не даёт агенту бесконечно повторять, не даёт очереди расти без предела, не даёт пробе менять клиентские данные. Всё остальное — обнаружение причины, починка зависимости, коммуникация с клиентом — остаётся за людьми и процессами.
Поэтому финальный шаг — не «включить предохранитель», а договориться, кто и по какому документу разбирает ручную блокировку и операции с неизвестным результатом. Условия и сроки такой работы стоит сверять с применимыми внутренними регламентами и документацией зависимости у уполномоченного сотрудника, а не выводить из общих соображений. Если в компании есть политика обработки инцидентов, карточка зависимости должна на неё ссылаться.
Один шаг проверки
Смежный вопрос разобран отдельно: Ошибка 429 у ИИ-агента: ожидание, пределы и полезный отказ.
Источники и границы разбора
Источники и статус проверки на 9 ноября 2026. Первичные документы поясняют указанные механизмы; доступность и границы каждого источника отмечены ниже. Методические рекомендации и условные примеры составляют самостоятельную часть разбора.
- Сопровождение ИИ-агентов
Опубликованное направление работы 404ai. Предлагаемая в статье методика не означает, что каждое описанное действие уже доступно без настройки; состав решения согласуется отдельно.
- Microsoft: Circuit Breaker pattern
Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.
- Microsoft: Asynchronous request-reply pattern
Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.
- MDN: AbortController
Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.