Human in the Loop

Human-in-the-loop (HITL) — паттерн, где агент в цикле ReAct встаёт на checkpoint и запрашивает подтверждение/помощь человека, прежде чем продолжить (особенно на необратимых или высокорисковых действиях).

Суть

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

Зачем это нужно

Снижает риск автономных действий (агент может «сбежать» или ошибиться — см. Agent CostControl) и повышает доверие: критичные операции (оплата, удаление, юридические/медицинские решения) проходят через человека.

Как работает

  • В цикле агента предусмотрен checkpoint: …→ Action → (HITL? пауза за подтверждением) → Feedback →….
  • Пути эскалации к человеку для исключений и ошибок задаются явно (часть фазы внедрения).
  • Связка с graceful degradation: лучше отдать частичный результат и спросить человека, чем упасть с hard error.
  • Паттерн «план → человек утверждает → агент исполняет» — самый безопасный вход в прод, типичен в support/ops (см. роли в Agent Roles).
  • Clarify-first: если planner не уверен в инструменте или запрос расплывчат — сначала уточняющий вопрос, а не действие.
  • Supervisor как ревьюер: в multi-agent финальное решение принимает супервизор поверх работы аналитика/исполнителя/контролёра.
  • Реализация в LangGraph: пауза = прерывание графа (interrupt_before/after или динамический interrupt()), возобновление — Command(resume=...); работает только при подключённом checkpointer (см. LangGraph HITL). Паттерны взаимодействия — Approval / Edit / Co-authoring.

Альтернативный взгляд: границы доверия как продуктовое решение

Инженерная подача трактует human-in-the-loop как механику остановки графа: чекпоинт, interrupt(), сессия в состоянии ожидания. Продуктовая оптика экономики AI-сервиса начинает с другого конца — с цены ошибки бизнес-процесса.

В этой рамке вопрос звучит не «как технически прервать выполнение», а «какую степень автономности мы вообще готовы дать агенту». Три градации:

Граница доверия Когда выбирается
Полная автономность Ошибка дёшева и обратима
Автономность с подтверждением Действие затратно или заметно для клиента
Только рекомендация Ошибка необратима или дорога — деньги, юридические последствия

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

Ключевой сдвиг: человек здесь — самый дорогой ресурс в цепочке, а не безусловная гарантия качества. Каждое подтверждение оплачивается рабочим временем, поэтому HITL проектируется как статья расходов в unit-экономике (Unit Economics AI) и как верхние ступени лестницы эскалации (Support Escalation Ladder), а не как «поставим человека везде, где страшно».

Подтверждение как примитив протокола, а не условие в коде

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

Обвязка Codex выражает то же самое формой протокола (Agent Harness), и оттуда стоит унести два различия, не зависящих от продукта.

Подтверждение не однородно — типов запроса пять, а не один. Выполнение команды, правка файла, расширение прав песочницы, структурированный ввод от человека, запрос данных со стороны MCP-сервера. Это решения с разной ценой ошибки, и общий диалог «Разрешить действие? Да / Нет» стирает различие ровно там, где оно нужно — ср. класс обратимости в Rollback Boundary.

Ответов три, а не два: accept, decline, cancel. «Отказать в этом действии» и «прекратить работу» — разные исходы. Агент, получивший отказ на один шаг, должен уметь пойти другим путём; агент, получивший отмену, обязан остановиться. Интерфейс с двумя кнопками этой разницы выразить не может, и на практике она схлопывается в самый разрушительный вариант: любой отказ читается как «продолжай как-нибудь иначе».

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

Ожидание человека — это исход прогона, а не зависший вызов

Соседний открытый протокол выражает ту же мысль уровнем выше: не «каким бывает запрос», а чем заканчивается работа, упёршаяся в человека. В AG-UI прогон агента завершается успехом, ошибкой — или исходом «прервано» с непустым списком открытых прерываний; возобновление устроено как новый прогон, во входе которого перечислены ответы на каждое открытое прерывание (AG UI Protocol).

Отсюда три вещи, полезные независимо от протокола:

  • ожидание человека — корректное завершение, а не удерживаемый вызов. Прогон закрыт, ресурсы освобождены, а ожидание живёт снаружи как состояние — то же самое, что «подтверждение есть хранимое состояние» (Approval Path), но записанное так, что иначе реализовать нельзя;
  • отвечать надо на каждое открытое прерывание, а не на последнее: несколько одновременных вопросов протоколом допускаются и не схлопываются в один;
  • отсутствие исхода читается как обычное завершение — совместимость со старым производителем сделана явным правилом, а не догадкой.

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

Два слоя подтверждения: кто решает и какие правила действуют

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

Слой режима отвечает на вопрос «кто решает». Задаётся на старте сессии и внутри неё не меняется. Три режима покрывают практику:

Режим Кто подтверждает Когда
интерактивный человек за терминалом локальная работа
фоновый никто, всё разрешено заранее CI и автоматические прогоны
делегированный родитель, выдавший подагенту срез своего доверия подагенты

Форма этой настройки эволюционирует предсказуемо: булево → функция → размеченное объединение. Булево блокирует всё и годится только чтобы задать форму вопроса. Функция умеет решать по входу, но правило в ней зашито, и CI получает тот же гейт, что терминал. Размеченное объединение выносит режим в данные, а функцию строит по режиму — и заодно даёт типизацию, при которой поле, осмысленное только для делегирования, в других ветках недоступно.

Слой событий отвечает на вопрос «какие правила действуют». Срабатывает на каждом вызове инструмента, и подписчик может пропустить, заблокировать или изменить вызов. Сюда ложится то, что в режим не помещается: «никогда не писать в файл окружения, независимо от режима», «оборачивать любую команду в дополнительную изоляцию».

Слои не заменяют друг друга, потому что решения приходят из разных мест. Режим задаёт тот, кто запускает — человек, CI, родительский агент. Правила задаёт проект: этот файл чувствителен, этот каталог только на чтение. Одна настройка не унесёт оба вида решений, не запутавшись.

Порядок срабатывания даёт защиту в глубину: событие срабатывает после режима, но до выполнения. То есть «человек разрешил» и «политика запрещает» не спорят — второе сильнее.

Соотношение с протокольной формой из Agent Harness: там пять типов запроса и три ответа описывают как спросить; здесь два слоя описывают кого спрашивать и что запрещено независимо от ответа. Устройства дополняющие, а не конкурирующие.

Агент не станет спрашивать сам

Отдельное наблюдение, которое меняет проектирование: завести инструмент вопроса недостаточно. Модель увидит его, прочитает описание и продолжит не спрашивать. Обучение на переписке разработчиков закрепляет манеру «сейчас сам разберусь»; спросить читается как слабость, и модель предпочитает угадать.

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

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

Форма вопроса — множественный выбор из двух-четырёх вариантов, а не свободный текст: так ответ разбирается однозначно, а сам вопрос вынужден быть конкретным.

Обратная крайность лечится бюджетом, а не уговорами. Агент, которому прописали спрашивать, начинает спрашивать про всё подряд, и это та же approval fatigue из раздела ниже, только по другому каналу: человек перестаёт читать вопросы. Работает связка из трёх ограничений, и каждое нужно:

  • числовой потолок на ход — например, не больше двух уточняющих вопросов за раз. Число делает ограничение принуждаемым, «не задавай лишних вопросов» — нет;
  • критерий допуска: ответ обязан менять конкретное решение, и какое именно — называется. Не «полезно бы знать», а «от этого зависит выбор ветки / маршрутизация / уверенность в воспроизведении». Вопрос, который не проходит критерий, не задаётся вовсе, а не откладывается;
  • приоритизация, когда неизвестных больше потолка: выбираются те, что сильнее всего разблокируют, остальные остаются предположениями и проговариваются как предположения. Иначе потолок обходится склейкой — один пункт с тремя подвопросами внутри, который читается как один, а стоит как три.

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

Вмешательство без остановки

Заметка целиком отвечает на вопрос «когда агент зовёт человека». Обратное направление — человек говорит, когда его не спрашивали — устроено иначе и обычно не проектируется вовсе, хотя нужно ровно тогда же: пользователь видит на экране, что агент пошёл не туда, и ждать ближайшего подтверждения незачем.

Форма, которая для этого работает: набранные строки копятся в очереди, пока задача идёт, и вливаются очередным сообщением на границе хода — не посреди вызова инструмента. Ненужное к концу задачи не выбрасывается, а переигрывается после неё, поэтому напечатанное не теряется ни при каком исходе. Отдельно стоит разбудить ожидание: координатор, собирающий отчёты подагентов, может стоять минутами, и появление указания обязано его поднять, а не ждать таймаута.

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

Чтобы HITL не стал бутылочным горлышком

Узкое место возникает не от количества подтверждений, а от их недифференцированности: когда человека спрашивают обо всём подряд, он перестаёт читать и жмёт «ОК» — это approval fatigue, и она хуже отсутствия HITL, потому что создаёт иллюзию контроля (Guardrails, Agent Governance).

Три приёма, снимающие нагрузку без потери контроля:

  • Ставить гейт только на необратимое. Платежи, отправка наружу, изменение прав, деструктивные команды — да; чтение и черновики — нет. Список необратимого короткий и определяется один раз.
  • Отдавать проверяемое коду. Всё, что выразимо правилом — принадлежность счёта клиенту, домен получателя, лимит суммы — проверяется детерминированно и до человека (Deterministic Veto). Человеку остаётся то, что правилом не выражается.
  • Батчить однородное. Двадцать однотипных подтверждений подряд — это одно решение о политике, а не двадцать решений; их собирают в один экран с возможностью отклонить выборочно.
  • Выдавать постоянное разрешение вместо разового «разрешить всё». Сессионный переключатель «больше не спрашивать» меняет всю оставшуюся безопасность на удобство одним нажатием, и нажимают его именно от усталости. Заменяется он сохраняемым правилом на конкретную команду: «всегда разрешать прогон тестов», «никогда не разрешать отправку в удалённый репозиторий».

У постоянных разрешений обязан быть контракт, иначе они становятся тем же переключателем. Хранилище правил умеет ровно две вещи: понизить обычный запрос подтверждения до безопасного либо форсировать отказ. Расширить разрешённое оно не может, потому что консультируются с ним после детектора опасности и жёсткого запретного списка — сохранённое «всегда разрешать git» не пропустит принудительную перезапись ветки. Это тот же порядок «политика сильнее человека», что у слоя событий выше, распространённый на решения, которые переживают сессию.

Гранулярность правила решает, работает контракт или нет. Первого слова команды мало: разрешив npm test, по первому токену разрешаешь и npm publish. Для команд с подкомандами — git, npm, docker, пакетные менеджеры — правило хранит два слова. И сверяется оно с каждым сегментом составной команды, разделённой &&, | или ;, а неразобранный текст трактуется в пользу отказа: иначе разрешение обходится дописыванием второй команды через амперсанд.

Необратимость бывает социальной

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

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

Показательный отказ на детерминированном правиле. Классический бот, закрывающий обращения по отсутствию активности за N дней, устроен безупречно с точки зрения кода и вызывает устойчивую неприязнь у сообществ. Причина в подмене: отсутствие обсуждения означает «до этого не дошли руки», а бот трактует это как «неактуально». Человек, потративший время на подробное описание проблемы, получает автоматический отказ — и правило, сэкономившее сопровождающим час, стоит проекта репутации.

Форма, которая это разрешает, — разделить пометку и решение. Агент делает объёмную часть и обосновывает, человек делает короткую и решает:

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

Экономия сохраняется почти полностью: дорогая часть работы — поиск и обоснование — снята с человека, а за ним остаётся решение, которое он принимает быстро именно потому, что материал уже собран. Это ровно тот же ход, что «отдавать проверяемое коду» из раздела выше, только с обратным знаком: проверяемое отдаётся агенту, а непроверяемое — то, как это прочтёт человек, — остаётся человеку.

Практический признак класса формулируется одним вопросом: узнает ли кто-то посторонний о том, что действие произошло? Если да, обратимость технической отмены значения не имеет, и действие идёт в тот же список, что платежи и удаления.

Связано с

  • Agent Failure Modes — что происходит, когда гейт поставлен сигналом и вызов исчезает молча
  • Tool Catalog — контракт инструмента, поверх которого работает гейт
  • Agent Harness — подтверждение как примитив протокола: пять типов запроса, три ответа
  • Approval Path — подтверждение как хранимое состояние: срок, эскалация, запись
  • AG UI Protocol — прерывание, выраженное исходом прогона, и возобновление новым прогоном
  • Agent Client Protocol — запрос разрешения как метод протокола, а не условие в коде агента
  • Capability Containment — почему одних подтверждений недостаточно
  • ReAct — checkpoint встроен в цикл рассуждения
  • AI Agent — баланс автономности и контроля
  • Agent CostControl — человек как ещё один слой защиты
  • LangGraph HITL — конкретная реализация паттерна в LangGraph (interrupt/resume)
  • Stale Write Guard — предохранитель на тех же воротах подтверждения, с противоположной посадкой отказа