Пользовательские skill'ы: шаблоны, которые остаются безопасными
Как skill компилируется в отмеченную запись контекста: whitelist, один проход, три уровня scope, DLP до компиляции и freemium max_skills (3 или без лимита).

Инструкции, которые вызывают, а не код, которому доверяют
Пользовательский skill - переиспользуемый шаблон prompt'а: написан однажды, вызывается из чата как slash-команд вновь и вновь. Проектная ставка в формуле "шаблон prompt'а": skill не содержит HTTP-вызовов, файлового ввода-вывода, условной логики, песочницы - бежать некуда, поэтому функция air-gap-safe по самому построению. Что skill содержит - фиксированный管线 компиляции с настоящими границами: параметры, whitelist-ованные по схеме, один проход подстановки, маскировка до компиляции и инъекция, приходящая как недоверенные данные.
Эта статья проходят путь написания от начала до конца: как пишется и компилируется шаблон, грамматика slash-команд, три уровня scope, граница привилегий, держащая компилированный вывод честным, и лицензионная форма (max_skills - 3 в Community, без лимита в Enterprise).
Шаблон и его правила
Шаблон - простая строка с placeholder'ами {{param}}:
Translate to {{targetLang}}: {{input}}Конвейер компиляции навязывает четыре правила (прибитые к схеме):
- Whitelist - подставляются только ключи, объявленные в
inputSchema.propertiesskill'а (плюс его дефолтные параметры). Неизвестный placeholder остаётся литеральным в выводе - никогда не решается из окружения, системного контекста, чего-либо ещё. - Один проход - выполняется ровно один проход замены. Подставленное значение, содержащее внутри себя формы маркеров
{...}, никогда не разворачивается снова; инъекция через шаблон умирает здесь на уровне структуры. - Согласованность - каждый placeholder шаблона обязан иметь свою запись в input-схеме; create/patch без неё = именованный 400.
- Отвержение маркеров - статический текст шаблона (без placeholder'ов) не должен содержать формы tool-call-маркеров межпровайдерного класса; они отвергаются при create/patch. Значения параметров несут свою собственную защиту ниже.
Грамматика slash-команд
Символ / открывает палитру skill'ов (автодополнение); выбранная строка вставляет свой slug без отправки. На отправке клиент разбирает аргументы и шлёт вызов вместе с набранным сообщением:
- Позиционно:
/translate Ciao mondo- токены сплавляются в первое обязательное поле. - Парами:
/translate input=Ciao- именованные заполнения; отрезки в кавычках (input="Ciao mondo") несут пробелы. - Смешанно и по умолчанию:
input="Ciao" targetLang=Spanish, или обязательный ключ, принимающий свой объявленный дефолт.
Два поведения делают грамматику заслуживающей доверия, а не хитрой:
- Правило «никогда не ошибка» - нераспознанное
/слово, имя builtin или выключенная строка проскальзывает в обычное сообщение: без вызова, без ошибки, без ничего спрятанного. - Сервер перепроверяет всё - неизвестные ключи игнорируются; slug заново решается на сервере с тем же фильтром scope при каждом вызове (караул IDOR).
Список зарезервированных slug'ов (фиксированные команды чата + имена builtin skill'ов) делает затенение невозможным: пользовательский skill никогда не может взять зарезервированное имя.
Путь запроса: решить, замаскировать, скомпилировать, обернуть
"/slug params" → POST chat stream { message, skillCall }
→ resolveInvocableSkill (на сервере, с фильтром scope)
→ DLP-маска ДО компиляции (значения сканируются, когда DLP активен)
→ compileTemplate (whitelist-замена, один проход) + спотлайт-обёртка
→ ОДНА отмеченная запись контекста уровня пользователя в оркестратореПорядок с DLP - несущая деталь: параметры маскируются до компиляции. При обратном порядке компиляции текст, вписанный в шаблон, мог бы спрятать параметр от сканера; при этом порядке компилированный prompt содержит только замаскированные значения. (Сама DLP-труба - нумерованные маски, зашифрованные оригиналы, admin-размаскирование - своя отдельная история.)
Граница привилегий
Компилированный вывод шаблона - данные, предоставленные пользователем, а не инструкции. Он входит в разговор одной отмеченной записью контекста - с меткой, со счётом бюджета, как всякий tool result, и прибит привилегийным тестом: пользовательский skill может добавить контекст, но никогда не может добавить tools, разрешения, записи реестра. Транскрипт хранит набранное сообщение; в контекст skill'а входит только компилированный prompt.
Потому "без песочницы" - свойство, а не ограничение: поверхность силы шаблона - то, на что она может влиять - ровно одна запись контекста, и больше ничего.
Scope: три уровня, решаемые на сервере
- Личный - решает его только автор (любой пользователь с правом создания).
- Workspace - всякий, кто болтает в этом workspace (создаётся editor+; viewer получает явный 403). Сохранённая строка несёт null в user id - строка со штампом автора протекла бы сквозь workspace'ы, и схема закрывает этот класс.
- Глобальный - все в организации (создаётся только админами).
Заявленный клиентом scope никогда не доверяется: сервер решает уровень по сохранённой форме и фильтрует решение на каждый чат. Правки и удаления действуют со следующего чата - без hooks, без рестартов. Удаление - мягкий надгробный камень, и slug освобождается сразу: имя удалённого skill'а пересоздаваемо на месте.
Freemium-факт: max_skills
Лицензионный лимит зеркалит прочие числовые заслоны платформы: 3 пользовательских skill'а в Community, без лимита в Enterprise - строка census'а F-SKILL-1, стоящая на лицензионных константах:
- Действует только на CREATE - заградить вызовы значило бы рвать чат посреди по лицензионной причине - отвергнутый проект; счёт живёт на провизионировании.
- Счёт - по организации, по пользовательским строкам - builtin и marketplace-строки никогда не считаются.
- Сверх лимита стандартная paywall-форма называет функцию и ступень - и выход self-service: каждый пользователь может удалить свои skill'ы, освободив квоту, без вмешательства админа.
Последнее свойство - freemium-договор, сделанный честно: лимит виден в интерфейсе управления, 402 объясняет себя, и выход не стоит тикета поддержки.
Читать дальше
Путеводитель несёт полную операторскую справку - endpoint'ы, диалог формы, семантику test-preview - на /ru/docs/custom-skills:
- Custom skills - путеводитель
- Факты этого поста: строки
FACT-CENSUS.mdF-SKILL-1 (пользовательские skill'ы + freemiummax_skills) и F-DLP-1 (слой маскировки, по которому идёт этот путь)
Provenance: Simmetric Chat docs/CUSTOM_SKILLS.md (v0.28.1, Phase 190 SKIL-01..05), packages/shared/src/schemas/skill.schema.ts, license.ts (max_skills) - на зафиксированном census'ом документальном состоянии (SHA aca5b8ff).
