opencode-config/docs/decisions/026-pr-63-enforce-tool-usage-policy.md
Sergey a2666183f8
fix(agents+skills): enforce tool-usage policy across prompts and skills (#63)
* fix(agents): add tool usage policy to AGENTS.md

* fix(skills): replace raw bash with tools in 6 skills

* feat(skills): restore tunnel skill

* docs(handoff): scaffold handoff and ADR for PR

* docs(handoff): set PR number

* docs(project-map): add tunnel skill + tool usage policy note (PR#63)

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-07-25 18:46:16 +03:00

7.1 KiB
Raw Blame History

ADR-026: Enforce tool-usage policy across prompts and skills

Статус

Accepted (2026-07-25)

Контекст

Research (subagent explore) выявил 3 gap'а в tool-usage инфраструктуре репо slaid098/opencode-config:

Gap A: Skills предписывают raw bash, заблокированный deny-правилами

6 skill-файлов предписывали subagent'ам raw bash команды, которые глобально заблокированы в opencode.json:311-314 (4 deny-правила: git commit *, gh pr create *, gh pr merge *, gh issue create *):

  • run-pipeline/SKILL.md Template A: gh pr create, git commit
  • run-pipeline/SKILL.md Template B/D: git commit
  • issue/SKILL.md:83,127: gh issue create; :154: gh pr merge
  • spec/SKILL.md:311: gh issue create
  • release/SKILL.md:44, add-skill/SKILL.md:70, repo-init/SKILL.md:41: git commit

Симптом: subagent грузит skill → получает инструкцию нарушить permission layer → падает (deny блокирует bash) или импровизирует обход через gh api (см. memory technical/gh-issue-create-blocked-workaround.mdgh api repos/*/issues allow-rule существует, но использование его для создания issue обходит валидацию тулзы и ломает формат репо).

Gap B: Tool failure handling только для 2 из 10 tools

Секция "Tool failure handling" (инструкция "при сбое tool — STOP, не fallback на raw bash") была только для post_review (reviewer.md:285) и post_docs_review (docs-reviewer.md:228). Для commit, create_pr, create_issue, merge_pr, pipeline_status, spec_status, memory_setup, tunnel — отсутствует. При сбое agent без инструкции, импровизирует.

Gap C: 4 tool'а не упомянуты нигде как tool

  • create_issue, create_pr — 0 упоминаний как tool в промптах/skills (только raw gh issue create/gh pr create в skills, заблокированные deny)
  • tunnel — skill удалён в PR#42 (orchestration-switch), tool остался orphan (0 упоминаний как tool; пользователь использует вручную, но агент не "видит" его в контексте)
  • memory_setup — 1 строка в memory/SKILL.md:19, в промптах агентов нет

Дополнительно

gh pr comment* остался в allow-list reviewer/docs-reviewer (ADR-019 отклонил strict-deny "для обратной совместимости"). Решено: оставить в allow (промпт уже запрещает fallback при сбое tool, противоречие minimal).

Решение

1. AGENTS.md: единая Tool Usage Policy секция

В AGENTS.md добавлена секция ## Tool Usage Policy с таблицей всех 10 tools. Колонки: имя tool | raw-эквивалент (заблокирован deny) | когда использовать | при сбое — STOP, репорт, НЕ fallback. Правило: "Используй tool вместо raw bash. Raw bash-эквиваленты заблокированы deny (opencode.json:311-314). При сбое tool — STOP и репорт оркестратору, НЕ fallback на raw bash, НЕ импровизируй обход через gh api."

Это устраняет Gap B (единая policy для всех 10 tools вместо per-tool инструкций) и Gap C (4 tool'а теперь упомянуты как tool в едином месте).

Global copy /root/.config/opencode/AGENTS.md — bind-mount read-only из workspace AGENTS.md (compose: ./AGENTS.md:/root/.config/opencode/AGENTS.md:ro). В контейнере файл не редактируется; изменения идут в workspace copy, после merge + host git pull + container restart подхватываются автоматически. Это symlink-эквивалент через bind-mount.

2. 6 skills: raw bash → tool-вызовы

Все предписания raw bash в 6 skills заменены на tool-вызовы (см. handoff "Что сделано" для полного списка). git add и git push оставлены — они не заблокированы deny. Anti-instructions (упоминания "НЕ raw bash") сохранены как документация.

3. Skill tunnel восстановлен

Создан .opencode/skills/tunnel/SKILL.md (удалён в PR#42, tool остался orphan). Frontmatter: name: tunnel, description с триггерами "подними тоннель"/"пробрось порт"/"tunnel". Тело: инструкция вызывать tool tunnel() (1-й вызов — start, 2-й — stop, беспараметровый). Skill станет доступен после рестарта opencode (skills загружаются при старте).

4. gh pr comment* оставлен в allow-list

Не трогали — intentional (ADR-019). Промпт уже запрещает fallback при сбое tool (post_review/post_docs_review), противоречие minimal. strict-deny gh pr comment* отклонён (см. Альтернативы).

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

  • Per-agent промпты: добавить tool failure handling в каждый agent.md по отдельности — отклонено из-за дублирования. 4 agent-файла × 10 tools = 40 упоминаний, дрейф при добавлении новых tools. Единая policy в AGENTS.md (грузится всеми агентами) — canonical источник, добавление нового tool = 1 строка в таблице.

  • strict-deny gh pr comment* (принудить к post_review/post_docs_review) — отклонено (повторяет ADR-019). gh pr comment* остаётся в allow-list для обратной совместимости; tools используют spawnSync напрямую (не через bash permission layer), raw gh pr comment* не конфликтует. Промпт уже запрещает fallback при сбое tool — этого достаточно.

  • Упомянуть 4 orphan tool'а (Gap C) в per-agent промптах — отклонено по той же причине дублирования. Единая таблица в AGENTS.md покрывает все 10 tools одним источником правды.

  • Удалить skill tunnel совсем (tool orphan → удалить tool) — отклонено: tool tunnel используется пользователем вручную и в pipeline (tunnel.sh scaffold). Восстановление skill даёт агенту контекст для вызова tool'а по триггерам пользователя.