Опубликовано
Профиль на проект: как перестать кричать на агента, что он опять натворил
Автор: Гусев Николай [портфолио]
Обновлено

Сложность: средняя
Ситуация, знакомая каждому, кто работает с агентом не первый месяц. Ты ставишь задачу, отходишь, возвращаешься, а агент за это время переименовал каталог, поменял настройки и применил не тот способ доступа. Начинаешь разбираться и понимаешь: он не ошибся. Он работал не с тем. Креды, модель, скиллы, привычки доступа - всё это были чужие, потому что у проекта не было ни своего профиля, ни своего проекта внутри него. Был один агент на всё, а значит, ни одно из решений не было его собственный.
Тезис
Один проект - один профиль агента. Всё, чего в этом профиле нет, для этого проекта чужое.
Не "полезно бы иметь профиль", не "когда много проектов". А именно так: есть проект, есть отдельный профиль, и переключение между проектами - это переключение профилей, а не смена промпта в одном и том же агенте.
Главный риск - контаминация
Вот ради чего всё затевается. Не "удобнее", не "чище". Риск один, и он тихий.
Когда один профиль ведёт двадцать с лишним проектов, агент не знает, что вот для этого конкретного проекта нужны вот эти конкретные инструменты, вот эти ключи и вот этот способ доступа. У него нет правила выбора. Он берёт то, что осталось в контексте от предыдущей работы, то, что попалось первым в поиске, то, что он делал пять минут назад. Не потому что считает это правильным, а потому что выбирать не из чего.
Дальше начинается то, что выглядит как ошибка агента, но на самом деле ошибка архитектуры.
Способ доступа. Проект требует идти напрямую, а агент идёт через SOCKS5-прокси, потому что вчера через него ходил и получилось. Что он видит на выходе: обрывы TLS, несуществующие дефекты, завышенное время отклика. Я на этом ловил аудит внешнего сайта: пять из шести запросов "умирали", задержка первого байта 4131 мс, и всё это выглядело как блокер хостинга. Напрямую тот же сайт отдавал 20 запросов из 20 с кодом 200 и задержкой 0.16-0.25 с. В отчёте это потом стояло как вывод о нерабочем сайте, и вывод был ложным. Прокси не был дефектом. Прокси был привычкой из соседней задачи.
Сетевой контур. Проекту ходить без VPN, агент поднимает туннель - или наоборот, проект без VPN, а агент пускает всё через внешнюю ноду. Идёт, получает чужие ответы, другой IP в логах, другая картина, и выводы по позициям в поиске делаются не по тому региону, о котором ты спрашивал.
Версии софта. Удалённый хост на Python 3.8, локальный на 3.11. Агент пишет код под тот, что на локальной машине, и на целевом хосте получает синтаксические ошибки там, где не должно быть ни одного. Или наоборот: команда, которая на старой версии есть, на новой уже переименована. Проверять это каждый раз вручную - работа, которой не существует, если у проекта свой профиль с зафиксированным окружением.
Способы управления. Один проект разворачивается через systemd, другой - через контейнеры, третий - ansible. Список команд в голове агента один, и он в общем профиле смешан. На проекте, где нужен один способ, он применяет другой - и получает отказ там, где всё должно было пройти.
Креды и доступы. Набор ключей в .env у профиля один. Проект требует корпоративный доступ, агент идёт с тем, что нашлось, или с тем, что работало вчера. Плюс ко всему чужой набор ключей, лежащий рядом, - это ещё и лишняя поверхность, которую вы держите на своей машине без надобности.
Обобщение простое: состояние агента, выработанное на одном проекте, применяется к другому без спроса. И чем больше проектов живёт в одном профиле, тем выше вероятность, что перенос состояния произойдёт. Переключение профиля это отсекает жёстко: там, где в профиле нет ни ключа, ни скилла, ни привычки, нет и соблазна что-то взять не оттуда.
Как это выглядит на практике
Изоляция двухуровневая. Профиль отвечает за роль и креды, проект внутри него - за содержание.
Схема выглядит так:
профиль (роль)
├── общие скиллы и инструменты
├── свои креды, своя модель
└── проекты
├── проект А
│ ├── рабочая папка
│ ├── HERMES.md: идея + правила
│ ├── свои наработки как сущности в базе знаний
│ └── .agents/skills: скиллы этого проекта
├── проект Б
│ ├── своя рабочая папка
│ ├── свой HERMES.md
│ ├── свои наработки в базе знаний
│ └── .agents/skills
└── проект В
└── ...
Скиллы живут на двух уровнях сразу, и это не конфликт, а разделение труда. На уровне профиля лежат скиллы роли, ими пользуется любой проект внутри него. На уровне проекта лежат скиллы, которые нужны только этому проекту, и они перекрывают одноимённые скиллы профиля.
Два каталога, оба поддерживаются: .hermes/skills и .agents/skills.
Второй используют чаще, потому что он совпадает с конвенцией других агентов.
Проект в Hermes - это именованная рабочая область, которая может состоять из нескольких папок. Внутри профиля их можно заводить сколько угодно, и у каждой свой активный статус:
hermes project create "Название" C:\Work\projects\first --use
hermes project use "Название" # переключиться на другой проект
hermes project add-folder "Название" C:\Work\projects\second
hermes project list
То есть профиль программиста может вести несколько проектов разработки, и каждый получит свою папку, свой файл идей, свои наработки и свои скиллы, а инструменты роли у них останутся общие.
Про скиллы отдельно, потому что это место чаще всего понимают неправильно. Скилл не привязан к профилю жёстко. Он лежит в каталоге, и каталог может быть любым из трёх:
.hermes/skillsили.agents/skillsвнутри папки проекта. Такой скилл достаётся только этому проекту и перекрывает одноимённый скилл профиля. В индексе он помечен как проектный, и это видно глазами.skillsпрофиля. Доступен всем проектам внутри профиля, то есть всей роли.- Внешний каталог, прописанный в конфиге. Общий для нескольких профилей, физически один файл.
Итого три уровня, а не два: профиль, проект, и общий каталог поверх них. Практический вывод тот же, что был, только точнее: скилл, который нужен роли целиком, кладём в профиль, скилл, который нужен одному проекту, кладём в проект и получаем ровно то, что нужно, без копирования по двадцати папкам.
Одна деталь, на которой спотыкаются. Скиллы проекта не читаются, пока проекту не выдан статус доверенного. Команда hermes skills trust работает на уровне репозитория, и решение принимается один раз, а не на каждый запуск. Это сделано как защита от подмены: если бы репозиторий можно было доверять молча, то обычный git pull с чужой ветки принёс бы агенту скилл, который выполнит что угодно. Поэтому проверка есть, и снимать её не надо.
Барьеров тут два, и это стоит знать, потому что скилл может не подхватиться по любой из двух причин, и они выглядят одинаково. Первый это доверие, про которое я только что сказал. Второй это корень проекта: скиллы ищутся от корня репозитория, то есть от ближайшего каталога с .git вверх по дереву. Если папка проекта лежит вне репозитория, корня нет и уровень просто не включается, даже если доверие выдано. Отменить решение можно обратной командой hermes skills untrust, и это полезно, когда подозреваете, что ветку переписали.
И третье, мелочь, но она объясняет половину вопросов. Обнаружение можно выключить целиком одной настройкой в конфиге, параметром project_discovery в секции скиллов, и тогда не сработает ни доверие, ни корни. Проверить надо обе вещи, прежде чем делать вывод, что проектные скиллы не работают.
Второй момент защиты. Каждый проектный скилл прогоняется через сканер безопасности, и результат кэшируется по хэшу содержимого, а не внутри репозитория. Если проверка признаёт скилл опасным, он выпадает из индекса, из списка и из команд, то есть агент его не увидит вообще. Назначение простое: доверять репозиторию можно один раз, а содержимое в нём меняется.
Что это даёт против изоляции на уровне профиля. Если каждый проект требовал бы своего профиля, то всё, что нужно двадцати проектам, пришлось бы или копировать в каждый, или связывать симлинками вручную, и при двадцати проектах это становится неуправляемым. Профиль как роль снимает вопрос для скиллов роли, а каталог проекта снимает его для частных вещей. Один раз собрал инструменты под дисциплину, и все проекты в профиле их видят, а частное лежит там, где не мешает соседям. Плата за это - вход и выход в роль, но для однородных работ это ровно то, что нужно.
Содержание проекта живёт в трёх местах:
-
Рабочие папки. Имя проекта связано с путём на диске, первая папка считается основной, остальные можно добавить. Агент не угадывает путь и не приносит путь из соседнего проекта.
-
Файл HERMES.md в папке проекта. Он читается на старте сессии, и в нём две вещи: что мы делаем (идея, цель, что считается готовым) и как мы это делаем (правила, соглашения, что выполнять нельзя). Это контекстный файл с наивысшим приоритетом, и он загружается автоматически. Если папка внутри git-репозитория, Hermes читает такие файлы по всей цепочке от корня репозитория до текущей папки, причём более специфичные читаются последними, то есть имеют приоритет. Уровень вложенности даёт естественную иерархию правил.
Есть ещё один файл, который вы получаете бесплатно при создании проекта из карточки в приложении. Когда заполняете поле "О чём этот проект?" и жмёте создать, приложение само пишет этот текст в файл IDEA.md в основной папке проекта. Отдельная кнопка с кубиком умеет сгенерировать идею за вас. Удобно, но есть тонкость, о которой стоит знать: IDEA.md не контекстный файл. В официальном списке таких файлов его нет, агент не читает его в промпт сам и не знает, что файл существует, пока вы не попросите или он случайно не наткнётся. В командной строке у команды создания проекта вообще нет флага для идеи, только описание, так что файл появляется именно из карточки.
Отсюда практический вывод. Если идея и правила должны работать на автомате, кладите их в HERMES.md - он попадёт в промпт без всяких просьб. IDEA.md оставьте как запись о том, зачем проект вообще заведён, сверху от рабочих документов: он полезен человеку и пригодится, когда будете разбираться в старом проекте. Смешивать две сущности в одном файле не надо: правила должны быть там, где агент их видит без подсказки.
-
Наработки в базе знаний. Каждый проект получает свою сущность со своим описанием, и агент читает её по необходимости, а не тащит в системный промпт всегда. Память профиля ограничена примерно двумя тысячами символов - туда не влезет описание проекта со всеми сущностями, стендами и правилами, поэтому база знаний это отдельный слой.
-
Скиллы, и тут два уровня сразу. На уровне роли лежат то, чем пользуется любой проект профиля. На уровне проекта лежат то, что нужно только ему, и лежит в каталоге проекта. Скилл про рецепты промптов для картинки в профиле для пресейла - мусор, в профиле для рисования - второй рабочий инструмент. А скилл про конкретный формат отчёта по конкретному заказчику вообще не нужен ни в одном профиле, кроме того, где этот заказчик есть.
-
Свои доступы и модели на уровне роли. Файл .env профиля задаёт ключи, которыми пользуется этот агент, и больше нигде. И своя модель, потому что задачи разные и требования к модели тоже. Проект поверх этого может сузить модель через свой файл правил, но не может завести ключи: ключи живут только на профиле.
Вот как это выглядит на одной машине, где несколько проектов живут параллельно:
профиль скиллов задач по расписанию модель
издание 120 6 средний класс
инженерный 310 9 средний класс
рисование 45 3 бесплатная
дайджест новостей 38 4 бесплатная
демо для вебинара 24 2 локальная
Разница в скиллах кратная. Профилю для инженерных задач нужны поддеревья развёртывания, синхронизаторы, шаблоны отчётов, профилю для рисования - модели изображений и десятки рецептов промптов. В одном списке это значит держать в контексте то, что никогда не пригодится, и платить за это токенами на каждом ходе.
Когда скиллы надо переносить, а когда не надо
Ключевой момент, который экономит больше всего нервов: сначала реши, куда скилл принадлежит по смыслу, и положи именно туда. Профилю, проекту или общему каталогу. Копировать не нужно почти ничего, потому что три уровня и так покрывают три разных случая.
И тут важная деталь про приоритет, которая экономит больше всего нервов при отладке. Если в проекте лежит скилл с тем же именем, что и в профиле, побеждает проектный, и он помечается как проектный в индексе. То есть перекрытие не молчаливое, его видно. Обратная ситуация тоже понятна: если скилла нет в проекте, он спокойно приезжает из профиля, и это нормально, а не утечка. Вот зачем нужны обе ступени: перекрытие без молчалки даёт контроль, а отсутствие при переносе не превращается в ошибку.
У Hermes есть внешние каталоги скиллов: в конфиге профиля прописывается список папок, которые сканируются вместе с локальной. Механизм рабочий, и я на нём сначала и споткнулся. Если скилл с одним именем лежит и во внешней папке, и в локальной, побеждает локальная копия. Эталон правится, а профиль читает свой дубль, и правки в эталоне тихо не применяются.
Что работает без молчаливых дублей - один каталог-хранилище общих скиллов и симлинки на нужное в каталоге скиллов профиля:
mklink /J %PROFILE%\skills\content\writer C:\Work\agents\shared-skills\content\writer
Сканер ходит по симлинкам как по обычным каталогам, слэш-команды и список скиллов видят то же самое, а файл физически один. Правишь скилл - изменение сразу в нужных профилях. Специфика профиля при этом остаётся обычными каталогами рядом и не выдаёт себя за общий эталон.
Два других пути, чем это решается:
/export, /import профиль целиком в архив, ключи вырезаются
автоматически, получатель получает живого
агента со всеми наработками
hermes profile install <git-url> профиль как git-репозиторий с манифестом,
обновление через hermes profile update,
память и сессии получателя не трогаются
Разница принципиальная: экспорт - это разовая передача коллеге, установка из репозитория - это сопровождение агента как продукта, у которого есть версии.
Не всем проектам нужна самая дорогая модель
Модель - это параметр профиля, а не всего агента целиком, и профили этим пользуются. Задача, где агент пишет промпт и смотрит на картинку, отлично идёт на бесплатной модели. Разбор чужой архитектуры - уже другая история, там цена ошибки другая. А разница в цене умножается на объём: профиль, который гоняет сотни задач в день на дорогой модели, обходится в разы дороже профиля, который там же сидит на бесплатной и справляется.
Грабли, на которые я наступил
Пустой профиль не умеет ни одного запроса: у него нет ни ключей, ни провайдера. Клонировать надо от того профиля, который уже ходит в живую модель.
Флаг описания - это --text, а не --description. Второго не существует, команда падает.
Путь профиля - только строчные латинские буквы, цифры и дефис, пробелы не допускаются.
Создание проекта внутри профиля требует указания папки. Без пути команда не создаёт ничего, и проект потом не находится в списке.
Новый профиль не появляется в ростере, пока не перезагрузишь плагины в приложении.
Один профиль - один агент. Два процесса на одной папке профиля будут конфликтовать за память, и это прямое предупреждение в документации.
Файлы инструкций в папке проекта приоритетны, как и SOUL.md. Если рядом оказался чужой HERMES.md, он перебьёт привычные правила оформления текста. Файл личности в профиле тоже один на все проекты внутри, поэтому "личность, подстроенная под один проект" - это уже профиль под один проект, и тогда стоит завести отдельный.
Скиллы проекта не подхватываются молча. Пока проекту не выдан статус доверенного, агент их не видит, причём он не знает, что они там лежат. Выглядит это так: положил скилл в .agents/skills, а его нет ни в списке, ни в командах. Лечится одной командой hermes skills trust в корне проекта. Это не настройка, а защита, и объяснение в том, что доверять репозиторий можно один раз, а содержимое в нём меняется каждым пулом.
Обратная сторона той же защиты. Если в проекте лежит скилл с тем же именем, что и в профиле, проектный побеждает, и это не ошибка, а задуманное поведение. Но победивший проектный скилл помечается как проектный, так что перекрытие видно глазами. Не перекрывается ничего молча, и это ровно то поведение, которого не хватает большинству инструментов.
И ещё: скилл из внешнего каталога и скилл из профиля с одинаковым именем это разные вещи, и побеждает локальный. Про это стоит помнить, когда переносите наборы между машинами, иначе получите эталон, который правите, и копию, которую читает агент.
И отдельно про то, что мы обсуждали в самом начале: универсальный скилл, полезный везде, это нормально. Проблема не в том, что скилл общий, а в том, что он общий и при этом молчаливый - то есть не говорит, в каком контексте его применять нельзя.

Итог
Схема простая и скучная, и в этом её смысл. Есть роль - сделай ей профиль. Есть проект внутри роли - заведи его в профиле, дай ему рабочую папку, файл с идеей и правилами, свою страницу в базе знаний и свои скиллы в каталоге проекта.
Ключи и модель живут на уровне профиля, потому что это параметры того, кто работает, и они делятся между проектами роли. Скиллы живут на обоих уровнях: то, чем пользуется вся роль, лежит в профиле, то, что нужно одному проекту, лежит в проекте и перекрывает одноимённое. Общие скиллы нескольких профилей лежат во внешнем каталоге, физически одним файлом.
Переключение между проектами одного профиля - это переключение проекта, между разными ролями - переключение профиля. И то и другое - не смена формулировки в промпте.
И тогда исчезает целый класс ситуаций, когда агент сделал не то. Не потому что не понял задачу. Потому что у него не было для этого ни модели, ни ключа, ни нужного способа доступа - и взять было неоткуда, всё остальное было от соседнего проекта.
Продолжение будет отдельно: как два профиля договариваются между собой, когда один редактор зовёт второго инженера по внутреннему телефону, вместо того чтобы тащить его скиллы к себе. Но это надо обкатать вживую, а не описать.