# ADR-020 (PR #49): Remove invalid top-level doom_loop frontmatter + reviewer doom-loop guard

## Статус
Accepted (2026-07-24)

## Контекст

При review PR #46 (reviewer ревьюил свой собственный .md файл) reviewer agent ушёл в doom loop — 75 повторений `git show f06c9f2 | grep ADR-019` за 6 минут, отменён пользователем на step 88. Investigation выявило 2 root causes:

### Root cause 1: top-level `doom_loop: deny` в agent frontmatter

Три agent-файла (`reviewer.md`, `docs-reviewer.md`, `memory-syncer.md`) содержали `doom_loop: deny` КАК top-level frontmatter поле (строка 6, indent 0, ВНЕ блока `permission:`).

Проблема: `doom_loop` НЕ входит в схему `AgentV2.Info` opencode (`packages/opencode/src/agent/agent.ts`). Unknown top-level поля форвардятся провайдеру как model parameters (passthrough). На строгих провайдерах (umans-ai-coding-plan, anthropic) это вызывает `AI_APICallError: Extra inputs are not permitted`. Подтверждено логом `opencode.log:91717`.

Валидное место для `doom_loop` — ВНУТРИ блока `permission:` (отступ 2 пробела). Там поле распознаётся opencode как permission rule (guard против doom loops на permission layer). Top-level копия — ошибочное дублирование (вероятно при миграции `config/` → `.opencode/` в PR#23).

### Root cause 2: self-referential ADR-019 trigger в reviewer.md Setup шаг 5

Setup шаг 5 в reviewer.md содержал inline-ссылку на ADR-019 и детальное объяснение запрета `gh pr checks` (403) и `python3 pipeline-status.py` (denied). При review своего собственного .md файла модель зациклилась на верификации этой ссылки: читает reviewer.md → видит "ADR-019" → идёт проверять существование ADR-019 (`git show f06c9f2 | grep ADR-019`) → повторяет 75 раз.

Это "крючок" (self-referential trigger) — промпт-ссылка на документ, который агент пытается верифицировать, вместо того чтобы выполнять review.

## Решение

### 1. Удалить top-level `doom_loop: deny` из frontmatter (3 файла)

Удалена строка `doom_loop: deny` на indent 0 из `reviewer.md:6`, `docs-reviewer.md:6`, `memory-syncer.md:6`.

`doom_loop: deny` ВНУТРИ блока `permission:` (indent 2, строки 8/8/8) — ОСТАВЛЕН (валидный permission guard). `steps: 100` — НЕ ТРОНУТ.

### 2. Упростить Setup шаг 5 в reviewer.md

Inline-ссылка на ADR-019 и детальное объяснение запрета заменены на лаконичную инструкцию:
- `pipeline_status({pr_number: <PR_NUMBER>})` tool (primary)
- `gh run list --branch <headRefName> --limit 3` (fallback)
- "Do NOT use `gh pr checks` (403) or bash `python3 .../pipeline-status.py` (denied)" (краткий запрет без ADR-ссылки)

Запреты функционально обеспечены permission rules (frontmatter) + DANGEROUS_PATTERNS (check-permissions.py), inline-ссылка не была функциональным guard — только self-referential hook.

### 3. Investigation Budget секция

После Setup секции добавлена `## Investigation Budget`:
- Максимум ~15 steps для investigation (Setup + checklist)
- После 15 steps — обязательный `post_review`, даже при неполном review
- "An incomplete review with verdict NEEDS_DISCUSSION is better than an infinite investigation"
- "Do NOT repeatedly verify references in agent .md files — read once, assess, move on"

Structural guard против doom loops: явный step limit даёт модели сигнал остановиться, вместо бесконечной верификации.

### 4. Тесты (`tests/test_agent_frontmatter.py`)

6 тестов (pyyaml не в зависимостях — ручной парсинг frontmatter по паттерну `check-permissions.py`):
- `test_no_top_level_doom_loop` — parsed frontmatter не содержит top-level `doom_loop`
- `test_no_top_level_doom_loop_raw` — raw text check: нет `doom_loop:` на indent 0
- `test_permission_doom_loop_present` — `permission.doom_loop: deny` присутствует во всех 3 файлах
- `test_steps_100_present` — `steps: 100` присутствует во всех 3 файлах
- `test_agent_files_exist` — все 3 файла существуют
- `test_frontmatter_parseable` — frontmatter корректно парсится

## Альтернативы

- **Оставить top-level `doom_loop` и добавить его в opencode `AgentV2.Info` схему** — отклонено: opencode — upstream проект, модификация его schema вне scope этого репо. Правильное решение — убрать невалидное поле, использовать валидное место (`permission:`).

- **Удалить `permission.doom_loop` тоже (раз top-level невалиден)** — отклонено: `permission.doom_loop` — валидное и полезное поле (permission guard против doom loops на permission layer). Top-level копия — ошибка, permission-вложенное — намеренный guard. Удалять guard нельзя.

- **Убрать ADR-019 ссылку, но оставить детальное объяснение запрета** — отклонено: детальное объяснение тоже self-referential hook (модель объясняет себе, почему нельзя, вместо того чтобы не делать). Лаконичное "Do NOT" + permission rules — достаточно.

- **Investigation Budget как hard guard в type system (tool timeout)** — отклонено: opencode tools не имеют per-agent step budget (только global `steps: 100` в frontmatter). Hard guard = steps: 100 (модель упрётся в лимит). Investigation Budget — soft guard, ранний сигнал "остановись до global лимита". Лучше soft + hard, чем только hard.

- **Параметризовать ~15 в конфиге (env var / frontmatter)** — отклонено: over-engineering для текущей задачи. ~15 — эвристика из эмпирики doom loop (88 steps = слишком много, 15 = достаточно для Setup + checklist). Если понадобится тюнинг — отдельный PR.