> **Коротко.** Самый опасный сбой ИИ-агента происходит не тогда, когда действие не выполнилось, а
> тогда, когда оно, возможно, выполнилось, но подтверждение потерялось. Счёт мог уже появиться в 1С,
> письмо — уйти клиенту, цена — измениться в кабинете, хотя агент видит только таймаут. Слепой повтор
> в такой ситуации превращает восстановление в дубль. Поэтому надёжный агент должен уметь четыре
> разные вещи: сохранить своё место в задаче, обнаружить остановившийся процесс, не выполнить одну и
> ту же запись повторно и честно остановиться, когда результат нельзя доказать. Разбираем, как это
> устроено в «Сам Решу» и почему кнопка «повторить» сама по себе ничего не чинит.

Вы попросили агента выставить счёт клиенту. 1С приняла запрос, создала документ и начала отвечать.
В этот момент пропала сеть.

Для 1С работа закончена: счёт существует. Для агента — нет: ответа он не получил. Если повторить
команду, можно создать второй счёт. Если не повторять, задача останется незавершённой. А если просто
написать пользователю «ошибка», человек пойдёт выставлять счёт руками и тоже рискует сделать дубль.

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

Чат может позволить себе закончиться сообщением «попробуйте ещё раз». Сотрудник, которому отдали
участок работы, — нет. Он обязан сначала ответить на другой вопрос: **что уже произошло?**

## У ошибки не два исхода, а три

Большинство интерфейсов рисует два состояния: получилось или не получилось. Для изменяющего
действия этого недостаточно.

| Что видит агент | Что могло произойти в системе | Можно ли повторять |
|---|---|---|
| Сервис отказал до выполнения | Ничего не изменилось | Да, если причина временная |
| Сервис подтвердил успех | Изменение произошло | Нет необходимости |
| Ответ потерялся | Изменение могло произойти, а могло и нет | Только после доказательства |

Третье состояние — самое дорогое. У него нет честного русского слова в обычном интерфейсе, поэтому
его часто маскируют словом «ошибка». Но ошибка связи и отказ операции — разные события. Таймаут
сообщает только, что ответ не пришёл вовремя. Он не сообщает, успел ли сервер провести платёж,
создать заказ или отправить письмо.

В инженерных системах это называют **неизвестной судьбой запроса**. В работе бизнеса перевод проще:
**не делай второй раз, пока не проверил первый**.

## Почему обычный retry здесь опасен

Повтор хорошо лечит чтение. Не загрузился список заказов — запросили ещё раз. Не открылся отчёт —
подождали и перечитали. Внешнее состояние от этого не меняется.

С записью иначе:

- повторное создание счёта даёт два документа;
- повторная отправка письма даёт клиенту два одинаковых требования;
- повторная постановка платежа может списать деньги дважды;
- повторное изменение цены может применить одно и то же смещение ещё раз;
- повторный клик «Отправить» в браузере может отправить уже заполненную форму второй раз.

AWS формулирует задачу почти тем же примером: повтор полезен, пока один запрос должен привести к
одному результату, даже если по сети его пришлось послать несколько раз. Stripe поэтому принимает
ключ идемпотентности для создающих запросов: повтор с тем же ключом возвращает результат первой
операции вместо создания второй.

Но ключ решает только одну часть задачи. Агент всё ещё должен помнить, **какое поручение** он
выполнял, **до какого места** дошёл и **почему** решил повторять именно этот вызов. Поэтому
надёжность собирается не одной настройкой, а четырьмя слоями.

## Первый слой: задача помнит, где остановилась

Если агент держит весь ход работы только в памяти процесса, исчезновение процесса стирает и ход.
Новый worker получает исходную постановку и начинает сначала: снова читает документы, снова строит
таблицу, снова доходит до изменяющего действия.

В «Сам Решу» долговременная задача хранит контрольные точки. После рабочего шага сохраняются
состояние агента, найденные факты, созданные файлы и текущая итерация. Исполнение и состояние живут
отдельно: процесс можно потерять, не потеряв уже сделанную работу.

Это похоже не на сохранение текста ответа, а на закладку в рабочем деле. В ней лежит не только
страница, на которой остановились, но и то, какие источники уже прочитаны, какие инструменты уже
вызывались и что они вернули.

Temporal описывает ту же идею как durable execution: поток работы переживает падение worker и
восстанавливается на другом процессе из сохранённого состояния. Важна здесь не конкретная
технология, а разделение двух вещей, которые в обычном чате склеены: **задача живёт дольше процесса,
который исполняет её прямо сейчас**.

## Второй слой: кто-то должен заметить остановку

Checkpoint бесполезен, если никто не понял, что работа остановилась.

У каждого запущенного задания есть heartbeat — короткая отметка «я ещё работаю». Отдельный процесс
смотрит не на красивый статус в интерфейсе, а на эту отметку и на владельца исполнения. Если worker
исчез или перестал отзываться, выполнение возвращается в очередь и продолжается с последней
контрольной точки.

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

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

## Третий слой: повторный вызов не должен стать повторным действием

Контрольная точка отвечает на вопрос «где мы были». Идемпотентность — на другой: «делали ли мы уже
ровно это».

Перед изменяющим вызовом «Сам Решу» строит его подпись из участка задачи, имени инструмента и
аргументов. Для одинакового действия внутри одного участка существует один durable-слот.

Дальше возможны три исхода:

1. Вызов новый — агент занимает слот и выполняет его.
2. Такой вызов уже завершился — агент получает сохранённый результат и не запускает действие снова.
3. Такой вызов ещё выполняется — второй исполнитель не входит параллельно.

Это защищает не только от сетевого повтора. Модель сама может второй раз сформулировать тот же вызов
после восстановления контекста. Для внешней системы причина не имеет значения: два одинаковых
вызова всё равно были бы двумя действиями.

Но локальная защита не умеет переписать законы чужого API. Если внешний сервис принял запрос, а
наш процесс исчез до того, как сохранил результат, одного внутреннего слота недостаточно. Нужна
вторая половина договора — ключ идемпотентности у самого сервиса или возможность однозначно найти
созданный объект по бизнес-ключу.

Поэтому запрос с неизвестной судьбой допускается повторить только тогда, когда безопасность можно
доказать:

- метод сам не меняет состояние;
- внешний API принимает ключ идемпотентности;
- либо состояние можно сначала проверить отдельным чтением и убедиться, что действия ещё не было.

На транспортном уровне «Сам Решу» автоматически повторяет потерянный запрос только в первых двух
случаях. В третьем повтор — уже новое решение агента после чтения системы. Если доказательства нет,
агент не угадывает: оставляет исход неизвестным и зовёт человека.

## Четвёртый слой: журнал отвечает не «что думал», а «что сделал»

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

Поэтому изменяющее действие получает отдельную строку в журнале. В ней есть:

- какой инструмент вызван;
- к какому участку и кабинету относилось действие;
- требовалось ли подтверждение человека;
- чем закончился вызов;
- какое исполнение и какой вызов его сделали;
- можно ли построить обратное действие.

Это не видеозапись экрана агента и не поток его внутренних рассуждений. Журнал нужен для более
узкого вопроса: **какие изменения агент оставил после себя**.

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

## Разберём тот самый счёт в 1С

Пусть задача звучит так: «каждый будний день найди отгрузки без счёта, подготовь счета в 1С и
покажи мне перед отправкой клиентам».

Нормальный ход выглядит так:

1. Агент читает отгрузки и уже существующие счета.
2. Сохраняет найденные пары «отгрузка — контрагент — сумма».
3. Перед созданием документа фиксирует конкретное намерение.
4. Создаёт счёт.
5. Перечитывает созданный документ и сохраняет его идентификатор.
6. Просит подтверждение перед письмом клиенту.

Теперь сеть пропадает на четвёртом шаге.

Плохой агент видит таймаут и снова вызывает «создать счёт».

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

Следующая граница ещё жёстче. Отправленное письмо нельзя «отправить обратно», поэтому отсутствие
подтверждения после отправки — не повод послать его снова. Сначала проверяется папка отправленных или
статус провайдера. Если проверить нельзя, правильный результат звучит не «ошибка отправки», а
«не удалось доказать, ушло ли письмо; повторно не отправлял».

Такое сообщение выглядит менее уверенно. Зато оно не превращает техническую неопределённость в два
письма клиенту.

## Почему браузер — отдельный случай

В API запрос можно снабдить идентификатором. У клика идентификатора нет.

Два вызова «нажать Enter» могут означать случайный повтор, а могут — отправку двух разных форм.
Одинаковые координаты и клавиши ничего не доказывают о результате. Поэтому «Сам Решу» не применяет
к браузерным жестам ту же дедупликацию, что к вызовам коннекторов.

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

Это важная граница любого агента с компьютером. Собственный браузер и возможность продолжать работу
после сбоя ещё не делают каждое действие повторяемым. Иногда восстановление — это не повтор шага, а
повторное наблюдение.

## «Ровно один раз» — не свойство всей задачи

Фраза exactly once звучит как обещание, что агент никогда ничего не продублирует. В распределённой
работе так говорить нечестно.

Можно обеспечить один вход в локальный участок. Можно сохранить результат вызова. Можно передать
внешнему сервису ключ и получить его обещание не создать второй объект. Можно перечитать состояние
после сомнительного ответа. Но если чужая система не принимает ключ, не отдаёт историю операций и
не имеет уникального бизнес-идентификатора, доказать единственность снаружи невозможно.

У надёжной системы поэтому не одно обещание, а три:

- **повторяем там, где повтор безопасен;**
- **проверяем там, где результат можно прочитать;**
- **останавливаемся там, где судьбу нельзя доказать.**

Последний пункт не слабость агента. Это граница полномочий, которую живой сотрудник тоже должен был
бы соблюдать.

## Что спросить у поставщика ИИ-агента

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

**Что произойдёт, если worker исчезнет через десять минут работы?**  Ответ «задача запустится заново»
хуже ответа «она продолжится с сохранённого шага».

**Как система отличает временный отказ от потерянного ответа?** Если оба случая называются retryable
error, изменяющие запросы однажды продублируются.

**Где хранится результат уже выполненного действия?** История сообщения недостаточна: нужен адрес
конкретного вызова и его результата.

**Что происходит с двумя исполнителями одной задачи?** Очередь сама по себе не спасает от worker,
который очнулся после передачи работы другому.

**Что увидит пользователь, если результат нельзя доказать?** Правильный ответ содержит состояние,
последнее подтверждённое действие и безопасный следующий шаг. «Что-то пошло не так, повторите» — не
ответ для системы, которой разрешена запись.

## Надёжность начинается после первого сбоя

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

Надёжный агент тоже падает. Сеть всё равно моргает, сервисы отвечают 500, процессы перезапускаются,
а браузер закрывается между кликом и подтверждением. Разница видна после этого:

- он помнит, где остановился;
- новый исполнитель не спорит со старым за одну задачу;
- завершённое действие не запускается второй раз;
- неизвестный исход не переименовывается в неуспех;
- человек получает вопрос только там, где у системы закончились доказательства.

Именно здесь чат превращается в сотрудника. Не когда научился нажимать кнопку, а когда научился не
нажимать её второй раз без причины.

---

**Последнее обновление:** 4 сентября 2026.

**Источники:** [n8n — How To Build Reliable Workflows With API Idempotency](https://blog.n8n.io/idempotency-api/);
[Temporal — The thread is the Workflow: Durable AI agents without changing Agent code](https://temporal.io/blog/manetu-the-thread-is-the-workflow);
[Braintrust — One connected system for agent observability](https://www.braintrust.dev/blog/active-observability-loop-patterns-debugger);
[AWS Builders’ Library — Making retries safe with idempotent APIs](https://aws.amazon.com/builders-library/making-retries-safe-with-idempotent-APIs/);
[Stripe — Idempotent requests](https://docs.stripe.com/api/idempotent_requests).
