Как из правила в промпте сделать механизм: маскировать то, что уходит в модель, и писать строку в журнал. Ниже — проверенный контракт и решения, которые принимает владелец, а не агент.
Плагин регистрирует middleware из register(ctx); вызовы применяются в рантайме, а не в промпте:
| Точка | Что получает | Что может | Где применяется |
|---|---|---|---|
tool_request |
tool_name, args, original_args |
заменить аргументы инструмента или отказать до выполнения | до проверок доступности, guardrails и approval |
llm_request |
request (полные kwargs провайдера, включая messages/input), original_request |
заменить запрос целиком; исключение прерывает вызов | agent/turn_api_request.py → apply_llm_request_middleware |
llm_execution |
request, next_call |
обернуть или заменить сам вызов провайдера | вокруг исполнения |
Порядок на каждый запрос: собрать kwargs → llm_request → наблюдатели pre_api_request → llm_execution → post_api_request. Регистрация: ctx.register_middleware("llm_request", fn) в hermes_cli/plugins.py.
Практический вывод: hooks/ — это наблюдатели, они ничего не меняют. Пустой hooks/ не означает «принуждения быть не может»; смотреть надо на зарегистрированные middleware (включённые плагины в config.yaml).
| Точка | Задача |
|---|---|
tool_request на read_file, DOCX/PDF-парсер, OCR |
определить тип документа (имя файла, титул, штамп), сверить с ролью пользователя, поставить метку категории в сессии; при запрете — отказ до чтения, содержимое в модель не попадает |
llm_request |
замаскировать по правилам категории из метки и записать строку в журнал |
llm_execution |
вернуть реальные значения в ответе — иначе модель отвечает про [РОЛ_01] и ответ бесполезен |
Шаблоны ПДн дают ложные срабатывания на техническом тексте: номера СП и ГОСТ, ИНН поставщика в спецификации, кадастровые в выписке. Поэтому решение принимается не по содержимому, а по типу документа (он известен до чтения) + роли пользователя. Шаблоны остаются подсказкой для автоклассификации; при сомнении тип подтверждает человек одним вопросом, ответ живёт в метке сессии — иначе одно и то же спрашивают десять раз за день и отвечают не глядя.
Политика — отдельный файл в профиле, правится без пересборки:
| Категория | Кому | Что входит | Режим |
|---|---|---|---|
| 🟢 зелёный | всем | открытые нормативы, методики, обезличенные тексты | без маски |
| 🟡 жёлтый | всем | ТЗ, реестры замечаний, переписка, локальные сметы, ВОР | маска + журнал |
| 🔴 красный | только выделенная роль (отдельная учётка) | договоры, сводные сметы, акты, ИД, кадры, финансы | остальным — отказ с подсказкой, к кому идти |
Два ограничения, которые нельзя размывать:
Маскировать красное и отправлять нельзя: у договора маска убивает предмет — стороны, суммы, особые условия. Единственный корректный ответ для красного — не пускать.
Роли берутся из таблицы учёток организации (той же, где логины и лимиты), а policy.yaml генерируется из неё скриптом. Руками политику не править: два источника правды разъедутся на первой же кадровой замене, и узнается об этом на инциденте.
engineer / contracts / admin), а не перечисление задач. «инженер ОВиК, ТЗ, ТУ, СТУ, проекты» не парсится и у каждого записано по-своему; человекочитаемое описание держать отдельной колонкой рядом с кодом.root политику не обходит. Учётка со статусом root проходит те же проверки; отличие — только пометка admin в журнале. Иначе достаточно попросить root, и контроль отменён.tool_request + политика + журнал в режиме «только запись»: ничего не маскируем, смотрим реальный поток и настраиваем правила.llm_execution).Обратный порядок (сразу маска без периода наблюдения) даёт ложные маски на нормальном техническом тексте, и работу уносят на личный телефон — ровно то, от чего защищались.
vision_analyze) и вложения не маскируются.