* fix(agents): remove invalid top-level doom_loop field from frontmatter * fix(reviewer): simplify setup step 5 and add investigation budget * test(agents): add frontmatter validation tests * docs(handoff): add handoff and ADR-020 for doom_loop frontmatter fix * docs(handoff): set PR number * docs: update project map --------- Co-authored-by: opencode-agent <agent@slaid098.dev>
71 lines
No EOL
6.9 KiB
Markdown
71 lines
No EOL
6.9 KiB
Markdown
# 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. |