# 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: })` tool (primary) - `gh run list --branch --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.