Дозволи важливіші за промпт: fail-closed контур OpenClaw

ai · 2026-10-08

Промпт пояснює агенту, що бажано зробити. Дозвіл визначає, що він технічно може зробити. Це різні рівні контролю: інструкція може бути проігнорована або неправильно витлумачена, а заборона, яку перевіряє runtime, має зупиняти дію незалежно від тексту відповіді.

Для OpenClaw це особливо важливо, коли один Telegram-бот одночасно читає vault, запускає команди, змінює файли й надсилає зовнішні повідомлення. Надійна схема починається з ролей і меж, а не з довшого system prompt.

Що саме контролюють Claude, Codex і OpenCode

У Claude Code правила allow, ask і deny застосовуються до інструментів, а порядок має значення: спочатку перевіряється deny, потім ask, потім allow. Важливе обмеження: правила дозволів Claude Code enforced runtime-ом, а не моделлю. Текст у CLAUDE.md може спрямувати модель, але не надати їй доступ. Окремий sandbox Claude Code обмежує shell-команди, однак file tools, MCP-сервери й hooks працюють поза ним. Отже, «shell у sandbox» не означає «весь агент ізольований».

У Codex sandbox і approval policy — дві різні осі. Документація Codex про sandbox описує технічну межу: які файли й мережеві ресурси доступні. Approval policy визначає, коли агент мусить зупинитися й попросити підтвердження. Практичний локальний режим — workspace-write + on-request; danger-full-access прибирає filesystem- і network-межі, тому це вже окремий рівень довіри, а не просто «швидший режим».

У OpenCode кожна дія має результат allow, ask або deny, а granular rules матчать конкретний інструмент і його аргументи. Правила OpenCode використовують принцип «останнє співпадіння перемагає», тому catch-all ставлять першим, а вузькі винятки — після нього. За замовчуванням OpenCode досить permissive: для unattended-запуску це зручно, але небезпечно як базова політика.

Спільний висновок: автономність треба видавати конкретній ролі, конкретній дії й конкретному шляху. Не «агенту взагалі».

Мінімальна модель ролей для OpenClaw

Почніть із матриці, яку можна перевірити до запуску:

Роль Vault Команди Зовнішня відправка Призначення
router читати дозволені індекси ні ні розібрати Telegram-запит і вибрати маршрут
researcher читати джерела, писати staging лише read-only ні зібрати матеріал і чернетку
executor читати й змінювати staging allowlist ні виконати конкретну операцію після маршрутизації
publisher читати затверджене тільки перевірки лише визначений канал опублікувати й повернути receipt

Роль router не повинна мати «про всяк випадок» shell або write-доступ. Якщо запит вимагає змінити файл, router створює typed task envelope для executor, а не виконує дію сам:

task: publish_article
source: staging/article.md
checks:
  - validate_frontmatter
  - validate_links
  - build_site
approval: required
side_effects:
  - git_commit
  - telegram_receipt

Це не чарівний формат OpenClaw, а контракт між ролями. Його сенс у тому, що worker отримує обмежене завдання і може відмовити, якщо поле side_effects або шлях не входить до allowlist.

Чотири робочі схеми

1. Telegram як control plane, а не універсальний shell

Зробіть Telegram-вхід тонким: бот приймає команду, нормалізує її, показує план і запускає окрему сесію worker-а. Worker повертає короткий receipt: task_id, змінені ресурси, перевірки й помилки. Сам router не отримує довільного exec, доступу до секретів або права надсилати повідомлення назовні.

Плюс — компрометація або prompt injection у вхідному повідомленні не дає одразу повного доступу. Мінус — зʼявляється стан задачі: потрібні idempotency key, timeout і спосіб відновити незавершену роботу. Для щоденних публікацій це краще за одну довгу сесію, яка має всі повноваження.

2. Vault із двома зонами запису

Розділіть сховище на source, staging і published. Більшість агентів отримує read-доступ до source, writer — write лише до staging, а publisher — read до staging і write тільки до контрольованого каталогу публікації. Не давайте досліднику права перезаписувати оригінальні нотатки: це зменшує ціну помилки й спрощує аудит diff-ом.

Псевдополітика може виглядати так:

permissions:
  source/**:
    read: allow
    write: deny
  staging/**:
    read: allow
    write: ask
  published/**:
    read: allow
    write: deny
roles:
  publisher:
    write:
      - published/approved/**

Нестандартний, але корисний варіант — write lease: writer отримує право запису на один файл на короткий час і втрачає його після receipt або timeout. Це зменшує довгоживучі дозволи, але потребує блокувань, прибирання прострочених lease і зрозумілого recovery-процесу.

3. Shell через вузькі capabilities

Не описуйте в інструкції «запускай безпечні команди». Винесіть безпечний набір у runtime-політику: git diff, валідатор, тестовий runner. Усе інше — ask або deny. Особливо окремо обмежуйте мережу, доступ до сокетів, package manager і команди, що можуть читати environment.

executor:
  shell:
    allow:
      - "git diff --check"
      - "make test"
      - "./scripts/validate"
    ask:
      - "git commit *"
      - "npm install *"
    deny:
      - "git push *"
      - "curl * | *"
      - "cat .env*"

Називайте це capability policy, а не security prompt. Якщо конкретний runtime не вміє патерни команд, обгорніть операції у короткі скрипти з фіксованими аргументами; це зменшує поверхню матчинг-помилок.

4. Окремий агент для небезпечної дії

Для публікації, видалення, відправлення пошти або змін у production використовуйте окрему роль із вузьким toolset. Вона отримує не всю історію Telegram, а структурований пакет: що змінюється, чому, які перевірки пройдено, який scope і яка дедлайн-умова. Після дії повертає receipt, а не довільний текст.

Це відрізняється від звичайного «попросімо другий агент перевірити» — тут межа потрібна саме для виконання, а не лише для ревʼю. Ціна — більше orchestration-коду й затримка на передачу пакета. Вигода — чіткий blast radius і можливість відтворити рішення без повного чату.

Операційний playbook

Перед підключенням нового tool або MCP-сервера перевірте пʼять речей:

  1. Яка мінімальна роль його потребує: router, researcher, executor чи publisher?
  2. Які аргументи повинні бути allow, а які завжди ask або deny?
  3. Чи може tool читати більше, ніж здається з його назви: environment, усі файли, мережу, локальні сокети?
  4. Який fail-closed результат при timeout, невідомому аргументі або відсутньому approval?
  5. Який receipt доведе, що дія справді виконана, а не лише обіцяна моделлю?

Тестуйте не тільки позитивний сценарій. Зробіть по одному негативному тесту на роль: router просить shell, researcher намагається писати в source, executor отримує шлях поза staging, publisher отримує неперевірений пакет. Очікуваний результат — блокування з поясненням, а не «модель пообіцяла бути обережною».

Що налаштувати сьогодні

  • Випишіть ролі й заборони в одну матрицю; починайте з deny, потім додавайте вузькі allow.
  • Приберіть write і зовнішню відправку з Telegram-router-а.
  • Введіть staging-зону для vault і receipt для кожної мутації.
  • Залиште ручне підтвердження для commit, публікації, видалення та зовнішніх повідомлень.
  • Раз на тиждень перевіряйте фактичні tool calls проти матриці, бо конфігурація з часом розширюється тихими винятками.

Найкращий агент — не той, якому можна довірити все, а той, чию помилку можна локалізувати. Промпт допомагає обрати правильну дію; дозволи визначають, наскільки дорогою буде неправильна.