Продолжение разбора агентской архитектуры на Habr добралось до главного: что происходит, когда процесс падает, вкладка закрывается или worker просто исчезает. Ответ — всё должно жить в базе данных, а не в памяти. Статья разбирает конкретные SQL-таблицы и паттерны, без которых «агент» — это просто красиво завёрнутый запрос к LLM.
Контекст
Первая часть серии заложила базу: AgentTurn, AgentPlanItem, AgentEvent — три таблицы, которые дают агенту хребет. Агент перестаёт быть нервным генератором текста и получает возможность знать, на каком шаге он находится, что уже сделал и что ещё ожидает подтверждения.
Но трёх таблиц недостаточно. В реальных продуктах сразу возникает следующий слой проблем: пользователь уже подтвердил действие — и забыл об этом; воркер взял задачу и умер; UI обновился и не знает, какие события уже получил; два параллельных запроса одновременно пишут в один проект. Вся эта боль решается не умным промптом, а скучными таблицами в PostgreSQL.
Это паттерн, который хорошо известен в распределённых системах — durable execution. Temporal, Inngest, хвалёные workflow-движки — всё это реализации одной идеи: состояние не должно жить в памяти процесса. В мире AI-агентов эта идея только начинает доходить до разработчиков.
Аналитика
Разрыв между «демо-агентом» и «продакшн-агентом» измеряется именно этим слоем. Демо работает красиво, пока всё идёт по плану. Продакшн — это постоянный поток нештатных ситуаций: сеть моргнула, пользователь ушёл с вкладки, backend перезапустился по расписанию, воркер получил OOM-kill. Без durable state агент при каждом таком событии либо теряет контекст, либо начинает сначала, либо молча делает то, на что уже получал разрешение — или не получал.
Особенно критичен ApprovalGrant. Если пользователь разрешил удалить файлы в рамках одного проекта, это разрешение не должно распространяться на другие проекты или будущие сессии. Без явного хранения grant-а с полями scope, expires_at, project_id и tool_name агент рискует либо спрашивать одно и то же по десять раз (плохой UX), либо действовать без достаточных оснований (риск).
Payload sanitizer — отдельная история, которую большинство пропускает до первого инцидента. В event log рано или поздно попадают API-ключи, base64 файлы по 300 КБ и персональные данные. Централизованный sanitizer, применяемый перед каждой записью в AgentEvent, решает это системно — не «договорённостью между разработчиками», которая живёт до первого пятничного хотфикса.
Кейсы применения в бизнесе
B2B SaaS стартап с AI-ассистентом: если агент обрабатывает документы пользователей, ProjectContext с operation_lock предотвращает ситуацию, когда два параллельных запроса одновременно перезаписывают один output-файл. Достаточно прикладной блокировки — не database lock, а поле active_operation_id с проверкой перед стартом операции. Реализация на несколько часов, а не недели.
Корпорация с legacy-интеграциями: долгие операции — парсинг Excel на 50 000 строк, генерация отчётов, экспорт — не должны жить внутри HTTP-запроса. BackgroundJob с атомарным claim через UPDATE ... WHERE status = 'queued' и lease-механизмом даёт надёжную очередь без Celery, если не нужна горизонтальная масштабируемость. Воркер умер — lease протух — другой воркер подобрал задачу.
SMB и локальный бизнес в КР/СНГ: агентские чат-боты для автоматизации клиентского сервиса часто ломаются именно на state: пользователь написал, ушёл, вернулся через час — и бот не помнит, где остановился. SessionContext с полями pending_approval, event_cursor и active_turn_id решает это за один вечер внедрения — и резко снижает количество «агент снова спрашивает одно и то же» в отзывах.
Кейсы в личной жизни
Разработчик, строящий собственного AI-ассистента или автоматизацию: статья даёт готовый набор SQLAlchemy-моделей. Можно скопировать структуры ApprovalGrant, SessionContext, BackgroundJob и EventCursor как есть, добавить Alembic-миграцию — и получить production-ready state layer за один вечер. Дальше уже можно думать про логику агента, а не про «как не потерять контекст после рестарта».
Контент-мейкер или фрилансер, использующий AI-инструменты для длинных задач: понимание того, как работает durable state, помогает выбирать правильные инструменты. Инструменты, где «прогресс» существует только в интерфейсе и исчезает при обновлении страницы — не production-ready. Стоит смотреть на решения, которые явно говорят о персистентности состояния.
Студент или junior-разработчик: статья — отличный пример того, как думать о системах. Не «как написать промпт», а «что должно остаться в базе, если процесс упадёт прямо сейчас». Это ментальная модель, которая работает далеко за пределами AI-агентов — в любой распределённой системе.
Как применить сегодня
- Добавь в свой агентский проект таблицу ApprovalGrant с полями
scope,expires_at,tool_name— и перестань хранить факт подтверждения в памяти процесса. - Вынеси все операции дольше 2-3 секунд в BackgroundJob с атомарным claim и lease:
UPDATE ... WHERE status = 'queued' AND (lease_expires_at IS NULL OR lease_expires_at < now()). - Добавь EventCursor на UI-стороне: при reconnect запрашивай события после
last_event_seq, не полагайся на WebSocket как на единственный источник истины. - Напиши centralized PayloadSanitizer и вызывай его перед каждой записью в event log — один раз, а не «по договорённости». Особое внимание: base64, строки длиннее 2000 символов, ключи с паттернами
api_key,token,secret. - Если строишь на FastAPI + SQLAlchemy 2: структуры из статьи переносятся почти дословно. Добавь Alembic, создай таблицы — и основа durable state готова.
«Если операция может занять больше пары секунд, она должна стать job-ом. HTTP-запрос принимает задачу, проверяет права, кладёт job в очередь и возвращает состояние: задача принята. А дальше работает worker.»