Skip to content

Reviewer

reviewer — это персона, которая второй раз подписывает каждую мутирующую операцию. В отличие от других воркеров она никогда не порождается coordinator — каталог сознательно её исключает. Единственный, кто её порождает, — это декоратор ReviewGate, который оборачивает каждый инструмент с Class равным "write" или "destructive".

Reviewer — это ответ на один вопрос: «кто следит за следящим?»

Не отключайте это.

Reviewer — это то, что вообще делает безопасным дать Ongrid host_restart_service или execute_skill. Пропуск гейта — это явный выбор конфигурации, и он всплывает в audit trail. Если вы форкнете персону на «всегда одобрять», audit log покажет каждое решение — аудиторы заметят.

Декоратор ReviewGate

Позиция в цепочке декораторов инструментов (chain.go):

text
tenant_bind → REVIEW_GATE → timeout → audit → ratelimit → metric → <inner tool>

Почему именно такой порядок:

  • Снаружи timeout. Reviewer сам по себе — это graph.Invoke со собственным бюджетом ходов. Оборачивать его внутри 15с таймаута внутреннего инструмента означало бы форсировать reviewer закончить за 15с — нереалистично. Гейт несёт собственный независимый потолок 60с (DefaultReviewerTimeout).
  • Снаружи audit. Отказы не должны писать синтетическую строку выполнения. Гейт пишет строку chat_mutating_proposals вместо этого. Audit логирует только выполнение внутреннего инструмента, которое случается только при одобрении.
  • Внутри tenant_bind. Payload предложения включает оператора user_id, который tenant_bind резолвит из ctx — гейт должен запускаться после того, как tenant_bind его заполнил.

Условие срабатывания

Декоратор инспектирует Class оборачиваемого инструмента:

ClassПоведение гейта
"read"Пропустить. Внутренний инструмент запускается.
"write"Перехватить. Породить reviewer. Внутренний запускается только при одобрении.
"destructive"То же, что "write". Оба класса проходят через гейт.

Конкретные Class: "write" инструменты в наборе сегодня:

  • host_restart_service — канонический мутирующий скилл.
  • execute_skill — marketplace-обёртка, которая запускает любой установленный из marketplace скилл (гейт не может прочитать тело скилла, чтобы классифицировать, поэтому он гейтит их все).
  • AgentTool — да, сам примитив диспетчеризации — "write", потому что он порождает воркера и, следовательно, расходует ресурсы. На практике reviewer редко гейтит вызовы AgentTool — см. allowlist персоны ниже.

Payload предложения

При перехвате гейт строит:

json
{
  "action": "host_restart_service",
  "target": {"device_id": 7, "service": "nginx"},
  "reason": "User reported 502; nginx error log shows OOM",
  "blast_radius": "single_device",
  "operator": "user_42",
  "context_summary": "<coordinator's running summary of the conversation>"
}

blast_radius — это одно из single_device | cluster | tenant_wide, выводится из метаданных инструмента. Reviewer использует это для взвешивания решения «разумно ли это» — tenant_wide действие сталкивается с большим вниманием, чем single_device.

Персона reviewer

Хайлайты frontmatter (дословно из agents/reviewer.md):

yaml
name: reviewer
description: SOP 二审 reviewer worker,对 mutating / destructive 提案做静态审查
permission_mode: read-only
max_turns: 5
model: anthropic/claude-opus-4-7   # 关键路径用最强
background: true                   # async: spawn returns immediately

tools:
  - get_incident_detail
  - get_edge_summary
  - query_promql
  - query_logql
  - get_sop_text                   # planned (HLD-003 PR-D)

disallowed_tools:
  - "*_skill"                      # wildcard — no skill execution
  - run_shell
  - execute_skill
  - host_restart_service           # explicitly: reviewer cannot itself act
  - kill_process

critical_reminder: |
  你是高危操作二审 reviewer。reject 是默认选项,approve 必须三条都满足:
    1. 找得到对应 SOP 且明确覆盖此场景
    2. 当前没有并行的同类操作(看告警 / 看运维窗口)
    3. 回滚路径已知

Ключевые наблюдения:

  • max_turns: 5. Reviewer не ходят вокруг да около. Пять ходов на решение; промпт персоны явно говорит «看不清就 reject».
  • model: anthropic/claude-opus-4-7. Вызов критического пути получает сильнейшую модель, независимо от org default. Ревью не должно понижаться до быстрой модели — стоимость неверного одобрения значительно превосходит стоимость медленного ревью.
  • background: true. Reviewer работает асинхронно. UI coordinator не блокируется на ревью; task_notification ревью публикуется обратно по готовности. (В текущем подключении внутренний инструмент блокирует до возврата reviewer — но UI нет, поэтому пользователь видит «review pending», пока оно работает.)
  • disallowed_tools wildcard *_skill. Reviewer не может выполнить никакой скилл. Он может читать контекст (get_incident_detail, get_edge_summary, telemetry-запросы), но никогда не действовать.
  • Нет run_shell, host_restart_service, kill_process. Даже если бы тело персоны это сказало, runtime-фильтр срезает эти инструменты. Защита в глубину.

5-шаговый workflow

Из тела персоны reviewer запускает:

  1. Найти SOP. get_sop_text(action) для официальной процедуры. Нет SOP → reject с "no SOP for action <X>".
  2. Проверить состояние цели. get_edge_summary(device_id). Устройство offline / в цикле перезапуска / последнее мутирующее действие в течение 5мин → reject.
  3. Проверить параллельные операции. query_logql для audit: записей, касающихся той же цели за последние 10 минут. Параллельное действие → reject с "parallel operation detected; wait for X to complete".
  4. Валидировать причину относительно алертов. get_incident_detail для ссылочного инцидента. Причина противоречит содержимому алерта → reject («оператор мог неверно прочитать симптом»).
  5. Решить.
    • Approve, когда все три ключевых гейта проходят. Вывод {decision: approve, sop_id, rollback_path, gates_passed}.
    • Reject иначе. Вывод {decision: reject, reason, missing_gates}.

Правило three-of-three

Critical reminder явное:

reject 是默认选项,approve 必须三条都满足:

  1. 找得到对应 SOP 且明确覆盖此场景
  2. 当前没有并行的同类操作(看告警 / 看运维窗口)
  3. 回滚路径已知

«Не уверен» эквивалентно «reject». Это сознательная калибровка — асимметрия стоимостей между неверным одобрением (production-урон) и неверным отказом (один лишний round-trip) сильно благоприятствует последнему.

Формат вывода

Финальный ответ reviewer (публикуется обратно coordinator через <task-notification>):

markdown
**Decision: approve | reject**

**Gates**
- ✓ SOP-007 covers restart nginx
- ✓ node-01 status online; last mutating 17min ago
- ✓ No parallel operation
- ✓ Rollback: `systemctl start nginx`

**Notes**
{1-2 sentence risk note; included even on approve.}

Декоратор парсит Decision: approve (нечувствительно к регистру, должно появиться в первой непустой строке ответа). Всё остальное — включая двусмысленные формулировки вроде «approve with caveats» — трактуется как reject.

Что происходит при approve

Декоратор:

  1. Пишет строку chat_mutating_proposals со status=approved, текстом решения reviewer и SOP id.
  2. Позволяет вызову провалиться во внутренний инструмент.
  3. Audit-декоратор (далее по цепочке) пишет строку выполнения.
  4. Внутренний инструмент возвращает свой нормальный результат coordinator LLM.

Coordinator LLM видит нормальный результат инструмента и продолжает.

Что происходит при reject

Декоратор:

  1. Пишет строку chat_mutating_proposals со status=rejected, причиной reviewer и пропущенными гейтами.
  2. Возвращает ErrReviewRejected, оборачивающий причину reviewer.
  3. Audit-декоратор не пишет строку выполнения (выполнения не было).

Coordinator LLM видит сообщение об ошибке — review rejected: <reviewer reason> — и должен объяснить ситуацию пользователю. Он не должен повторять тот же вызов инструмента; защита дедупликации плюс краткосрочный LRU на причинах отказа удерживают LLM от петли.

Edge case

Что, если пользователь является оператором и админом?

Reviewer всё равно запускается. Нет флага «пропустить ревью, потому что вы админ». Audit — это весь смысл — строка решения reviewer — это бумажный след, который говорит «это действие было одобрено против SOP-X с откатом Y во время T». Админ-override стёр бы след.

Что, если SOP для get_sop_text не существует?

Сегодня корпус SOP разрежен — get_sop_text — это запланированный инструмент (HLD-003 PR-D). В текущем MVP reviewer использует placeholder и откатывается на эвристику: критические действия на устройствах с тегом production требуют явного force_approve_no_sop: true в контексте предложения, который coordinator может выставить только после явного подтверждения пользователя. Это будет заменено корпусом SOP, как только он будет заполнен.

Что, если reviewer таймаутится?

Срабатывает таймаут гейта 60с. Декоратор возвращает ErrReviewTimeout. Coordinator LLM видит ошибку таймаута и должен сообщить о ней пользователю — не повторять. Стабильные таймауты reviewer — это проблема на стороне manager (вероятно, латентность модели); audit log позволяет SRE увидеть паттерн.

Что насчёт read-only инструментов воркеров?

Read-only вызовы инструментов воркеров (query_promql, get_host_load и т.д.) имеют Class: "read" и никогда не срабатывают на гейте. Только "write" / "destructive" инструменты делают это. Сам reviewer несёт только Class: "read" инструменты, поэтому воркер reviewer сам не может сработать на вложенном ReviewGate.

Кастомизация гейта

Что вы можете изменить в форке:

  • Модель reviewer. Если у вашей организации нет доступа к Anthropic, перепишите model: frontmatter на сильнейшую доступную модель. Гейт использует то, что декларирует персона.
  • Потолок 60с. Переопределите через опцию конструктора ReviewGate, если ваш reviewer регулярно занимает дольше. Не опускайтесь ниже 30с; reviewer нужно место для 5 ходов × LLM + round-trip инструментов.
  • Тело персоны reviewer. Добавьте team-specific чек-листы (PCI scope, change-freeze windows, on-call calendar). Гейту неважно, что в теле, лишь бы финальная строка решения была парсимой.

Что менять нельзя:

  • name: reviewer — гейт ищет его по имени (DefaultReviewerAgent).
  • Wildcard disallowed_tools *_skill — reviewer сам не должен выполнять действия; защита в глубину говорит, что это форсирует runtime-фильтр, а не промпт.

Для per-tool кастомного reviewer (например, db-specific), конструктор декоратора принимает override reviewerAgent, так что разные инструменты могут маршрутизироваться на разные персоны reviewer. Используйте экономно — несколько reviewer фрагментируют audit trail.