← Your First AI Agent: From Zero to Autonomy
05 / 07 · 40 min

Guardrails: Budgets, Timeouts, and Human-in-the-Loop

Setting boundaries for the agent: step, token, and money limits, tool timeouts, and mandatory human approval for irreversible actions.

Lesson content is currently available in Russian.

What you will learn

  • →Считать и ограничивать расход агента: шаги, токены, деньги, время.
  • →Классифицировать инструменты по опасности и строить шлюз подтверждения.
  • →Вести аудит-лог, по которому можно восстановить каждый прогон.

Бюджеты: шаги, токены, деньги, время

Автономность без лимитов — это неограниченная доверенность на ваш кошелёк и ваши системы. Потолок шагов из урока 2 — необходимый минимум, но его легко обойти: агент может уложиться в десять шагов, раздув каждый до сотен тысяч токенов. Поэтому бюджет должен быть многомерным: шаги, токены, деньги, wall-clock время — и проверяться перед каждым дорогим действием, а не после.

from dataclasses import dataclass, field
import time

@dataclass
class Budget:
    max_steps: int = 15
    max_tokens: int = 200_000
    max_usd: float = 0.50
    max_seconds: float = 120.0
    steps: int = 0
    tokens: int = 0
    usd: float = 0.0
    started: float = field(default_factory=time.monotonic)

    def charge(self, usage, price_in, price_out):
        self.steps += 1
        self.tokens += usage.total_tokens
        self.usd += (usage.prompt_tokens * price_in
                     + usage.completion_tokens * price_out) / 1e6

    def exceeded(self) -> str | None:
        if self.steps >= self.max_steps: return "steps"
        if self.tokens >= self.max_tokens: return "tokens"
        if self.usd >= self.max_usd: return "usd"
        if time.monotonic() - self.started >= self.max_seconds: return "time"
        return None

Поле usage приходит в каждом ответе API — используйте его, а не оценки на глаз. Ключевая деталь UX: при исчерпании бюджета не обрывайте прогон молча. Дайте модели один финальный вызов без инструментов с просьбой подвести итог: что успели, что осталось, что рекомендуется дальше. Частичный отчёт ценнее пустого таймаута.

Порядок проверок имеет значение: exceeded() вызывается перед обращением к модели и перед каждым пишущим инструментом — дешёвая проверка до дорогого действия, а не после. Значения лимитов подбирайте от задачи: интерактивному помощнику хватит пятнадцати шагов и пары минут, фоновому batch-прогону — сотни шагов, но жёсткий долларовый потолок. И заведите отдельный, более щедрый пресет для отладки: ловить ложные срабатывания бюджета на этапе разработки так же вредно, как не ловить настоящие в проде.

Таймауты и песочница исполнения

Бюджеты ограничивают объём работы, таймауты и песочница — её радиус поражения. Здесь работает принцип вложенных таймаутов: у каждого сетевого вызова инструмента свой таймаут (10-30 секунд), у вызова модели — свой (60-120 секунд, стриминг помогает заметить зависание раньше), у прогона целиком — общий потолок из бюджета. Наружный таймаут без внутренних — плохая конструкция: он сработает, но вы не узнаете, кто именно завис.

Песочница — это ответ на вопрос «что худшее может сделать этот инструмент, если модель ошибётся или её обманут». Минимальный набор для агента, который трогает файлы и сеть: файловые операции заперты в рабочем каталоге (нормализуйте путь и проверяйте, что он внутри корня, — иначе агент однажды прочитает ~/.ssh/id_rsa по вполне невинной причине); сетевые запросы идут только на allowlist доменов, а не «куда угодно»; секреты не лежат в промпте — инструмент сам подставляет токены из окружения, модель их не видит и не может утечь.

Отдельный режим, который стоит встроить с первого дня, — dry-run: все пишущие инструменты не исполняются, а возвращают «[dry-run] создал бы комментарий: ...». Это дёшево в реализации (один флаг в реестре инструментов) и бесценно в отладке: вы смотрите полный план действий агента на реальных данных, ничем не рискуя. Капстоун этого курса начнётся именно с dry-run-режима, и это общий паттерн запуска любого агента в новое окружение: сначала неделя dry-run и чтение логов, потом включение записи.

Human-in-the-loop: шлюз для необратимого

В уроке 3 мы пометили инструменты как readonly, write и irreversible. Теперь эта метаданная становится политикой: readonly исполняется молча, write — по правилам (лимит на количество, dry-run в новых окружениях), irreversible — только через явное подтверждение человека.

def execute(call, policy):
    tool = REGISTRY[call.name]
    if tool.danger == "readonly":
        return tool.fn(**call.args)
    if tool.danger == "irreversible" or policy.confirm_writes:
        answer = ask_human(f"Агент хочет: {call.name}({call.args}). Разрешить?")
        if answer != "yes":
            return f"отклонено человеком: {answer or 'без причины'}"
    return tool.fn(**call.args)

Обратите внимание: отказ человека возвращается модели как результат инструмента, с причиной. Это не сбой, а информация — агент скорректирует план («хорошо, тогда только предложу метки в комментарии, без изменения issue»). В консольном скрипте ask_human — это input(); в сервисе — запись «ожидает подтверждения» в состояние прогона и уведомление, к этому вернёмся в следующем уроке.

Три правила зрелого HITL. Первое: подтверждение показывает полные аргументы, а не только имя инструмента — «send_email(to=all-staff@...)» и «send_email(to=мне)» — разные решения. Второе: агрегируйте, где можно, — двадцать однотипных подтверждений подряд приучают человека жать «да» не глядя, и весь шлюз обесценивается; лучше одно подтверждение плана из двадцати пунктов. Третье: каждое решение человека пишется в аудит-лог вместе с шагами агента — JSONL-файл, где на строку приходится одно событие: вызов модели, вызов инструмента, результат, вердикт человека, счётчики бюджета. Когда агент сделает что-то странное — а он сделает, — этот лог будет единственным способом восстановить картину и починить первопричину, а не симптом.

Practice

Агент в клетке

Оберните агента из урока 4 в guardrails: класс Budget с четырьмя лимитами и финальной сводкой при исчерпании; таймауты на каждый инструмент; флаг dry-run для пишущих инструментов; шлюз подтверждения через input() для инструмента с пометкой irreversible; аудит-лог в JSONL со всеми событиями. Устройте стресс-тест: дайте задачу, провоцирующую цикл (инструмент стабильно возвращает ошибку), и покажите, что бюджет останавливает прогон со сводкой, а лог позволяет восстановить хронологию.

Deliverable

Код с Budget, dry-run, шлюзом подтверждения и JSONL-логом плюс лог стресс-теста, где виден останов по бюджету и финальная сводка агента.

Checkpoint

Ваш агент получает доступ к инструменту delete_repository. Перечислите слои защиты, которые вы поставите между намерением модели и исполнением.

  • ✓ Названы классификация инструмента как irreversible и подтверждение человеком с показом полных аргументов.
  • ✓ Упомянуты бюджеты и dry-run как предварительные слои, а не только подтверждение.
  • ✓ Сказано, что отказ человека возвращается модели как результат с причиной, а событие попадает в аудит-лог.

Check yourself

Почему лимита шагов недостаточно как единственного бюджета?

01Почему лимита шагов недостаточно как единственного бюджета?

Что должен сделать агент при исчерпании бюджета?

02Что должен сделать агент при исчерпании бюджета?

Зачем нужен режим dry-run?

03Зачем нужен режим dry-run?

Человек отклонил действие агента на шлюзе подтверждения. Что делает правильный цикл?

04Человек отклонил действие агента на шлюзе подтверждения. Что делает правильный цикл?

Почему двадцать подтверждений подряд — плохой HITL-дизайн?

05Почему двадцать подтверждений подряд — плохой HITL-дизайн?

Sources

Guardrails: Budgets, Timeouts, and Human-in-the-Loop · Your First AI Agent: From Zero to Autonomy · VORCL