Что на самом деле гарантирует надёжное выполнение
Движки надёжного выполнения — Restate, Temporal и подобные системы — решают реальную и сложную задачу: они позволяют workflow пережить смерть процесса, сетевые разделения и деплои. Гарантии точны, и их стоит сформулировать прямо.
- Однократное выполнение шага. Завершённый шаг не выполняется повторно при воспроизведении. Его результат читается из надёжного журнала.
- Возобновляемость после сбоя. Воркер может умереть посреди запуска; другой подхватывает журнал и продолжает с последней зафиксированной границы.
- Детерминированное воспроизведение завершённой работы. При том же журнале рантайм реконструирует ту же последовательность побочных эффектов.
Это свойства рантайма. Они ничего не говорят о рассуждениях агента. Workflow, который чисто возобновляется, доказал лишь, что рантайм способен реконструировать граф вызовов — а не то, что следующее действие всё ещё желанно, всё ещё разрешено или всё ещё согласуется с целью, запустившей выполнение.
Путаница возникает легко, потому что терминология пересекается. «Надёжный» звучит как «заслуживающий доверия». «Однократный» звучит как «корректный». В обычном workflow — списать с карты, отправить письмо, обновить строку — интент фиксируется на этапе написания и закодирован в самом графе. Граф и есть интент. В запуске агента граф порождается во время выполнения из рассуждений модели над целью. Журнал фиксирует, что агент сделал, а не почему ему было позволено это сделать.
Разрыв интента в момент возобновления
Рассмотрим, что на самом деле содержит запуск в момент старта. Есть цель («урегулировать запрос этого клиента на возврат»), план (последовательность вызовов инструментов, предложенная агентом), набор принятых ограничений (потолок возврата, разрешённые инструменты, данные, которые агент может читать) и бюджет (токены, астрономическое время, число внешних вызовов).
Чекпоинт обычно сериализует переменные: текущий индекс шага, выводы инструментов, содержимое блокнота, возможно, объект плана. Он не сериализует полномочия, в рамках которых эти переменные были получены. Потолок возврата был предоставлен политикой, которая могла измениться. Область инструментов была одобрена для сессии, которая могла истечь. Инструкция пользователя — «вернуть до суммы в счёте» — могла быть заменена более поздним сообщением, которого возобновлённый запуск не видит.
Между сбоем и возобновлением мир движется. Цены меняются. Запасы перераспределяются. Разрешения отзываются. Регулятор публикует руководство. Пользователь передумывает. Ни одно из этих событий не попадает в журнал, потому что журнал знает только то, что агент наблюдал до остановки.
Это порождает специфический и неприятный режим отказа: возобновлённый запуск, который успешно завершается, делая то, что исходная траектория никогда не санкционировала. Завершение выглядит правдоподобно. Логи чистые. Последовательность шагов воспроизводится детерминированно. И действие неверно, потому что интент, оправдывавший его, устарел.

Это баг корректности, а не баг доступности. Он не проявляется в тестах на восстановление после сбоя. Он проявляется в продакшене, недели спустя, когда кто-то спрашивает, почему агент выдал возврат, который политика уже запретила.
Фиксация контекста решения
Решение — сохранять контекст решения рядом с состоянием. Конкретно, чекпоинт должен нести:
- цель в том виде, в каком она была заявлена на старте запуска, с хешем исходной инструкции;
- версию плана, который выполняет агент, а не только следующий шаг;
- предоставление области инструментов — какие инструменты, по какой политике, с каким сроком действия;
- оставшийся бюджет в единицах, которые имеют значение (вызовы, токены, лимиты в денежном эквиваленте, выраженные как границы политики, а не суммы);
- причину следующего шага в форме, которую может проверить валидатор.
При возобновлении рантайм повторно валидирует этот контекст против текущей политики до продолжения. Повторная валидация — не формальность. Она спрашивает: эта цель всё ещё разрешена? Эта область инструментов всё ещё предоставлена? Оставшийся бюджет всё ещё согласуется с планом? Причина следующего шага всё ещё подкреплена целью?
Провал повторной валидации — это терминальное состояние, а не повтор. Повтор при отказе политики лишь сжигает бюджет и даёт тот же отказ. Запуск должен остановиться, показать разницу между исходным и текущим интентом и вернуть управление человеку или оркестратору более высокого уровня.
Это реальный компромисс. Повторная валидация добавляет задержку при каждом возобновлении и требует, чтобы политика была доступна как сервис, а не встроена в код. Это также означает, что некоторые запуски, которые завершились бы корректно, останавливаются на проверку. Альтернатива — молчаливое завершение под истёкшими полномочиями — хуже, но цена не нулевая, и об этом стоит говорить честно.
Идемпотентность — это не согласие
Ключи идемпотентности — хорошо понятный механизм: они гарантируют, что повторный внешний вызов не спишет дважды, н
Ключ идемпотентности предотвращает двукратное срабатывание одного и того же вызова. Он ничего не делает с единственным вызовом, сделанным по истёкшему мандату. Если полномочия агента на выдачу возвратов истекли в 14:00, а запуск возобновляется в 14:05, идемпотентный вызов возврата выполнится ровно один раз — и будет несанкционированным.

Эти два понятия должны быть разделены в рантайме:
- ключ идемпотентности идентифицирует операцию, поэтому повторы безопасны;
- токен полномочий идентифицирует мандат, поэтому выполнение разрешено.
Оба должны быть действительны в момент выполнения. Долго живущему запуску нужны полномочия, которые истекают и явно продлеваются, а не токен, выпущенный на старте и переносимый на протяжении всей жизни workflow. Продление — это точка решения: его можно предоставить, отклонить или предоставить с более узкой областью. Эта точка решения — то место, где интент возвращается в систему.
Правила проектирования для длинных запусков
Из сказанного следует несколько правил. Они не бесплатны, но дешевле молчаливого отказа корректности.
Ограничивайте длительность запуска временем жизни полномочий, а не терпением инфраструктуры. Если мандат действителен час, у запуска есть час. Не позволяйте workflow работать три дня только потому, что очередь это выдержала. Когда полномочия истекают, запуск приостанавливается и запрашивает продление.
Перестраивайте план при возобновлении, а не воспроизводите устаревший. План — это предложение, а не скрипт. При возобновлении агенту следует дать цель, текущее состояние мира и оставшийся бюджет и попросить снова предложить следующий шаг. Журнал ограничивает то, что уже произошло; он не должен диктовать, что произойдёт дальше.
Логируйте каждое событие возобновления с разницей между исходным и повторно валидированным интентом. Разница — это интересный артефакт. Она фиксирует, что изменилось — политика, бюджет, область, состояние мира — и почему запуск продолжился или остановился. Без неё пост-инцидентный разбор не имеет опоры.
Конкретный работающий механизм: запись контекста решения, хранимая рядом с журналом, версионируемая и проверяемая небольшим сервисом-валидатором на каждой границе возобновления. Валидатор берёт запись и текущий снимок политики и возвращает continue, pause или terminate. Журнал остаётся заботой рантайма; контекст решения — заботой агента. Их разделение делает разрыв интента видимым, а не прячет его внутри сериализованного стека.
Более широкая мысль в том, что движки надёжного выполнения корректны в том, что они заявляют. Они позволяют workflow пережить сбой. Они не заставляют агентов действовать по правильным причинам. Это второе свойство нужно проектировать сверху, и начинается оно с признания, что чекпоинт без интента — это просто очень надёжный способ ошибаться.
