chore(opencode): эксперимент с permission-reviewer — итоги и отключение
Плагин настроен и проверен (jev через Zen + прокси, enforce+deny), но признан не стоящим использования: хрупкая цепочка, нервные эскалации, запертая сессия. Запись в opencode.json отключена (закомментирована), выводы — в docs/research/.
This commit is contained in:
1 parent
9f6db963be
commit
16e85139b9
4 files changed
+165
-11
No files matched your search
@@ -0,0 +1,75 @@
|
|||||||
|
# Эксперимент: ИИ-ревьюер разрешений в OpenCode (autoapprove)
|
||||||
|
|
||||||
|
**Дата:** 2026-10-11
|
||||||
|
**Статус:** эксперимент закрыт, плагин снят. Документ сохранён как описание
|
||||||
|
системы и причин отказа — если идея вернётся, начинать отсюда.
|
||||||
|
|
||||||
|
## Итог одним абзацем
|
||||||
|
|
||||||
|
Плагин `opencode-permission-reviewer` (LLM-ревьюер каждого `ask`) настроен и
|
||||||
|
работоспособен, но признан не стоящим использования: хрупкая цепочка (ключ в
|
||||||
|
окружении сервиса → региональный 451 на endpoint jev → прокси), плюс ревьюер
|
||||||
|
нервно эскалует всё подряд в сессиях с «опасным» транскриптом. Боевой режим:
|
||||||
|
`permission: autoaccept` + deny-паттерны на секреты + snapshots/git как
|
||||||
|
обратимость. AFK для системного администрирования не достигнут: узкое место —
|
||||||
|
не разрешения, а компетентность модели-исполнителя (glm flash провалил простые
|
||||||
|
админские задачи: «делал странное с умным видом»).
|
||||||
|
|
||||||
|
## Как была устроена система
|
||||||
|
|
||||||
|
Цепочка решений для каждого `ask` из `opencode.json`:
|
||||||
|
|
||||||
|
1. **Deny-паттерны** (`permission.bash`) — мгновенный отказ по строке команды,
|
||||||
|
до моделей. Только секреты: `*.env*`, `*.secrets*`, `~/.ssh/*` и т.п.
|
||||||
|
Паттерны слепы к содержимому скриптов — защита секретов, не рубеж.
|
||||||
|
2. **Jev** (`opencode/jev-1.13`, System One) — типизированный ревьюер: команда,
|
||||||
|
свидетельства из транскрипта, содержимое локальных скриптов (read-only
|
||||||
|
enrichment). Отвечает allow / deny / escalate.
|
||||||
|
3. **Второе мнение** (`opencode-go/glm-5.3-flash`) — трудные случаи, где jev
|
||||||
|
неуверен.
|
||||||
|
4. **Финал** — `escalationMode`: `manual` (спросить человека; при
|
||||||
|
`permission=autoaccept` на него отвечает auto) или `deny` (отказ с причиной).
|
||||||
|
`enforcementMode`: `observe` (только аудит, дефолт) или `enforce`.
|
||||||
|
|
||||||
|
## Инфраструктурные грабли (главная ценность эксперимента)
|
||||||
|
|
||||||
|
- Jev ходит на отдельный endpoint Zen `/zen/v1/systemone`. Zen закрывает его
|
||||||
|
по региону (HTTP 451) — нужен прокси в окружении сервиса
|
||||||
|
(`HTTPS_PROXY` в `~/.env.secrets`). Чат-модели Zen
|
||||||
|
(`/zen/v1/chat/completions`) доступны без прокси; блокирован только
|
||||||
|
systemone. Jev нет в общем каталоге моделей — это намеренно (не чат-модель),
|
||||||
|
на плагин не влияет.
|
||||||
|
- `OPENCODE_API_KEY` должен быть в окружении **процесса сервиса**. Сервис
|
||||||
|
наследует окружение шелла, из которого запущен; после правки
|
||||||
|
`~/.env.secrets` нужны `source` + рестарт из свежего шелла. Рестарт из
|
||||||
|
старого шелла ключ не подхватывает.
|
||||||
|
- Ревьюер зовётся из серверного процесса: рестарт сервиса из сессии агента
|
||||||
|
рвёт саму сессию.
|
||||||
|
- Emergency brake: команды, пахнущие выносом креденшелов
|
||||||
|
(`curl -H "Authorization: ... $KEY"`), режутся детерминированно.
|
||||||
|
- Конфиг плагина читается с O_NOFOLLOW: симлинк молча игнорируется, деплой
|
||||||
|
только копией (см. `.stow-local-ignore`).
|
||||||
|
|
||||||
|
## Известные свойства поведения
|
||||||
|
|
||||||
|
- Сессия, полная опасных команд в транскрипте, выглядит для ревьюера
|
||||||
|
подозрительно: jev эскалует всё подряд, и с `escalationMode: deny` такая
|
||||||
|
сессия становится неработоспособной — fail-closed по замыслу.
|
||||||
|
- В этой сессии это проявилось полностью: заперт даже `git status`, отменить
|
||||||
|
отказы изнутри сессии нельзя (явное разрешение в чате не помогло).
|
||||||
|
- Пороги тонкой настройки (не крутились): `systemOneConfidenceThreshold`
|
||||||
|
(дефолт 0.4), `systemOneReasoningThreshold` (дефолт 0.38),
|
||||||
|
`confidenceThreshold` (чат-ревьюер, 0.7).
|
||||||
|
- Установлено про авто-подтверждение без плагина: ручное approve при серии
|
||||||
|
запросов вырождается в слепое «да» — честный auto + обратимость лучше
|
||||||
|
декоративного ручного.
|
||||||
|
|
||||||
|
## Ссылки
|
||||||
|
|
||||||
|
- Плагин и все опции: https://github.com/warc0s/opencode-permission-reviewer
|
||||||
|
(README: https://npm.io/package/opencode-permission-reviewer)
|
||||||
|
- Модель разрешений OpenCode V2: https://opencode.ai/v2/docs/permissions
|
||||||
|
- Jev: https://jevmodel.org/ (прямой API TypeSafe с ключом `TYPESAFE_API_KEY` —
|
||||||
|
запасной маршрут мимо Zen)
|
||||||
|
- Аудит решений (на машине): `~/.local/share/opencode/permission-reviewer-audit.jsonl`
|
||||||
|
- Снятый конфиг сохранён закомментированным в `opencode/.config/opencode/opencode.json`
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
"$schema": "https://opencode.ai/config.json",
|
||||||
"plugins": [
|
// Плагин opencode-permission-reviewer снят (эксперимент 2026-10-11 признан
|
||||||
{
|
// неудачным — см. docs/research/). Блок сохранён для возврата:
|
||||||
"package": "opencode-permission-reviewer@^2.4.0",
|
// "plugins": [
|
||||||
"options": {}
|
// {
|
||||||
}
|
// "package": "opencode-permission-reviewer@^2.4.0",
|
||||||
],
|
// "options": {}
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
"default_agent": "build",
|
"default_agent": "build",
|
||||||
"permission": {
|
"permission": {
|
||||||
"write": "ask",
|
"write": "ask",
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
{
|
{
|
||||||
// Модель-ревьюер разрешений. Jev (System One) через OpenCode Zen:
|
// Как устроена вся система автоподтверждения — см. permission-reviewer.md рядом.
|
||||||
// ключ OPENCODE_API_KEY читается из окружения процесса opencode (~/.env.secrets).
|
// Модель-ревьюер разрешений: Jev (System One) через OpenCode Zen.
|
||||||
// Платный вариант без временного окна: opencode/jev-1.13
|
// Zen закрывает /zen/v1/systemone по региону (451) — нужен прокси
|
||||||
"model": "opencode/jev-1.13-free",
|
// (HTTPS_PROXY в ~/.env.secrets, сейчас hproxy.local:12334).
|
||||||
// Ниже порога уверенности решение уходит человеку
|
// Ключ OPENCODE_API_KEY читается из окружения процесса opencode.
|
||||||
|
"model": "opencode/jev-1.13",
|
||||||
|
// Ниже порога уверенности решение уходит дальше по цепочке
|
||||||
"confidenceThreshold": 0.7,
|
"confidenceThreshold": 0.7,
|
||||||
|
// enforce: политика рисков применяется (дефолт observe — только аудит)
|
||||||
|
"enforcementMode": "enforce",
|
||||||
|
// deny: финальная эскалация = отказ, а не запрос человеку
|
||||||
|
// (при permission=auto на запрос человека всё равно отвечает auto)
|
||||||
|
"escalationMode": "deny",
|
||||||
// Сложные/неуверенные решения Jev получают второе мнение чат-модели
|
// Сложные/неуверенные решения Jev получают второе мнение чат-модели
|
||||||
// вместо эскалации к человеку. glm-5.3-flash из подписки opencode-go:
|
// вместо эскалации к человеку. glm-5.3-flash из подписки opencode-go:
|
||||||
// лимиты в 4 раза выше, чем у deepseek-v4.1-flash, интеллект сравним.
|
// лимиты в 4 раза выше, чем у deepseek-v4.1-flash, интеллект сравним.
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
# Система автоподтверждения разрешений (autoapprove)
|
||||||
|
|
||||||
|
Как устроено подтверждение команд в OpenCode на этой машине, и где что искать.
|
||||||
|
|
||||||
|
## Цепочка решений
|
||||||
|
|
||||||
|
Каждый `ask` из `opencode.json` проходит слои сверху вниз:
|
||||||
|
|
||||||
|
1. **Deny-паттерны** (`permission.bash` в `opencode.json`) — мгновенный отказ по
|
||||||
|
строке команды, до всяких моделей. Только секреты: `*.env*`, `*.secrets*`,
|
||||||
|
`~/.ssh/*` и т.п. Паттерны слепы к содержимому скриптов — это не рубеж, а
|
||||||
|
только защита секретов.
|
||||||
|
2. **Jev** (`opencode/jev-1.13`, System One) — типизированный ревьюер: получает
|
||||||
|
команду, свидетельства из транскрипта и содержимое локальных скриптов
|
||||||
|
(read-only enrichment), отвечает allow / deny / escalate.
|
||||||
|
3. **Второе мнение** (`opencode-go/glm-5.3-flash`) — разбирает трудные случаи,
|
||||||
|
где jev неуверен, но есть шанс на решение.
|
||||||
|
4. **Финал** — `escalationMode: deny`: неразрешённое отклоняется с причиной
|
||||||
|
агенту. Человек в цепочке не участвует: при `permission=auto` запрос к
|
||||||
|
человеку всё равно решил бы auto.
|
||||||
|
|
||||||
|
Ключевые настройки в `permission-reviewer.jsonc` рядом с этим файлом:
|
||||||
|
`enforcementMode: enforce` (применять политику рисков; дефолт observe — только
|
||||||
|
аудит), `escalationMode: deny` (дефолт manual — спросить человека).
|
||||||
|
|
||||||
|
## Инфраструктура
|
||||||
|
|
||||||
|
- Jev ходит на OpenCode Zen endpoint `/zen/v1/systemone`. Zen закрывает его
|
||||||
|
по региону (HTTP 451), поэтому сервису нужен прокси: `HTTPS_PROXY` в
|
||||||
|
`~/.env.secrets` (сейчас `hproxy.local:12334`). Чат-модели Zen
|
||||||
|
(`/zen/v1/chat/completions`) доступны без прокси — блокирован только
|
||||||
|
systemone.
|
||||||
|
- Ключ `OPENCODE_API_KEY` должен быть в окружении **процесса сервиса**:
|
||||||
|
`~/.env.secrets` сорсится из `~/.bash_custom`, сервис наследует окружение
|
||||||
|
шелла, из которого его запустили. После правки `.env.secrets`:
|
||||||
|
`source ~/.env.secrets && opencode service restart` — рестарт из старого
|
||||||
|
шелла ключ не подхватит.
|
||||||
|
- Jev нет в общем каталоге моделей OpenCode — это намеренно (не чат-модель),
|
||||||
|
на плагин не влияет: он зовёт systemone endpoint напрямую.
|
||||||
|
|
||||||
|
## Аудит и отладка
|
||||||
|
|
||||||
|
- Журнал решений: `~/.local/share/opencode/permission-reviewer-audit.jsonl` —
|
||||||
|
одна JSON-строка на ревью: вердикт, уверенность, причина, кто решал
|
||||||
|
(`decisionSource: system-one-reviewer / llm-reviewer / failure-safe`).
|
||||||
|
- Поле `reviewerEscalatedFrom` показывает, что первичный ревьюер (jev)
|
||||||
|
сработал, но эскалировал.
|
||||||
|
- `failure-safe` в `reason` = ревьюер недоступен (нет ключа, 451, таймаут) —
|
||||||
|
решение ушло в эскалацию.
|
||||||
|
- Emergency brake: команды, пахнущие выносом креденшелов наружу
|
||||||
|
(`curl -H "Authorization: ..."`), режутся детерминированно, мимо моделей.
|
||||||
|
|
||||||
|
## Известные свойства
|
||||||
|
|
||||||
|
- Сессия, полная опасных команд в транскрипте, выглядит для ревьюера
|
||||||
|
подозрительно: jev начинает эскаловать всё подряд, и с `deny` такая сессия
|
||||||
|
становится неработоспособной. Это fail-closed по замыслу. Лечение — работать
|
||||||
|
в чистой сессии.
|
||||||
|
- Тонкие пороги: `systemOneConfidenceThreshold` (дефолт 0.4) — насколько
|
||||||
|
уверенным должен быть jev, чтобы решать самому;
|
||||||
|
`systemOneReasoningThreshold` (дефолт 0.38) — сколько эскалаций уходит
|
||||||
|
на второе мнение.
|
||||||
|
|
||||||
|
## Документация
|
||||||
|
|
||||||
|
- Плагин и все опции: https://github.com/warc0s/opencode-permission-reviewer
|
||||||
|
(README на https://npm.io/package/opencode-permission-reviewer)
|
||||||
|
- Модель разрешений OpenCode V2: https://opencode.ai/v2/docs/permissions
|
||||||
|
- Jev у TypeSafe AI: https://jevmodel.org/ (есть прямой API с отдельным ключом
|
||||||
|
`TYPESAFE_API_KEY` — запасной маршрут, если Zen закроет systemone совсем)
|
||||||
Reference in new issue
Block a user