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

AI-агент готовит заметки к релизу. Пользователь просит собрать изменения из задач, дождаться результатов тестов и опубликовать текст в проекте. Ожидание занимает несколько минут, поэтому приложение сохраняет промежуточный результат: черновик и шаг, на котором остановилась работа. Такую сохранённую точку будем называть checkpoint.

В момент запуска пользователь имеет право публиковать материалы. Пока агент ждёт тесты, его выводят из проекта. Черновик от этого не исчезает, но последняя операция уже не должна выполняться. Если агент сохранил в checkpoint старое решение can_publish=True и использует его после возобновления, сохранение состояния незаметно превращается в сохранение разрешения.

Здесь полезно разделить два вопроса. Checkpoint отвечает, с какого места продолжать вычисление. Система авторизации отвечает, можно ли выполнить действие сейчас. У этих ответов разный срок жизни.

Как появляется устаревшее разрешение

Можно проверить доступ при запуске и сохранить результат вместе с черновиком. Тогда после ожидания агент просто прочитает флаг can_publish_at_start. Проблема в том, что этот флаг хранит ответ на старый вопрос: разрешали ли пользователю публикацию в момент запуска? За несколько минут права могли измениться.

Если обработчик продолжения доверяет старому флагу, он опубликует текст даже после отзыва доступа. Проверка была, но произошла слишком рано.

Что сохранять в checkpoint

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

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

Полный пример со сменой роли

permissions = {("user-17", "project-42", "publish"): True}
published = []

def allowed(user_id: str, project_id: str, action: str) -> bool:
    return permissions.get((user_id, project_id, action), False)

def start_job():
    return {
        "user_id": "user-17",
        "project_id": "project-42",
        "draft": "Release 2.4 is ready",
        "can_publish_at_start": allowed("user-17", "project-42", "publish"),
    }

def resume_bad(checkpoint):
    if checkpoint["can_publish_at_start"]:
        published.append(checkpoint["draft"])
        return "published"
    return "denied"

def resume_fixed(checkpoint):
    if not allowed(checkpoint["user_id"], checkpoint["project_id"], "publish"):
        return "denied: permission changed"
    published.append(checkpoint["draft"])
    return "published"

checkpoint = start_job()
permissions[("user-17", "project-42", "publish")] = False

print("Saved authorization flag:", checkpoint["can_publish_at_start"])
print("Naive resume:", resume_bad(checkpoint), "published=", len(published))

published.clear()
print("Fixed resume:", resume_fixed(checkpoint), "published=", len(published))

Результат запуска:

Saved authorization flag: True
Naive resume: published published= 1
Fixed resume: denied: permission changed published= 0

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

Где нужны повторные проверки

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

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

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

Как проверить сценарий

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

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

Собрать долгоживущий агентный процесс

В AI Agents PRO: LangGraph, AutoGen и LLMOps в продакшне вы соберёте агента, который выполняет задачу в несколько шагов и обращается к внешним сервисам. В программе есть контроль полномочий, повторные запросы и защита от повторного выполнения одного действия. Затем вы запустите сервис через FastAPI и Docker и добавите проверки его работы.

Посмотреть программу AI Agents PRO. Открытый урок про цикл, tools и память AI-агента поможет собрать базовый контур перед более сложным графом.

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

Источник: официальная документация LangGraph по persistence.