Idempotency For Agents

Идемпотентность отвечает на один вопрос: если система по ошибке повторит этот вызов, что произойдёт? Для операций записи допустимых ответов два — либо повтор не меняет результат, либо система надёжно распознаёт дубль и не создаёт побочный эффект заново.

Суть

Без этого свойства любая нестабильность сети, таймаут или гонка между запусками начинает стоить денег или инцидентов. Причём в агентной системе поводов для повтора больше, чем в обычном сервисе: повтор бывает не только сетевым, но и логическим — модель решила вызвать инструмент второй раз, потому что не увидела результата первого.

Ключ идемпотентности — часть протокола, а не необязательная договорённость. Если он опционален, его рано или поздно забудут именно в том вызове, где он был нужен.

Граница ответственности: инструмент, а не оркестратор

Важное различие, которое постоянно путают. Оркестратор может быть сколь угодно атомарен относительно своего состояния — например, супершаг графа не закрывает снапшот при сбое узла (Pregel Model). Но вызов, который узел успел сделать наружу, уже произошёл: письмо отправлено, платёж проведён, запись создана. Никакой механизм графа этого не вернёт.

Отсюда правило: ключ идемпотентности лежит на инструменте, а не на оркестраторе. Повтор узла после паузы или сбоя начинается с первой строки функции, и защитить внешний мир может только сам инструмент (LangGraph Reliability).

Повтор без классификации ошибок множит хаос

Стратегия «не получилось — повторим ещё три раза» опасна, потому что исходы не равнозначны:

Класс исхода Что делать
validation_failure Повторять почти никогда не нужно — вход не станет валиднее
permission_denied Количеством повторов не чинится
retryable_failure Как раз здесь уместен повтор с экспоненциальной задержкой
side_effect_unknown Осторожность, а не слепой повтор

Политика повторов зависит от класса исхода, а не от надежды, что в этот раз получится.

Самый неприятный статус — side_effect_unknown

Категория, в которой неизвестно, произошёл побочный эффект или нет: таймаут после отправки запроса, обрыв соединения после фиксации транзакции, падение адаптера до сохранения подтверждения, ответ внешнего API, из которого итоговое состояние неясно.

Наивный повтор здесь особенно опасен. Правильные ветки другие:

  • проверить текущее состояние во внешней системе;
  • выполнить сверку по корреляционному идентификатору;
  • запросить человека (Approval Path);
  • остановить процесс и зафиксировать неопределённость.

На примере агента поддержки: самый опасный момент — не ошибка в рассуждении, а таймаут после вызова создания тикета. Без ключа идемпотентности повторный вызов превращает одно обращение клиента в два инцидента. Правильная ветка сначала ищет тикет по корреляционному идентификатору и либо привязывает найденный результат к трассе, либо останавливает запуск.

Выполнение и доставка — два разных факта

Для сообщения или webhook недостаточно статуса done. Вход может быть durably accepted и пережить рестарт до исполнения; исходящее действие может выполниться, но подтверждение доставки потеряться. Поэтому трасса хранит отдельно accepted, executed и delivered, а неопределённая доставка остаётся delivery_unknown, не превращаясь автоматически в retry.

Blind retry такого статуса создаёт дубли ровно так же, как повтор платежа. Сначала нужна сверка по correlation/idempotency key; если внешний канал не умеет её дать, система сохраняет неопределённость и эскалирует, а не делает вид, что exactly-once достижимо.

Ветка восстановления проектируется, а не импровизируется

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

Лимиты частоты — тоже часть безопасности

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

Поэтому лимит задаётся не только на сервис целиком, но и на инструмент (Agent CostControl).

Связано с

  • Tool Gateway — где проверяется ключ и класс риска
  • Rollback Boundary — что делать, когда повтор невозможен
  • Agent Failure Modes — каталог отказов, включая тихие
  • Approval Path — эскалация при неизвестном исходе
  • LangGraph Reliability — идемпотентность узлов в конкретном фреймворке
  • Pregel Model — почему атомарность графа не защищает внешний мир
  • AI Integration Patterns — идемпотентность инференса на границе систем
  • EDA For AI — дедупликация при доставке «хотя бы один раз»