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.