Самая заманчивая архитектура агентной системы выглядит так: даём модели инструменты — «создай задачу», «закрой задачу», «запиши результат» — и пусть она сама разбирается. Это работает на демо и разваливается в проде.
Проблема не в том, что модель ошибается. Проблема в том, что её ошибка становится состоянием системы. Задача закрыта дважды, статус переехал в обход перехода, запись появилась без ссылки на источник — и вы не можете ни воспроизвести это, ни откатить, потому что решение и его применение слиплись в один непрозрачный шаг.
Поэтому у нас есть инвариант, который нельзя нарушать: LLM решает — код пишет.
Вердикт как данные
Ни один вызов модели не имеет права записи. Классификатор, планировщик, чат-агент — все они возвращают структурированный вердикт, обычный JSON. Дальше его берёт детерминированный код и применяет транзакционно.
Планировщик, например, отвечает примерно так:
{
"verdict": "continue",
"steps": [{ "agent": "backend", "session": "resume", "prompt": "..." }],
"summary_update": "Разобраны требования, начата миграция схемы.",
"reason": "Требования непротиворечивы, можно переходить к схеме данных."
}
Здесь нет ни одного побочного эффекта. Это утверждение о том, что следовало бы сделать. Решение о том, произойдёт ли это, принимает код: он проверяет вердикт, сверяет его с текущим состоянием в базе и применяет — либо отклоняет.
Разница принципиальная. Модель, у которой есть инструмент записи, уже изменила мир к моменту, когда вы увидели её ответ. Модель, которая возвращает вердикт, ничего не изменила — у вас есть точка, где можно вмешаться.
Всё важное — в required
Практический урок, который стоил нам нескольких вечеров: если поле в JSON-схеме необязательное, модель на низком reasoning-бюджете его пропустит. Не иногда — систематически.
Мы навязываем схему ответа принудительно и держим в required всё, без чего вердикт бессмысленен: сам вердикт, обоснование, обновление проекций. Опциональным остаётся только то, отсутствие чего — валидное состояние.
Это же правило работает и как проверка дизайна. Если поле хочется сделать необязательным «чтобы модель не мучилась» — скорее всего, вы не решили, что делать при его отсутствии.
Одна транзакция
Вердикт применяется целиком или не применяется вовсе. Шаги, обновление сводки, запись в журнал, отметка о провенансе — это один коммит.
Провенанс здесь важен отдельно. Когда роутер решает, к какой задаче относится входящее сообщение, он записывает результат маршрутизации на сам входящий элемент, в той же транзакции. Через месяц, разбирая, почему письмо попало не туда, вы видите не только итог, но и то, каким решением он получен.
Каждое решение несёт reason
Поле reason обязательно во всех вердиктах. Это не украшение для логов — это то, что превращает систему из оракула в инструмент, который можно защитить перед аудитом.
Сюда же — полное сохранение каждого вызова: промпт целиком, ответ целиком, использованные токены, стоимость, причина остановки. Не выжимка, не первые сто символов. Когда через две недели нужно понять, почему агент принял странное решение, единственное, что помогает, — увидеть ровно тот вход, который он получил.
Гарды вокруг цикла
Детерминированный код отвечает не только за запись, но и за то, чтобы цикл вообще завершался. Вокруг движка стоят проверки, ни одна из которых не спрашивает модель:
- таймбокс на шаг и на задачу;
- бюджет итераций — сколько раз планировщик может сказать «продолжаем» подряд;
- несоответствие агента и сессии — попытка продолжить чужую линию рассуждений;
- пустой промпт — вердикт формально валиден, но работы в нём нет.
Любая из этих ситуаций останавливает цикл и поднимает вопрос человеку. Это скучные проверки, и именно поэтому они работают.
Модель может ошибиться в суждении — это нормально и ожидаемо. Она не должна иметь возможности ошибиться в записи.
Что это даёт
Три вещи, которые окупают дополнительный слой:
Воспроизводимость. Вердикт — это данные. Его можно сохранить, показать, переприменить на тестовом стенде и сравнить результат.
Ревьюируемость. Логика применения — обычный код, он проходит обычное ревью. Никто не проверяет промпт «на глаз», надеясь, что модель поймёт правильно.
Аудит. На вопрос «на каком основании система это сделала» есть ответ в виде цепочки: входящее → решение с обоснованием → транзакция → запись в журнале. Без пробелов.
Цена — необходимость заранее описать все возможные вердикты. Это ощущается как ограничение ровно до первого инцидента, который вы разобрали за десять минут вместо дня.