Две недели назад агент, которого я гонял сорок пять секунд, упал, потому что downstream HTTP-запрос отвалился по таймауту. Оркестратор молча ретрайнул с самого начала, и второй прогон нагаллюцинировал половину того, что первый прогон уже выполнил — включая запись строки в продакшен-базу, которую нам потом пришлось вручную сверять с eval-трейсом, который мы успели потерять. Фикс — три строки кода в чекпоинт-хелпере, который я должен был написать в первый же день. Урок — что я два года думал об агентах как prompt-инженер, а десять лет — как distributed-systems-инженер, и distributed-systems-инженер всё это время тихо бурчал с задних рядов.
Это пятый пост в арке, которую я пишу. Context engineering был рантайм-дисциплиной — как ты собираешь входы модели на каждом шаге. Spec-driven development — design-time-философия: твоя кодовая база — это не код, а спеки. Evals — слой верификации: если шипишь AI-фичу без evals, у тебя не продукт, а демо с пользователями. OpenSpec — операционная система, которая запускает все три на brownfield-команде, не разваливаясь. Каждый из этих постов неявно предполагал агента, который доходит до конца задачи. В продакшене агенты не доходят. Они падают посреди tool-call. Они выжирают context window на середине long-horizon задачи. Их ретрайнит оркестратор, который не знает, какие сайд-эффекты уже сели. Они стопорятся на human-in-the-loop чекпоинте, про который человек забывает на три дня.
Durable execution — это субстрат под остальными четырьмя дисциплинами. Это пятая, и без неё арка всегда была неполной.
Вот что я попросил бы любого senior .NET-инженера заметить до того, как пост начнётся всерьёз: вы уже понимаете durable execution. Вы её уже шипили — на территории csharp-to-claude-agents-path, ещё до того, как LLM стали продуктом. Каждая MassTransit-сага, которую вы писали, была durable execution. Каждый MediatR-pipeline с compensation handler — durable execution. Каждая Hangfire-задача с idempotency key — durable execution. Каждый NServiceBus-контракт, в котором различались OnceOnly и AtLeastOnce, — durable execution. Паттерн идентичен. Изменилось одно: участник стал недетерминированным и жжёт токены. Это не новая проблема — это новые ограничения для старой проблемы, и у старой проблемы есть отработанные паттерны.
Что значит “durable” для агента, конкретно
Durable execution — это исполнение, чей прогресс переживает смерть процесса, который его запустил. Грохнули контейнер, рестартанули оркестратор, поменяли модель, отвалилась сеть — workflow продолжается с последнего закоммиченного чекпоинта, не дублирует сайд-эффекты, не переупорядочивает их и приходит к тому же результату, к которому пришёл бы, если бы ничего не падало.
Две вещи ломают наивный ретрофит этой идеи на агентный loop, который под неё не проектировался. Первое: агенты стохастичны — повторение того же промпта при той же температуре на той же модели часто даёт другой tool-call, поэтому “просто ретрайнем агента с начала” гарантирует другие сайд-эффекты на втором прогоне. Второе: агенты дорогие по токенам — проигрывать историю на сорок тысяч токенов каждый раз на resume — это вопрос бюджета, а не инженерии, и финдиректор рано или поздно это заметит. Durable execution для агентов — это дисциплина проектирования loop так, чтобы ни одна из этих двух вещей не выстрелила в продакшене.
Лексика ложится один-в-один на workflow-engine-концепции, которые .NET-инженер уже понимает:
| Workflow / saga концепт | Эквивалент для агента | Цена пропуска |
|---|---|---|
| Idempotent activity | Idempotent tool call | Двойные списания, дубликаты строк в БД, тот самый reconcile-пейджер, который у меня загорелся две недели назад |
| Детерминизм + replay | Чекпоинт плана + replay с последнего закоммиченного шага | Токены, которые невозможно оправдать; reasoning-трейсы, которые на каждый ретрай выглядят как новый агент |
| Saga compensation handler | Откат tool-call (refund, delete, retract) | Половинчато применённые изменения; ручной разгрёб в ops; потеря доверия downstream-систем |
| Workflow-as-state-machine | План-как-state-machine, агент — участник | Оркестратор не знает, где агент в плане; не может ни восстановить, ни наблюдать |
OnceOnly-семантика | Dedupe по tool_use_id на границе activity | Тулы стреляют дважды на ретрае; race conditions; eventual-consistency-сюрпризы |
| Human-in-the-loop чекпоинт | Approval как durable wait | Workflow стопорится; человек забывает; прогон умирает; теряются три дня накопленного state |
Если вы прочитали эту таблицу и не увидели ничего нового — это и есть пойнт. Anthropic Agent SDK уже шипит durable run primitive, который ровно эту форму и обнажает: dedupe по tool_use_id, чекпоинты на каждый plan-step, resumable conversation state через vendor-side ретраи. Inngest Agent Kit, AI-расширения Temporal, agent-рантайм Restate, Cloudflare Workflows, AWS Step Functions — все сошлись к примерно одному и тому же примитиву за последние двенадцать месяцев. Конвергенция произошла быстро, потому что лежащий в основе паттерн уже был стандартом вне AI. Вендоры догоняют то, что saga-orchestration-сообщество делает с начала 2010-х.
Четыре failure mode не-durable агентов
Каждый агент, не спроектированный под durable execution, в продакшене умирает одним из четырёх способов. У меня шрам по каждому.
1. Crash посреди tool-call. Агент вызывает tool, который возвращается за десять секунд. На шестой секунде контейнер вытесняется, функция отваливается по таймауту, или сам model API отдаёт 502 из-за блипа выше. Сайд-эффект tool’а применился или нет? Если на принимающей стороне нет dedupe-таблицы по tool_use_id, узнать невозможно. Ретрайнешь — двойное списание. Не ретрайнешь — молча теряешь работу. Корректного ответа на этом слое нет — корректным ответом было сделать tool идемпотентным на этапе проектирования. Если вынести из всего поста одно правило: каждый tool, мутирующий state вне контекста агента, обязан принимать idempotency key, и принимающая система обязана проверять его до применения изменения.
2. Выжранный context window посреди прогона. Агент сорок пять шагов внутри long-horizon задачи. System prompt — две тысячи токенов; накопленная история tool-call’ов — сорок тысяч. Следующему planning-шагу нужно восемь тысяч токенов reasoning. Модель возвращает refusal, обрезанный план или — что хуже — уверенный план на контексте, который модель молча суммаризировала не на том слое. Это то, что Context Engineering называет “вышли за working set” — здесь стопорится большинство продакшен-агентов. Фикс — на runtime-слое: агрессивная компакция истории на чётких границах чекпоинтов, со сжатым summary, записанным в durable storage, чтобы возобновлённый прогон гидратился из того же summary, а не реплеил сырую историю.
3. Retry storm на неидемпотентных тулах. Агент упал на середине. Оркестратор ретрайнит. Ретрай попадает в tool, который предыдущий прогон уже вызвал. Tool неидемпотентный. Второй прогон порождает третий сайд-эффект. К моменту, когда вы это замечаете, у вас три Stripe-charge’а, два Slack-сообщения и одна продакшен-строка БД в трёх экземплярах. Механизм ретрая сделал хуже, не лучше — агент не восстановился, оркестратор просто умножил ущерб. Это та failure mode, которая научила saga-orchestration-сообщество запекать idempotency keys в контракт, а не прикручивать их после.
4. Зависшая human-in-the-loop пауза. Агент дошёл до approval-чекпоинта. Пинг в Slack. Человек на обеде, в самолёте или — чаще всего — открыл сообщение, решил ответить позже и забыл. Процесс агента продолжает крутиться, ждёт синхронного ответа, жжёт ресурсы, держит открытым сетевой сокет. Или хуже: процесс агента выходит, пока ждёт, человек подтверждает через три часа, и ответ улетает в Slack-тред, который никто не слушает. Паттерн в не-LLM saga-территории — моделировать human-in-the-loop pause как durable wait: workflow засыпает, resume-сигнал — durable callback, и workflow всё равно — resume может прийти через три секунды или через три дня. Тот же паттерн переносится напрямую.
Каждое из этих — failure mode рантайм-субстрата, не модели. Кидать на это более умную модель ничего из этого не чинит.
Пять правил, которые я выстрадал, отгружая durable-агентов
Эти правила я бы вписал в project.md любого нового репозитория, который шипит агентов в 2026. Они не оригинальные — это то, что workflow-engineering-сообщество говорит уже десятилетие, переведённое на агентный лексикон.
Правило 1: idempotent tools по умолчанию
Каждый tool, мутирующий state вне локального контекста агента, принимает idempotency key как first-class параметр, и принимающая система проверяет его до применения изменения. В простейшей реализации key — это tool_use_id из tool-call-схемы Anthropic/OpenAI; для тулов, охватывающих несколько ходов или компонующих другие тулы, — стабильный хэш plan-step ID. Исключений нет. Цена реализации — один лишний параметр на каждом tool; цена пропуска — тот самый продакшен-reconcile, с которого начинался этот пост.
Почему это не обсуждается. Тулы, которые сегодня выглядят “read-only”, завтра ретрофитятся кэшированием или телеметрией — и становятся side-effecting тем способом, который агентный loop увидеть не может. Сделайте всю поверхность тулов идемпотентной на уровне контракта — и больше никогда не придётся пересматривать, какие тулы “являются или нет”; все являются.
Правило 2: чекпоинт после каждой внешней записи
План агента разбит на шаги. После каждого шага, который производит внешний сайд-эффект — запись в БД, вызов API третьей стороны, сообщение в очередь — оркестратор коммитит durable-чекпоинт. Чекпоинт содержит: step ID, выполненный tool-call, возвращённый результат и хэш working context’а агента в этот момент. На resume оркестратор реплеит план с последнего закоммиченного чекпоинта, а не с начала.
Хранилище чекпоинтов не обязано быть сложным. Postgres-таблицы с уникальным ограничением на (workflow_id, step_id) достаточно. Cloudflare Workflows API обнажает это как step.do(). Anthropic Agent SDK — как callback checkpoint. Temporal — как activity history. Выберите один и стандартизируйте по всей кодовой базе.
Правило 3: никогда не возобновляйтесь в устаревший план
Самый тяжёлый баг в durable-агентах — resume-into-staleness. Агент упал на седьмом из двенадцати шагов плана. План был сгенерён из контекста, включавшего данные — скажем, snapshot инвентаря, — которые на тот момент были свежими. Через три часа workflow возобновляется. Snapshot инвентаря теперь неверный, но план об этом не знает, и агент уверенно идёт с восьмого шага против изменившегося мира.
Правило: любой элемент плана, зависящий от внешнего state, имеет freshness-бюджет, и resume за пределами бюджета триггерит re-plan с последнего хорошего чекпоинта, а не слепое продолжение. Freshness-бюджет — часть плана, а не часть runtime-конфига. Агент решает, что свежо; оркестратор это обеспечивает.
Правило 4: бюджетируйте токены на resume, а не только на старте
Возобновлённый агент наследует context window, который вырастил оригинальный прогон. Если не компактировать агрессивно на границах чекпоинтов, каждый resume дороже предыдущего, и workflow, ретрайнутый четыре раза, на пятой попытке может стоить больше токенов, чем суммарно первые четыре. Это та failure mode, которая превращает “хорошо настроенный eval-suite” в “тикет из финансов”.
Дисциплина — компактить историю разговора на каждом durable-чекпоинте: складывать сырые ходы в структурированное summary, живущее в payload чекпоинта, и регидратить из summary на resume, а не из сырых ходов. Claude API делает это простым через prompt caching: summary чекпоинта становится кэшируемым префиксом, а только новые working-ходы сидят вне кэша. Рычаг на 80% стоимости, про который я писал в csharp-to-claude-agents-path, — это ровно этот рычаг; большинство команд просто не применяют его к возобновлённым прогонам, только к свежим.
Правило 5: workflow — источник истины, агент — участник
Это правило saga-orchestration-сообщество усвоило пятнадцать лет назад, и LLM-экосистема всё ещё догоняет. Workflow — это не “то, что запускает агент”; workflow — это контракт, а агент — участник в нём. Агент может быть заменён на другую модель, другого вендора или детерминированную функцию, и workflow продолжит работать. State живёт в workflow; у агента нет своего durable state.
Если вы ловите себя на рассуждении “что агент знает на седьмом шаге” или “что агент помнит с третьего”, — вы уже проиграли. Агент знает то, что workflow ему расскажет на седьмом шаге, точка. Агент помнит то, что лежит в payload чекпоинта на третьем шаге, точка. “Память” агента — это state workflow, материализованный в промпт на каждом шаге. Вот и вся ментальная модель.
Где это всё ещё разваливается
Честная часть. Durable execution решает три из четырёх продакшен-failure-mode чисто. Четвёртая — и несколько смежных — остаются открытыми проблемами, на которые у меня нет чистых ответов. Если, прочитав следующие четыре абзаца, вы поняли, что ответы есть, — напишите пост и пришлите ссылку; я её прилинкую.
Долгий human-in-the-loop — это UX-проблема, переодетая в инфраструктурную. Durable waits решают workflow-сторону корректности: workflow засыпает, resume-сигнал приходит, workflow продолжает. Но человеческая сторона не решена. Через три дня в паузе человек забыл контекст, который сделал approval-вопрос осмысленным. Подача контекста заново на resume — это generation-проблема, не retrieval-проблема: нужно пересобрать workflow для человека, а не просто показать, где остановились. В 2026 я не видел, чтобы кто-то решил это хорошо; ближайшее — трактовать resume-уведомление как отдельный generation-проход, синтезирующий why-this-matters-контекст заново.
Cross-vendor swap модели посреди прогона теоретически возможен, практически болезнен. Можно компактнуть контекст, персистнуть и регидратить против другой модели на resume — Opus на Sonnet по cost-причинам, Claude на GPT по capability-причинам. На практике разница в форматах промптов, диалектах tool-call-схем и model-specific behavioral quirks означает, что возобновлённый прогон ведёт себя иначе, даже если контекст “тот же”. Инфраструктура может свапнуть модель. Агент свап может не пережить. Сегодня я трактую mid-run vendor swap как code smell, не feature.
Retry-семантика для LLM-as-judge неидемпотентна. Evals на LLM-judge-слое недетерминированы по построению. Если durable workflow ретрайнит judge-шаг, второе суждение может разойтись с первым. Честный ответ — либо snapshot’ить judgment в payload чекпоинта (тогда ретрай реплеит закэшированное judgment, не модель), либо коммитить на уровне “мы спросили judge один раз”, со всеми вытекающими failure-mode-последствиями. Я сейчас делаю первое и не очень рад этому.
Стоимость самой durable-execution инфраструктуры. Каждый чекпоинт — запись. Каждый resume — чтение. Каждый ретрайнутый workflow платит storage и read дважды. Для большинства продакшен-агентов оверхед в однозначных процентах, но для high-fanout multi-agent-систем математика жёстче. Я не видел чистого разбора “когда durable execution слишком дорогой, чтобы окупиться”; моя эвристика — любой workflow с внешним сайд-эффектом стоит чекпоинтить, а workflow, который только генерит текст без сайд-эффектов, скорее всего нет.
Карьерный угол: durable-agent engineer — следующая названная роль
Паттерн по всей арке один. Context engineering стал названной ролью; eval engineer становится. Spec author — следующая роль, на которую бренд делает ставку. Durable-agent engineer — это роль после неё, и у неё самый глубокий moat для senior .NET-бэкграунда.
Junior (ноль-два года после школы). Изучайте инструменты. Claude Agent SDK и Anthropic SDK шипят durable-run-примитивы на поверхности; читать их доки — самый дешёвый способ войти. Если .NET-а или distributed-systems-фона нет — пройдите RAG and Agents, там покрыты agent-loop-основы, без которых durable execution как концепт не сложится. Соберите один маленький durable-агент end-to-end (workflow + idempotent tool + checkpoint store + resume) — и у вас будет портфолио-кейс, которого почти ни у одного junior’а в 2026 на самом деле нет.
Mid (три-семь лет). Овнайте субстрат. Выберите оркестратор, на котором ваша команда стандартизируется — Temporal, Inngest, Restate, Cloudflare Workflows или native runtime Agent SDK — и овнайте его по всей кодовой базе. Напишите конвенции: какая форма idempotency-key, какая схема payload чекпоинтов, какая retry-политика. Mid-level-инженер, который тихо пишет durable-execution-секцию команды в AGENTS.md, через восемнадцать месяцев становится незаменимым. Если фон — .NET, AI-Powered .NET Development — это место, где agent-loop / saga-orchestration / ASP.NET-Core-интеграция складываются в одну линию; это естественный шаг после стандартной backend-программы.
Senior и staff (восемь лет и больше). Трактуйте durable execution как архитектурный вопрос, а не выбор библиотеки. Интересные вопросы на этом уровне: как durable-агенты сочетаются с существующими message-broker-контрактами? Как мигрировать brownfield-кодовую базу с агентами с “agent loop крутится in-process” на “agent loop — это workflow”? Как ставить token-бюджет, который переживёт ретраи? Как durable-execution-слой взаимодействует со слоем evals, когда judgment сам — workflow-шаг? Это те вопросы, по которым у любого senior .NET-инженера, мигрирующего в AI-инженерию, есть прайор-арт; осознание, что прайор-арт переносится, — это high-leverage-ход. Курсы Spec-Driven Development и OpenSpec Mastery дают методологический фрейм, чтобы эти решения были защитимыми перед остальной командой.
Закрытие арки
Арка всегда была из пяти дисциплин, не четырёх. Context engineering — как собирать входы. Spec-driven development — как выражать намерение. Evals — как доказывать, что выход тот, который хотели. OpenSpec — операционная система, которая запускает все три на живой команде. Durable execution — субстрат, который держит всё это в живых во вторник днём, когда блипает сеть, модель отдаёт 502 посреди tool-call, а человек уходит домой до того, как одобрить чекпоинт.
Каждый senior .NET-инженер, читающий этот пост, уже шипил этот паттерн. Осталось его назвать — и встроить в agent loop на первый день, а не привинчивать после первого продакшен-инцидента. Цена внедрения правил в новую агентную кодовую базу — день. Цена ретрофитнуть их после первого reconcile-пейджера — неделя уборки и медленное восстановление доверия с системами, в которые пишешь.
Субстрат — durable. Агент — участник. Workflow — истина. Spec, context, evals, OpenSpec и durable execution — это арка из пяти дисциплин. Всё остальное — интерфейс над ней.