25. Одобрение действий — что ещё улучшить
🔵 Идеи и пробелы, накопленные по итогам реализации human-in-the-loop approval
(23-approvals.md). Не баги — направления развития. Отсортировано по ценности.
Высокий приоритет (прод-готовность)
1. Персистентность pending-одобрений
Сейчас ожидающее одобрение живёт в памяти (DashMap + oneshot). Рестарт сервера
теряет висящие запросы (сам ход тоже эфемерен, но очередь /approvals и
аудит могли бы переживать рестарт).
- Сохранять
PendingApprovalв DuckDB (approval_roles.dbили отдельную таблицуapproval_requests) при создании, удалять при решении/таймауте. - После рестарта — восстанавливать очередь для просмотра (сам заблокированный ход, разумеется, не оживает — только видимость и аудит).
2. Аудит всех решений при кворуме
Сейчас ApprovalResolved эмитится один раз с именем последнего апрувера.
При quorum: all / N теряются имена остальных одобривших.
- Эмитить
ApprovalResolvedна каждое решение (approve/deny) от каждого апрувера, а не одно агрегированное. Даёт полный след «кто и когда одобрил».
3. Условный гейт (по аргументам)
Сейчас тул гейтится всегда. Хочется «одобрять только если сумма > N»:
approval:
required: true
when: "{{ input.amount }} > 10000" # MiniJinja-условие по аргументам тула
Иначе мелкие операции заваливают оператора, и approvals «резинками» аппрувятся без смотрения.
4. Шаблон действия (action)
Текст карточки сейчас захардкожен: «Выполнить действие инструмента „X“?». Полезно дать автору тула свой, с контекстом аргументов:
approval:
action: "Списать {{ input.amount }} ₽ со счёта {{ input.account }}"
Оператор тогда сразу видит суть, не разворачивая details.
5. Напоминание / эскалация по таймауту
Таймаут (600с) молча режет запрос на deny. Полезно:
- перед таймаутом напомнить апруверу («ждёт 5 минут»);
- по истечении уведомить инициатора и/или эскалировать другой роли.
Средний приоритет
6. VK как канал оператора
operator_notify уже умеет telegram / webhook / yandex_messenger.
Не хватает vk (текстовый «да/нет» + поллер, по аналогии с Яндексом) и, при
желании, email.
7. «Диал» по severity
Автоодобрение low-риска, гейт только для high (или обратный порог). Сейчас
политика бинарна. Добавить min_severity: high — гейтить только >= порога.
8. Сид ролей из конфига (IaC)
Роли создаются только через API. Для деплоя «как код» удобно сидить роли из
config/approval_roles.yaml при старте (upsert), а API оставить для оперативных
правок (наняли/уволили).
9. Участники роли по идентичности, а не по каналу
Сейчас член роли — канальный id (ym:login, tg:id). Полезно разрешить
user:<user_id> (канонический реестр пользователей) или email — тогда «нанять
сотрудника» не привязано к конкретному мессенджеру.
10. Дедупликация / идемпотентность
Если агент в одном ходу повторно просит то же одобрение — сейчас создаётся
второй запрос. Можно дедупить по (tool, hash(arguments)) в рамках сессии.
Низкий приоритет (UX / техдолг)
11. История решений в UI
/approvals показывает только pending. Добавить вкладку «история» (из
ApprovalResolved в events.db): кто/когда/что/решение.
12. Визуал «отклонено» в виджете
Tool-карточка при остановке помечается «Остановлено», при deny — просто
«Действие отклонено» текстом. Можно дать карточке явное состояние denied.
13. Отдельный бот-токен для оператора
Сейчас операторский канал делит бот-токен с end-user каналом агента (drom) —
из-за этого drom-бот пришлось отключить. Правильно: оператору — свой бот
(отдельный токен), чтобы каналы не конфликтовали.
14. Параметризованные запросы в approval_roles
Ролевой стор использует строковую интерполяцию с экранированием (sql_quote).
При рефакторинге — перейти на параметризованные запросы DuckDB для строгости.
15. Аудит паттерна emit в других блокирующих примитивах
Баг «событие шло только подписчикам канала, но не глобальным листенерам» найден
в approvals. request_user_input (вложения/oauth) использует тот же txb.send —
стоит проверить, не нужно ли там тоже глобальное уведомление (событийный аудит).
Заметки о демо/продакшене
- Роли (
logs/approval_roles.db) не синкаются через git — на каждом сервере свои. Для multi-instance это отдельная задача (общий стор / репликация). - Локальный и продовый сервер, поллящие один
YANDEX_MESSENGER_BOT_TOKEN, конфликтуют — отсюда и пункт 13.