Назад в блог

Идемпотентность инструментов AI-агентов: повторный вызов не должен дублировать платёж

Агент ретраит по построению: таймаут → повтор → двойное списание. Как проектировать safe retry: детерминированные Idempotency-Key, check before act, гейты для необратимого и чеклист инструментов.

Иллюстрация к материалу: Идемпотентность инструментов AI-агентов: повторный вызов не должен дублировать платёж

Агент вызывает payments.charge. Сеть рвётся, ответ не приходит. Агент не знает, прошло ли списание, — он знает только, что ответа нет. Единственный разумный ход — повторить. Платёж проходит дважды. В журнале две одинаковые записи, в банке два списания, в поддержке злой клиент. Это не баг модели и не «агент ошибся»: это отсутствие идемпотентности в инструменте. Обычный сервис ретраит редко и под контролем программиста. Агент ретраит постоянно — таймауты, обрывы контекста, падения на середине цепочки, перезапуски workflow. Если повторное выполнение небезопасно, дубли — вопрос времени, а не вероятности. AWS в своём Agentic AI Lens относит идемпотентное исполнение задач к практикам с высоким уровнем риска при отсутствии: retry — самый частый механизм восстановления, и без идемпотентности он производит дубли побочных эффектов.

Почему агенты ретраят чаще людей

Человек, не дождавшись ответа, сначала проверяет: «а деньги ушли?». Агент действует по циклу «вызов → наблюдение»: нет наблюдения — нет факта, а значит, надо вызывать снова. Добавьте специфику агентных систем: контекст переполняется и выполнение перезапускается с нуля без памяти о сделанном; оркестратор делегирует подзадачи, которые падают и повторяются; workflow resume после сбоя переигрывает шаги; loop-агент крутит вызовы до успеха. Каждый из этих механизмов умножает число повторов на порядки относительно обычных интеграций. Библиотеки надёжности для агентов — latch, agentguard, safeagent — выросли именно из этого наблюдения: между решением агента и необратимым действием нужен управляющий слой, потому что фреймворки обрабатывают ретраи на транспортном уровне и не знают, случился ли побочный эффект.

Анатомия дубля: таймаут без ответа

Классический сценарий, который описывает документация latch, разбирается на три такта:

  1. Агент вызывает инструмент. Вызов уходит, действие выполняется — но ответ теряется по дороге (таймаут, обрыв, падение воркера).
  2. Агент видит только отсутствие ответа. Различить «не выполнилось» и «выполнилось, но ответ потерян» он не может в принципе — это фундаментальная неопределённость распределённых систем.
  3. Агент повторяет вызов. Неидемпотентный инструмент выполняет побочный эффект второй раз: второй платёж, второе письмо, вторая запись.

Обратите внимание: ни на одном такте никто не «ошибся». Агент действовал разумно, инструмент — как написан, сеть — как сеть. Виновата архитектура: повтор не был спроектирован безопасным.

Детерминированные ключи идемпотентности

Базовый механизм — ключ идемпотентности, который делает повтор узнаваемым. Схема по AWS AGENTREL06-BP04:

  • Ключ детерминирован. Он выводится из входов операции детерминированным хешированием, так что повтор той же логической операции даёт тот же ключ. Случайный UUID на каждый вызов — антипаттерн: он делает каждый повтор «новой» операцией и обнуляет гарантию.
  • Проверка перед исполнением. До побочного эффекта система ищет в хранилище результат по этому ключу. Нашла успешный — возвращает кешированный ответ, не исполняя. Не нашла — исполняет и записывает результат. Для безопасности при конкуренции — conditional writes (например, DynamoDB с условной записью) и TTL-экспирация записей.
  • Ключи текут по цепочке. Оркестратор, делегируя подзадачу, передаёт ключ родительского workflow или его детерминированную производную, чтобы идемпотентность держалась на каждом шаге. Внешним системам со встроенной идемпотентностью ключ пробрасывается дальше.
  • 5xx не кешируется. Ответ с ошибкой сервера ключ освобождает: повтор должен переисполниться, а не вернуть ошибку из кэша. Кешируется только успех — иначе одна транзиентная ошибка становится вечной.

Хранилище квитанций обязано переживать рестарты: durable receipts в SQLite или внешней базе, а не память процесса. Иначе перезапуск воркера стирает память о выполненном — и первый же повтор после рестарта дублирует.

Check before act и естественная идемпотентность

Не всё требует ключей. Первый приём из каталога AgentPatterns — «проверь, прежде чем действовать»: перед созданием проверь, не существует ли уже; перед постом — нет ли эквивалента; перед переходом статуса — не стоит ли он уже. Цена — одна read-операция, альтернатива — дубль состояния. Агент, проверяющий текущую метку перед сменой, не плодит шум в треде вторым комментарием.

Ряд операций идемпотентен по природе: коммит идентичного содержимого даёт тот же SHA в git, повторный push без новых коммитов — no-op, upsert в хранилище с ключом — та же запись. Проектируйте инструменты так, чтобы использовать эту естественность: «создать или вернуть существующее» вместо «создать», upsert вместо insert, декларативное «привести к состоянию» вместо императивного «сделать шаг».

Чекпоинты сужают окно повтора: переисполняется только сегмент после последнего чекпоинта. Но у них честное ограничение — они покрывают правки через файловые инструменты; изменения через shell (rm, mv, миграции) откату не подлежат. Чекпоинты уменьшают поверхность, но не заменяют идемпотентность артефактов: большинство побочных эффектов агента происходит через tool и shell-вызовы, а не через редактирование файлов.

Что нельзя сделать идемпотентным: гейты

Честная часть методологии: часть операций принципиально неидемпотентна. Внешние API, создающие ресурсы без поддержки ключей, отправка писем и SMS через чужой шлюз, деплой с эффектами вне git-состояния, уведомления во внешние системы — повтор здесь всегда риск. Для них два механизма вместо идемпотентности:

  1. Журнал с уникальным ключом. Перед исполнением пишется запись «собираюсь выполнить X с ключом K», после — результат. Перед каждым исполнением журнал проверяется. Журнал и есть идемпотентность, вынесенная наружу.
  2. Гейты исполнения. SafeAgent описывает это как finality gate между решением агента и необратимым действием: исполнять только подтверждённые исходы, дедуплицировать по request ID, хранить квитанцию, блокировать исполнение при неоднозначных сигналах. Плюс пороги уверенности и явный запрет исполнения из промежуточных состояний.

Неидемпотентное без гейта — это платёж по кнопке «попробовать ещё раз».

Семантика повторов: at-least-once vs at-most-once

AWS Durable Execution SDK фиксирует выбор, который обязан сделать каждый проектировщик шагов workflow:

  • At-least-once per retry (дефолт). Шаг переисполняется при любом прерывании — безопасно только для идемпотентных операций: чтения без мутаций, upsert-записи, вызовы с ключом идемпотентности.
  • At-most-once per retry. SDK ждёт подтверждения записи старта; при незавершённой попытке на реплее поднимает ошибку вместо переисполнения. Для операций с внешними побочными эффектами: списание карты, one-shot SMS, POST в неидемпотентный API.

Ни одна семантика не даёт exactly-once на весь workflow сама по себе: стратегия ретраев при ошибке всё равно запустит шаг снова. Чтобы ограничить шаг одной попыткой end-to-end, at-most-once комбинируют с политикой без ретраев. А практика проста: сопоставляйте семантику с побочным эффектом до первого запуска, а не после первого дубля. Ретраимая идемпотентная запись — at-least-once; списание — at-most-once без ретраев, с гейтом и квитанцией.

Многошаговые workflow и саги

В цепочках из шагов идемпотентность обязана течь, а не жить в отдельных шагах. Правила связки: родительский ключ или его производные — в каждый шаг; каждый шаг проверяет квитанцию до исполнения; частичный провал откатывается компенсирующими действиями (сага), а не «надеждой, что повтор всё починит»; внешние системы получают ключ агента, если умеют. Классическая ошибка — идемпотентный шаг 1 и неидемпотентный шаг 3: повтор workflow после падения на шаге 3 безопасно переиграет первые шаги, но продублирует третий. Аудит workflow обязан показывать идемпотентный статус каждого шага отдельно — среднее по больнице здесь не работает.

Сюда же относится защита от смежных бед, с которыми safe retry идёт в комплекте: circuit breaker против retry-штормов в лежащую зависимость (после порога последовательных ошибок вызовы отклоняются сразу, без дёргания упавшего), жёсткие таймауты на вызов, чтобы один зависший инструмент не вешал цикл, и детектор зацикливаний, останавливающий бесконечные повторы до того, как счёт вырастет.

Чеклист проектирования инструментов для агентов

  • Каждый инструмент с побочным эффектом принимает детерминированный Idempotency-Key; случайные ключи запрещены контрактом.
  • Проверка квитанции — до исполнения; успех кешируется, 5xx освобождает ключ; хранилище durable с TTL.
  • Ключ пробрасывается через делегирование и саб-агентов; внешние системы получают его при поддержке.
  • Где возможно — check before act и естественная идемпотентность (get-or-create, upsert, декларативные состояния).
  • Неидемпотентное выделено в отдельный список: журнал + finality gate + approval, без исключений.
  • Семантика шагов задана явно: at-least-once для идемпотентного, at-most-once без ретраев для списаний и отправок.
  • Circuit breaker, таймауты и детектор циклов настроены на каждом внешнем вызове.
  • Dry-run режим показывает, что будет сделано, без побочных эффектов.
  • Журнал квитанций читается по операции: ключ, входы, результат, число дедуплицированных повторов.

Где Codenik

Codenik выносит safe retry из головы агента на слой доступа — и делает его принудительным, а не рекомендательным. Каждый вызов инструмента идёт с ключом: повтор с тем же ключом возвращает квитанцию, а не переисполняет. Неидемпотентные операции живут за гейтом: dry-run для предпросмотра, подтверждение человеком для исполнения, журнал квитанций для разбора. Circuit breaker и лимиты режут retry-штормы до того, как они превратятся в счёт от провайдера. Агент может ретраить сколько угодно — архитектура гарантирует, что «ещё раз» означает «верни квитанцию», а не «спиши ещё раз». Надёжность перестаёт зависеть от аккуратности промпта и становится свойством контура.

Короткий вывод

Агенты ретраят по построению — значит, безопасный повтор должен быть свойством инструментов, а не надеждой. Детерминированные ключи, проверка до исполнения, честный список неидемпотентного за гейтами, явная семантика шагов и квитанции, переживающие рестарты. Спроектируйте это до первого продакшен-вызова: первый дубль платежа обходится дороже, чем вся инженерия идемпотентности вместе взятая.

Источники и дальнейшее чтение