temporality
Изменить страницу↗
Инженерные документы

Контур качества агентов (Quality Loop)

УПРАЗДНЁН. Гейт-контур с сьютами, калибровкой судьи и свежими зелёными прогонами заменён моделью «версия + наблюдение + откат»: действующая документация — docs/agent-evolution.md. Документ ниже сохранён как исторический след реализованной когда-то механики.

#1. Цель

Сделать эволюцию агентов проверяемой, а не декларативной. Агенты в Temporality развиваются через предложения определений (agent_propose, docs/evaluable-agent.md): агент наблюдает собственное трение, предлагает изменение промпта, человек применяет. Пока применение ничем не подтверждается, кроме внимания человека, любой переход на новую версию определения — акт веры: «кажется, станет лучше».

Контур качества замыкает эту петлю измеримым сигналом:

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

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

Документ описывает реализованный контур: HITL-вердикты как сигнал, сбор golden-кейсов из прогонов, гейт на применении, судью с калибровкой и дашборд качества.


#2. Проблематика состояния до контура


#3. Основные понятия

#3.1. Evaluation Suite (оценочный сьют)

Список golden-кейсов, привязанный к агенту (не к версии — сьют один, версии меняются). Кейс декларативен:

Сьют ограничен 16 кейсами — это потолок осмысленного ревью, а не техническое ограничение.

#3.2. Evaluation Run (оценочный прогон)

Прогон сьюта против зафиксированной версии определения: каждый кейс — отдельный вызов модели, заземлённый скомпилированным промптом именно этой версии. Прогон записывается с привязкой к версии, поэтому сравниваются определения, а не дрейфующий «текущий» промпт.

Вердикт прогона: pass / fail / error (error — никогда не pass: ошибка судьи или модели не считается успехом). Триггеры прогона: manual, gate (запущен гейтом применения), schedule (регулярный запуск планировщиком по настраиваемому интервалу, см. §8.1).

#3.3. Eval Gate (гейт применения)

Состояние, вычисляемое на момент применения предложения:

#3.4. HITL-вердикт

Решение человека, спроецированное в типизированный сигнал и привязанное к версии определения, жившей на момент вопроса: accepted (согласился), corrected (исправил ответ), rejected (отверг). Источники: ответ на ask_human, решение по операции, ручная сверка операции. Классификация исхода прогона по вердиктам детерминирована: отвергнутый вердикт дисквалифицирует прогон как кандидата в golden.

#3.5. Кандидат в golden-кейсы

Завершённый продакшн-ран, из которого может вырасти оценочный кейс: input — промпт задачи, expected_output — ответ прогона (источник succeeded_run) или исправление человека (источник human_corrected). Кандидат проходит ревью человеком; отклонённые удаляются через 90 дней.

#3.6. LLM-судья

Модель инсталляции (EVAL_JUDGE_MODEL), оценивающая открытые ответы по заданной инструкции с порогом 0–100. Судья допускается до гейта только после калибровки: соглашение с историческими вердиктами не ниже 0.7, иначе судья помечается judge-degraded, и его прогоны гейт отвергает как красные.

#3.7. Quality Report

Агрегат по каждой версии определения: прогоны, исходы HITL-вердиктов, лучший и последний прогон сьюта (с вердиктом и соглашением судьи), количество golden и время до подтверждённого улучшения. Версия без данных честно отвечает has_data=false — никаких фейковых нулей.


#4. Петля качества целиком

flowchart TD
    A[Продакшн-раны агента] -->|HITL-вердикты| B[Проекция вердиктов на версию определения]
    A -->|завершённые раны| C[Харвест кандидатов в golden-кейсы]
    C -->|ревью человека| D[Оценочный сьют]
    E[Предложение определения agent_propose] --> F{Гейт применения}
    D --> F
    F -->|fresh green| G[Применение: claim + version++ + снапшот]
    F -->|нет прогона| H[202: гейт сам запускает прогон]
    H --> F
    F -->|red / suite_stale| I[409 + причина, override писателем]
    I --> J[Аудит + событие обхода]
    I --> G
    G --> A
    G --> K[Дашборд качества по версиям]
    B --> K

Петля читается так: продакшн создаёт и кейсы, и вердикты; сьют с вердиктами питает гейт; гейт охраняет применение; применение меняет агента; дашборд показывает, стало ли лучше — и цикл повторяется. Ни одна стрелка не ведёт мимо наблюдаемости: обход гейта (gate:false, override) оставляет аудит и событие.


#5. HITL-вердикты как оценочный сигнал

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


#6. Сбор golden-кейсов из продакшна

Фоновая задача ядра (тик 5 минут) превращает законченные раны в кандидатов:

  1. Обновление терминальных статусов — строки прогонов в workspace остаются «started», пока их никто не спросит; задача подтягивает статус и ответ из Temporal (те же данные, что отдаёт ручной GET рана).
  2. Харвест — в кандидаты попадают только:
    • терминальные раны (completed/succeeded/failed/turn_limit/time_limit/ token_limit/budget_limit);
    • без отвергнутых HITL-вердиктов;
    • без несверованных операций (неопределённый эффект — не эталон);
    • с ответом не длиннее 8192 символов;
    • в окне 90 дней и после «успокоения» журнала (2 минуты). Источник эталона: исправление человека (human_corrected) приоритетнее ответа успешного прогона (succeeded_run). Очередь ревью держит самые показательные кандидаты — не более 10 на агента: переигровки одной задачи (ретраи, релеи) конкурируют за одно место, решённые задачи заблокированы навсегда, а ранжирование отдаёт приоритет исправлению человека, затем проверенным человеком прогонам, затем свежим задачам. Избыточные pending-строки триммируются; их раны могут вернуться в очередь, когда освободится место.
  3. Ревью-очередь — человек принимает или отклоняет; принятое становится кейсом сьюта с expected_output.
  4. Защита от загрязнения — не более 70 % принятых golden агента может приходить из одного проекта (иначе сьют выучивает специфику одного клиента). Правило включается, когда принятый набор уже охватывает несколько проектов; пока проект один, защищать нечего — действует бутстрап-налог в 10 кейсов из одного проекта, иначе однопроектный агент мог принять ровно один golden (2 из 2 — это 100 %) и ревью умирало на 422 после первого «Применить».
  5. Уборка — отклонённые кандидаты удаляются через 90 дней; повторный сбор отклонённого исключён.

События: eval.case_candidate.created, eval.case_candidate.decided. Кандидаты несут производственный текст, поэтому ревью — зона writer/owner.


#7. Гейт на применении предложения

#7.1. Базовое правило

POST /v1/workspace/agents/{id}/proposals/{pid}/apply требует свежий зелёный прогон сьюта на базовой версии предложения (той, на которой предложение основано). Логика:

Состояние Ответ Что происходит
База отстала 409 base_version_stale Предложение нужно перебазировать
Протухший golden 409 suite_stale + список кейсов Ревью эталонов или override
Сьют не может решить гейт 409 suite_not_ready + cause Пусто / меньше 3 кейсов / judge без модели
Нет прогона 202 + evaluation_run_id Сервер сам запускает прогон (trigger=gate)
Повторный apply при прогоне тот же 202, тот же run Идемпотентность: второй прогон не стартует
Последний прогон — error новый 202 (новый gate-прогон) Инфра-ошибка не гасит применение навсегда
Красный прогон 409 gate_blocked + проваленные кейсы Override писателем с justification
Зелёный (свежий) применение Транзакционный apply

Причины suite_not_ready (suite_empty, suite_too_small, judge_not_configured) — те, что вердикт-алгоритм в любом случае превратил бы в error. Отказ до запуска прогона — осознанный: детерминированная неисправимость сьюта иначе превращается в цикл обречённых gate-прогонов (202 → error-ран → «нет прогона» → новый 202). Пути применения: дополнить сьют до трёх кейсов, настроить судью, override с обоснованием или gate:false — все обходы по-прежнему аудируются.

Модель, отвечающая на кейсы gate-прогона, разрешается той же цепочкой, что и черновые визарды: env-шлюз (TEMPORALITY_MODEL_*) → настроенная модель судьи (Agents → Quality) → первая chat-совместимая модель провайдера. Инсталл, настроенный только через онбординг (без env-шлюза), больше не роняет gate-прогон с gate_run_aborted до первого же кейса. UI применяет предложение автоматически, как только запущенный гейтом прогон становится зелёным — первый клик «Применить» остаётся единственным решением человека, повторный клик после ожидания не требуется.

Инфра-сбой модельного вызова кейса (таймаут, транспорт, 5xx после ретраев) записывается как ошибочный кейс с cause model_timeout / model_error — вердикт-алгоритм превращает его в error-прогон, а не в красный fail: медленный провайдер не должен masquerade'иться под деградацию определения. Следующий apply видит error-прогон и сам стартует свежий gate-прогон. Генерация кейса ограничена 4096 токенами — паттерны проверяют только первые 8k символов ответа, а полные 16k генерации на медленной модели превращаются в таймаут-хвосты, валящие иначе зелёные прогоны.

#7.2. Свежесть

Зелёный прогон действителен 3 рабочих дня (EVAL_GATE_FRESHNESS_WORKDAYS, 0 — без ограничения). Суббота и воскресенье не расходуют окно: решение, принятое в пятницу, валидно в среду. Ручка существует, чтобы подстроиться под реальную частоту применений.

#7.3. Транзакционность применения

Применение — одна транзакция: claim предложения + инкремент версии агента + неизменяемый снапшот версии. Частичное применение невозможно.

#7.4. Обход и его цена

gate:false пропускает проверку (право писателя), но обход не бывает ни тихим, ни бесплатным:

#7.5. Мёртвые прогоны

Прогон гейта, зависший в «running» дольше 30 минут (умерло ядро, отвалился путь записи), переводится в error следующим apply — частичные прогоны никогда не гейтят, вечного 202 не бывает.


#8. LLM-судья с калибровкой

#8.1. Зачем

Паттерны отвечают на «есть ли подстрока», но не на «полный ли это ответ, без ли вредного совета и по делу ли». Класс judge/patterns+judge поручает оценку модели-судье.

#8.2. Контракт судьи

#8.1. Конфигурация судьи и расписания

GET/PUT /v1/workspace/evaluation-judge — модель судьи. PUT требует права оператора и валидирует модель по спискам провайдеров (422 на неизвестную); пустая модель — явное «выключено». Приоритет: настройка в интерфейсе бьёт env EVAL_JUDGE_MODEL; существующая строка настройки (даже пустая) — осознанный выбор оператора, env используется только пока строки нет.

GET/PUT /v1/workspace/quality-schedule — интервал регулярных прогонов (interval_hours, 0 = выключено; GET доступен читателю, PUT — оператору). Планировщик перечитывает настройку каждую минуту (правки без рестарта), якорит следующий прогон на последний trigger=schedule прогон и гоняет сьюты последовательно (таймаут 10 минут на агента) по текущей версии определения. Обе настройки живут в UI на странице агентов.


#9. Дашборд качества

GET /v1/workspace/agents/{id}/quality (writer/owner) — таблица по версиям определения:

GET /v1/workspace/agents/{id}/versions/diff?from=&to= (reader) — структурный diff двух снапшотов (что добавлено/удалено/изменено в определении) плюс diff скомпилированных промптов: «что именно изменится в голове агента».


#10. Наблюдаемость

События контура (durable outbox, лучшие усилия — кроме F-2):

Событие Когда
eval.case_candidate.created Ран захарвестен в кандидата
eval.case_candidate.decided Кандидат принят/отклонён
agent.evaluation.gate.passed Гейт пропустил применение
agent.evaluation.gate.blocked Красный прогон заблокировал применение
agent.evaluation.gate.overridden Писатель провёл красный с justification
agent.evaluation.gate.skipped gate:false (после фактического применения)
agent.evaluation.judge.calibrated Калибровка судьи завершена
agent.evaluation.judge.configured Оператор изменил модель судьи
agent.evaluation.schedule.changed Оператор изменил интервал расписания
agent.quality.dashboard_viewed Кто-то открыл дашборд качества

Аудит (access_audit_log): override красного, gate:false — транзакционно.


#11. Роли и приватность


#12. Метрики здоровья петли


#13. Что сознательно не делаем


#14. Критерии готовности

Реализовано и покрыто тестами:


#15. Открытые вопросы


#16. Архитектурные ограничения