PII-хук считал одно и то же дважды — виноват не он, а как CLI пересылает историю

Cloud-модель в этой же сессии — Sonnet, тот же провайдер, тот же контур — не видит _finance/ и _contacts/ напрямую. Запрос гасится до того, как текст доходит до модели. Проверил только что: попросил прочитать файл с финансовым планом — получил отказ с точной причиной, не пустой файл.

Баг, с которого стоит начать: я решил, что хук для редактирования PII в исходящих сообщениях — простая штука, вызывается на каждое сообщение, к уже обработанному не возвращается. Замер сказал другое. Agent CLI пересылает всю историю заново на каждом ходу — значит хук без кэша пересканирует уже отредактированное сообщение с нуля. На втором ходу того же диалога, где новых PII было ровно ноль, число вызовов выросло на 8 к Presidio и на 2 к Privacy Filter — оба движка честно пересчитывали уже отредактированный текст заново. Добавил кэш с ключом — хэш оригинального текста.

Проверить фикс через две отдельные команды omp -p не вышло — каждый вызов CLI поднимает новый процесс с пустым кэшем, я сам не сразу это заметил и чуть не записал в пост число из смешанного замера (два разных session-id в одной директории — тихая улика). Настоящая проверка — вызвать обработчик хука дважды в одном процессе на одном и том же тексте: первый вызов — 4 обращения к Presidio и 1 к Privacy Filter (первое обнаружение), второй, тот же текст — ноль новых обращений к обоим движкам. Именно так это и работает в реальной интерактивной сессии — один процесс на весь диалог, а не серия одноразовых вызовов.

Два движка, не один. Presidio (self-hosted, аналайзер + анонимайзер, RU-модель) ловит имена, организации, локации уверенно. На русском тексте телефон и почту пропускает — контекстные словари у него английские по умолчанию. Второй движок — открытая модель Privacy Filter (OpenAI, Apache-2.0, вес не облачный) — те же телефон и почту в русском тексте ловит по контексту. Ни один из двух не покрывает оба класса в одиночку. Нужны оба параллельно.

Спросил себя: а нет ли готового инструмента получше. Нашёл PrivAiTe — self-hosted PII-прокси для LLM API, BSD-3, живая CI, без телеметрии (проверил исходники relay.py — прокси только на URL, который передаёт вызывающий, ничего захардкоженного). Дошёл до установки — и поймал себя на той же ошибке, что и часа за три до этого со своим же Privacy Filter: pip install молча тащит CUDA-сборку torch на 554 МБ вместо CPU-версии на 190. Второй раз за сессию, честно записываю.

Настоящая находка не в установке. У PrivAiTe preset по умолчанию (onnx) — это Presidio + тот же Privacy Filter. Два движка, что уже стоят здесь. Разница не в детекции — в обвязке: он сканирует PII внутри аргументов tool-call (мы — нет), восстанавливает значения в ответе по стабильному глоссарию на сессию (мы — нет), и есть отдельный gateway-режим для Claude Code/Codex. Опубликованный бенчмарк у него, кстати, не покрывает русский вообще — EN/FR/DE/IT.

Не поменял архитектуру целиком. Заворачивать весь дневной трафик в ещё один прокси — новая единая точка отказа перед всем стеком; ту же идею для встроенного LiteLLM-guardrail отклонил парой часов раньше по той же причине. Но две вещи оттуда честные и уже открытые: глоссарий на сессию и сканирование tool-call — следующий шаг, не сегодняшний.

Честная оговорка: это покрывает только сессии omp в этом vault. Прямой веб-интерфейс Claude, мобильное приложение, что угодно ещё — вне периметра. Вопрос retention/ZDR у провайдера — вопрос отдельный, наш редактор его не решает.

Читать по теме