Знакомьтесь, Jev: второе мнение, которое отвечает типами, а не прозой
Теперь Agent! спрашивает Jev (TypeSafe System One), насколько разрушительна команда шелла, прежде чем её выполнить. Рассказываем, почему Jev — советник, а не ещё один LLM-провайдер.
Сегодня в Agent! появляется новый уровень защиты: Jev, сокращение от TypeSafe System One. Перед запуском команды шелла Agent! теперь может задать Jev один вопрос: насколько вероятно, что эта команда безвозвратно уничтожит данные? Если оценка выше вашего порога, команда отклоняется.
Функция появилась в коммите 337c94a2, «Add Jev (TypeSafe System One) decision layer». В этой статье речь пойдёт о самом важном проектном решении: Jev — не ещё один LLM-провайдер.
Почему бы просто не спросить другую модель?
Agent! уже работает с 23 LLM-провайдерами. Самым простым путём было бы добавить «модель безопасности» как ещё одного провайдера и спрашивать её обычным языком, выглядит ли команда опасной. Проблема в том, что возвращается в ответ: проза. «Эта команда может быть рискованной в зависимости от…» — это не вердикт. В итоге вы разбираете предложения, чтобы решить, запускать ли rm.
Jev устроен иначе. Он отвечает на типизированные вопросы о состоянии, возвращая Choice, Score или Noul — типизированный ответ «ничего». Он не генерирует текст и не генерирует аргументы для инструментов. Сообщение коммита формулирует вывод так: Jev «консультирует существующий цикл инструментов, а не выступает в роли APIProvider».
В этом разделении и заключается вся архитектура:
- Выбранная вами модель действует. Она планирует, вызывает инструменты и пишет код.
- Jev оценивает. Он возвращает число, которое код может сравнить с порогом.
Где он находится
Jev ничего не заменяет. Порядок проверок для каждой команды шелла такой:
ShellSafetyService.check: жёстко заданные правила, отклоняющие катастрофические команды. Никаких моделей.- Jev: только для команд, уже прошедших шаг 1, как второе мнение о том, чего не видят шаблоны.
- Выполнение команды.
С первого дня проверка охватывает оба пути выполнения шелла в ShellTools (executeTCC и executeTCCStreaming).
Создан так, чтобы никогда не тормозить вашу задачу
Советник, способный заблокировать агента, по-своему опасен. Если сервис недоступен, зависнет ли ваша ночная задача? Здесь ответ — нет. JevAdvisor работает по принципу fail-open. Нет ключа, выключен переключатель или сервис лежит — это означает «нет мнения», и команда выполняется.
Однако fail-open безопасен, только если он заметен. В течение первого же дня два последующих коммита это обеспечили:
- «Make the advisor observable» (
e25384f3) добавил колбэк использования и отчёты об ошибках, так что неудачная проверка записывается в лог, а не выглядит молча как «безопасно». - «Log the verdict, not just that Jev answered» (
d3c47e67) заставил каждую проверку выводить фактический результат: процент риска разрушения и то, была ли команда разрешена или отклонена.
Клиент — полноценный пакет
Первая версия использовала самописный HTTP-клиент: POST /v1/systemone для вопросов, GET /v1/models для списка моделей, Bearer-аутентификацию и повторные попытки с задержкой при ответах 429 и 529. Уже через несколько часов 9e330357 заменил его пакетом TypeSafeKit, а f3a817b8 вендорил этот пакет прямо в репозиторий Agent, чтобы сборка никогда не зависела от его загрузки.
Настройка
Jev находится в настройках и имеет собственный API-ключ, который хранится в Keychain в отдельном слоте, не связанном с провайдерами. Там же есть выбор модели, загружаемый из /v1/models, и переключатель консультаций. Выключите переключатель — и Agent! будет вести себя в точности как раньше.
Почему это важно
Агенты повсюду получают доступ к шеллу, а стандартный ответ на вопрос о безопасности — «модель будет осторожна». Правила на основе шаблонов лучше, потому что их можно проверить. Но они знают только те шаблоны, которые кто-то записал. Типизированное второе мнение добавляет суждение, не добавляя неоднозначности: оно возвращает число, а решение принимает код.
Мы будем настраивать порог по умолчанию и следить за логами. Если Jev отклонит то, что не следовало, или пропустит то, что должен был поймать, откройте issue на GitHub и приложите строку из лога.