Пользователь нажимает «Отменить», спиннер гаснет, а через сорок минут во внешней системе появляется запись, которую никто не просил создавать. Прекращение ожидания на экране не доказывает, что побочное действие отменено или хотя бы остановлено.
Отмена поручения ИИ-агента — это не одно событие, а фиксация трёх разных вещей: прекращения ожидания, прекращения планирования следующих шагов и отдельной судьбы уже совершённых действий. Прекращение HTTP-ожидания не откатывает запрос во внешнем сервисе. Поэтому карточка поручения должна различать состояния «останавливаем», «остановлено» и «исход неизвестен», а компенсация выполненных шагов требует отдельного подтверждения.
Пользователь нажимает «Отменить» на карточке поручения, интерфейс мгновенно гасит спиннер, а через сорок минут в CRM появляется новый контакт, которого никто не просил создавать. Это условная сцена, но она точно описывает разрыв, с которым сталкиваются команды, отдавшие ИИ-агенту длинную цепочку действий. Кнопка отмены управляет ожиданием на экране, а не миром по ту сторону интерфейса.
Механизм здесь простой и неприятный: любое поручение агенту распадается на последовательность шагов, и часть шагов уже необратима к моменту, когда человек передумал. Отмена — это не одно событие, а переговорный процесс между тем, кто просит остановиться, и тем, кто уже что-то сделал. Ниже — как устроить этот процесс так, чтобы «отменено» на экране означало что-то проверяемое, а не просто погасшую анимацию.

Почему «Отменить» на экране не отменяет запрос в чужом сервисе
Классическая ошибка проектирования — считать, что отмена задачи и отмена HTTP-запроса это одно и то же. Когда браузер или серверный клиент обрывает соединение через AbortController, он перестаёт ждать ответ. Согласно документации MDN по AbortController, сигнал отмены лишь сообщает запросу, что результат больше не нужен; он не гарантирует, что удалённая сторона прекратила обработку. Сервер мог уже принять данные, записать строку в базу или поставить сообщение в очередь.
Для ИИ-агента это критично, потому что его шаги часто идут во внешние системы: CRM, календарь, почтовый шлюз, платёжный сервис, мессенджер. Прекращение ожидания ответа от такого сервиса не откатывает побочное действие. Более того, асинхронные сценарии по своей природе предполагают, что клиент отвязывается от результата и забирает его позже — об этом прямо говорит шаблон Asynchronous request-reply в документации Microsoft. Если агент построен на такой схеме, «отмена» на клиенте вообще не адресована исполнителю.
Отсюда первое правило: интерфейс отмены должен различать три разных вещи — прекращение ожидания, прекращение планирования следующих шагов и откат уже совершённых действий. Смешивать их в одной кнопке значит обещать пользователю то, чего система не умеет.
Если ваша задача — проверить отмену поручения, на странице соответствующего направления 404ai можно посмотреть опубликованные возможности и оставить описание текущего процесса. Конкретные подключения, исходные данные и критерии приёмки согласуем отдельно.
Карточка поручения как рабочий объект, а не лог чата
Чтобы отмена была проверяемой, поручение нужно сделать объектом с состоянием, а не потоком сообщений. У такого объекта есть идентификатор, список запланированных шагов, отметки о том, какие шаги начаты, какие завершены, какие завершились неизвестным исходом. Именно последняя категория — «неизвестно» — обычно и отсутствует в самодельных реализациях, хотя она самая частая.
Форма рабочего объекта, который стоит завести, выглядит примерно так (все поля условные, это иллюстрация структуры, а не готовый продукт):
| Поле | Что хранит | Зачем нужно при отмене |
|---|---|---|
job_id |
Устойчивый идентификатор поручения | Связывает экран, журнал и внешние вызовы |
step_id |
Идентификатор шага внутри поручения | Позволяет отменять не всё, а конкретный шаг |
step_state |
planned / started / done / unknown / compensated |
Отделяет «не начали» от «не знаем, что вышло» |
idempotency_key |
Ключ для повторного вызова | Защищает от дублей при повторном поручении |
external_ref |
Идентификатор объекта в чужой системе | Нужен, чтобы откатить или найти созданное |
cancel_requested_at |
Момент запроса отмены | Отделяет отмену от факта остановки |
cancel_confirmed_at |
Момент подтверждения остановки | Доказывает, что исполнитель реально встал |
Ключевая мысль таблицы: момент запроса отмены и момент подтверждения — разные поля. Пока второе пустое, карточка честно показывает «останавливаем», а не «отменено». Это не педантизм, а разница между отчётом и реальностью.
Точки отмены: где агент реально может остановиться
Агент не останавливается где угодно. Есть узкие места, где остановка безопасна, и есть участки, где она либо невозможна, либо создаёт полуготовое состояние. Полезно заранее разметить шаги по типу точки отмены.
- До старта шага. Шаг ещё в очереди, ничего не отправлено. Отмена здесь дешёвая и полная: шаг просто снимается с плана.
- Между шагами. Предыдущий шаг завершён, следующий не начат. Отмена останавливает цепочку, но уже сделанное остаётся.
- Внутри шага, до внешнего вызова. Агент прочитал данные, готовится писать. Отмена безопасна, если внешний вызов ещё не ушёл.
- Внутри шага, после внешнего вызова. Самый опасный участок. Запрос ушёл, ответа нет или он потерялся. Здесь отмена превращается в вопрос «а что там на самом деле произошло».
- После записи, до подтверждения. Действие совершено, но система ещё не зафиксировала результат. Это зона
unknown.
Шаблон Circuit Breaker в документации Microsoft решает другую задачу — прекратить повторные обращения к сбоящей зависимости. Для отмены он не подходит: размыкание цепи не говорит, что произошло с уже отправленным запросом. Поэтому при потере связи карточка не должна показывать «отменено» — она должна показывать «связь потеряна, исход неизвестен». Применительно к отмене это означает: если исполнитель перестал отвечать, карточка не должна показывать «отменено» — она должна показывать «связь потеряна, исход неизвестен».
Два эпизода: когда отмена сработала и когда сделала хуже
Эпизод первый, условный. Менеджер отдела продаж поручает агенту «подготовить подборку по десяти контактам и разослать приглашения на вебинар». Агент начинает с чтения карточек, формирует список, затем по одному отправляет письма. На третьем письме менеджер понимает, что выбрал не тот сегмент, и жмёт «Отменить». Здесь всё зависит от того, как устроены точки отмены. Если между отправками есть проверка сигнала отмены, агент останавливается после третьего письма, а карточка показывает: два письма ушли, третье в состоянии unknown (ушло или нет — надо проверить), остальные сняты с плана. Менеджер получает список того, что реально произошло, и решает, писать ли извинения двум адресатам. Отмена сработала не потому, что была волшебной, а потому что шаги были разбиты и состояние каждого фиксировалось.
Эпизод второй, тоже условный. Руководитель просит агента «свести заявки за неделю и обновить статусы в учётной системе». Агент читает заявки, затем начинает массовое обновление: сотня записей уходит одним пакетом во внешнюю систему. Руководитель замечает ошибку в фильтре и жмёт «Отменить» в момент, когда пакет уже отправлен, а ответ ещё не пришёл. Клиент обрывает ожидание, экран показывает «отменено». Через десять минут выясняется, что пакет применился целиком: сто записей обновлены по неверному фильтру. Отмена не отменила ничего, потому что массовая операция была одним неделимым шагом, а её исход остался неизвестным интерфейсу. Разгребать пришлось вручную, сверяя журнал внешней системы с тем, что ожидалось.
Разница между эпизодами не в кнопке, а в гранулярности шагов и в том, признаёт ли система состояние unknown. В первом случае отмена дала проверяемый результат, во втором — ложное чувство завершённости.
Исход «неизвестно»: как с ним жить, а не прятать
Состояние unknown — не баг, а честная категория. Оно возникает всякий раз, когда запрос ушёл, а подтверждение не вернулось: таймаут, обрыв сети, перезапуск воркера, ответ потерялся в очереди. Прятать этот случай за словом «отменено» — значит копить сюрпризы.
Работать с unknown помогает идемпотентность. Если у каждого шага есть idempotency_key, повторный вызов с тем же ключом не создаст дубль, а вернёт результат первой операции. Это позволяет безопасно переспросить внешнюю систему: «а было ли это действие?». Без ключа повтор превращается в лотерею с дублями.
Второй инструмент — сверка по external_ref. Если агент записал объект во внешнюю систему и сохранил её идентификатор, при разборе unknown можно пойти и посмотреть, существует ли объект. Это не отменяет действие, но превращает неизвестность в факт, с которым можно работать.
Третий — явный список компенсаций. Для каждого типа записи стоит заранее решить, чем является её откат: удалением, пометкой «отменено», обратной транзакцией или ручным разбором. Компенсация — это отдельное действие, которое само может не удаться, и его тоже надо отслеживать.
Компенсация требует согласования, а не молчаливого отката
Соблазн сделать «отмену» умной и автоматически откатывать всё, что успели, понятен, но опасен. Откат записи в CRM может стереть данные, которые кто-то уже использовал. Обратное письмо может уйти клиенту. Удаление созданной сущности может нарушить связи, на которые ссылаются другие объекты.
Поэтому компенсацию разумно выносить в отдельное решение с подтверждением. Карточка показывает: «Отменено частично. Создано 3 объекта. Откатить?». Дальше — либо автоматический откат по заранее описанному правилу, либо ручное действие уполномоченного сотрудника. Какое именно правило применимо к вашим данным и какие ограничения действуют, определяется по внутренним регламентам и применимым документам у ответственного сотрудника, а не по общим советам из статьи.
Отдельно стоит вопрос повторного поручения. После отмены пользователь часто хочет «сделать то же, но правильно». Если просто нажать «Повторить», агент может наложить новое действие поверх старого, включая те шаги, что остались в unknown. Поэтому повтор должен опираться на карточку: показать, что уже сделано, что неизвестно, и предложить продолжить с конкретного шага, а не с начала.
Отмена поручения агента на экране сотрудника
Экран отмены решает одну задачу — не дать человеку обмануться. Хороший интерфейс здесь скучный и конкретный.
- Статус с тремя различиями. «Останавливаем», «остановлено», «исход неизвестен» — три разных состояния, а не одно «отменено».
- Список шагов с отметками. Что запланировано, что выполнено, что в процессе, что неизвестно. Без этого человек не поймёт, что разгребать.
- Кнопка отмены с честной подписью. Если отмена не мгновенная, так и написано: «Остановить после текущего шага».
- Отдельное действие для компенсации. Не слито с отменой, требует подтверждения и показывает, что именно будет откачено.
- Ссылка на журнал. Чтобы при разборе
unknownможно было посмотреть исходные запросы и ответы.
Чего не должно быть: анимации, которая гаснет раньше, чем пришло подтверждение; слова «готово» рядом с незавершённым шагом; автоматического закрытия карточки после нажатия отмены. Карточка живёт, пока есть шаги в состоянии started или unknown.
Журнал и след: чем доказывать, что отмена произошла
Спор «я же нажал отмену» решается журналом. В нём фиксируются: момент запроса отмены, момент, когда исполнитель увидел сигнал, момент подтверждения остановки, а также все внешние вызовы с их идентификаторами и ответами. Журнал должен быть доступен не только инженеру, но и тому, кто разбирает последствия.
Полезно хранить не только факт вызова, но и его намерение: какой шаг, с каким ключом идемпотентности, к какой системе. Тогда при разборе unknown можно восстановить картину, не гадая. Это же помогает при повторном поручении — видно, какие ключи уже использованы.
Журнал не должен быть свалкой сырых логов. Достаточно структурированных записей по шагам с понятными статусами. Сырые тела запросов можно хранить отдельно и с ограниченным доступом, если в них есть персональные данные; порядок обращения с такими данными определяется по применимым документам у ответственного сотрудника.
Тесты отмены: проверять не кнопку, а последствия
Тестировать отмену через «нажал и посмотрел, что спиннер погас» бессмысленно. Проверять нужно то, что происходит по ту сторону.
| Сценарий | Что проверяем | Ожидаемый результат |
|---|---|---|
| Отмена до старта | Шаг не ушёл во внешнюю систему | Нет внешних вызовов, состояние cancelled |
| Отмена между шагами | Цепочка остановилась, сделанное сохранено | Выполненные шаги в done, следующие сняты |
| Отмена во время чтения | Ожидание прервано, чтение не пишет | Нет записи, состояние шага cancelled |
| Отмена после записи | Запись состоялась, откат не автоматический | Состояние done, компенсация предложена отдельно |
| Потеря ответа | Исход неизвестен | Состояние unknown, есть external_ref для сверки |
| Повторное поручение | Нет дублей | Повтор с тем же ключом не создаёт второй объект |
| Компенсация без согласования | Откат не выполняется молча | Требуется подтверждение уполномоченного сотрудника |
Хороший тест имитирует обрыв на каждом участке: до вызова, во время вызова, после вызова, до получения ответа. Если система во всех случаях честно показывает состояние и не врёт про отмену, она готова к реальной работе. Если хотя бы в одном случае экран говорит «отменено», а внешняя система показывает обратное — это дефект, а не мелочь.
Практикум: разбор одного поручения на пять шагов
Возьмём условное поручение: «Найди в базе контакты, подходящие под критерий, создай по ним задачи в трекере и отправь каждому короткое уведомление в мессенджере». Пять шагов: поиск, фильтрация, создание задач, отправка уведомлений, отчёт. Пройдём по нему как по упражнению.
Сначала разметьте шаги по точкам отмены. Поиск и фильтрация — чтение, их отмена безопасна и полна. Создание задач — запись, после неё нужен external_ref и решение о компенсации. Отправка уведомлений — необратимое внешнее действие, откат невозможен, возможна только фиксация факта. Отчёт — чтение, безопасно.
Затем решите, что происходит при отмене на каждом участке. На чтении — стоп без последствий. На записи — стоп с пометкой «создано N задач, откатить?». На уведомлениях — стоп с честным «отправлено M, отменить нельзя». На отчёте — просто стоп.
Потом проверьте повтор. Если пользователь после отмены захочет «сделать то же, но с другим фильтром», система должна предложить продолжить с шага фильтрации, а не создавать задачи заново поверх уже созданных. Для этого нужны ключи идемпотентности, привязанные к шагу и к набору данных.
И наконец, прогоните тесты с обрывом на каждом шаге. Если после обрыва на создании задач карточка честно показывает «создано 4, исход по 5-й неизвестен», а не «отменено», упражнение выполнено. Дальше можно усложнять: добавлять компенсации, согласование, разные роли.
Что остаётся за рамками и почему это нормально
Не всякую отмену можно довести до полного отката, и это не повод отказываться от честного интерфейса. Отправленное письмо не вернуть, созданную сделку можно только пометить, а иногда и вовсе нельзя. Задача системы — не притворяться всемогущей, а точно показать, где остановка возможна, где она частична, а где уже поздно.
Из этого следует скромный, но полезный вывод: проектируйте поручение так, чтобы необратимые шаги шли последними и по возможности по одному. Тогда отмена на ранних этапах будет полной, а на поздних — хотя бы предсказуемой. Массовые неделимые операции стоит дробить, даже если это чуть медленнее: цена дробления ниже цены ста неверно обновлённых записей.
Проверка на практике: отмена поручения агента
Если вы уже отдали агенту длинные цепочки, начните с одного поручения, где есть и чтение, и запись, и внешнее уведомление. Прогоните его с намеренным обрывом на каждом шаге и посмотрите, что показывает карточка в момент, когда ответ не пришёл. Если она говорит «отменено», а внешняя система показывает обратное, у вас есть конкретный дефект, который стоит починить до того, как он случится с реальным клиентом. Остальное — гранулярность шагов, ключи идемпотентности, согласование компенсаций — достраивается вокруг этого первого честного состояния.
Смежный вопрос разобран отдельно: Управление задачами с ИИ: очередь, сроки и приёмка.
Источники и границы разбора
Источники и статус проверки на 9 ноября 2026. Первичные документы поясняют указанные механизмы; доступность и границы каждого источника отмечены ниже. Методические рекомендации и условные примеры составляют самостоятельную часть разбора.
- Внедрение ИИ
Опубликованное направление работы 404ai. Предлагаемая в статье методика не означает, что каждое описанное действие уже доступно без настройки; состав решения согласуется отдельно.
- MDN: AbortController
Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.
- Microsoft: Asynchronous request-reply pattern
Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.
- Microsoft: Circuit Breaker pattern
Первичная документация указанного инструмента или механизма. Практические рекомендации и условные эпизоды статьи — самостоятельная методика, а не описание готовых функций 404ai.