Blog
Tutoriels

Écrire des skills personnalisés : des modèles qui restent sûrs

La compilation d'un skill vers une entrée de contexte surlignée : whitelist, passe unique, scoping ×3, DLP avant compilation, et le freemium max_skills (3 ou illimité).

Simmetric Team8 min de lecture12/10/2026
Écrire des skills personnalisés : des modèles qui restent sûrs

Des instructions que l'on appelle, pas du code auquel il faut se fier

Un skill personnalisé est un modèle de prompt réutilisable : écrit une fois, appelé à répétition depuis le chat comme une commande slash. Le pari de conception est dans la formule « modèle de prompt » : un skill ne contient pas d'appels HTTP, pas d'I/O fichier, pas de logique conditionnelle, pas de sandbox - il n'y a rien dont s'échapper, donc la fonction est air-gap-safe par construction. Ce qu'un skill contient, c'est une pipeline de compilation figée avec de vraies frontières : des paramètres à liste blanche vis-à-vis d'un schéma, une seule passe de remplacement, un masquage avant la compilation, et une injection qui arrive comme donnée non fiable.

Cet article parcourt le chemin d'écriture de bout en bout : comment un modèle s'écrit et se compile, la grammaire de la commande slash, les trois niveaux de périmètre, la frontière de privilège qui garde l'output compilé honnête, et la forme de licence (max_skills - 3 en Community, illimités en Enterprise).

Le modèle et ses règles

Un modèle est une chaîne simple avec des placeholders {{param}} :

text
Translate to {{targetLang}}: {{input}}

La pipeline de compilation applique quatre règles (pinned au schéma) :

  • Liste blanche - seules les clés déclarées par inputSchema.properties du skill (et ses paramètres par défaut) se substituent. Un placeholder inconnu reste littéral dans l'output - jamais résolu depuis l'environnement, le contexte système, ou quoi que ce soit d'autre.
  • Passe unique - exactement une passe de remplacement s'exécute. Une valeur substituée qui contient elle-même des formes de marqueurs {...} ne se ré-étend jamais ; l'injection via modèle meurt ici au niveau structurel.
  • Cohérence - chaque placeholder du modèle doit avoir son entrée dans le schéma d'entrée ; create/patch sans = un 400 nommé.
  • Rejet des marqueurs - le texte statique du modèle (placeholders ôtés) ne doit pas contenir de formes de marqueurs d'outils inter-fournisseurs ; rejetées au create/patch. Les valeurs des paramètres ont leur défense dédiée plus bas.

La grammaire de la commande slash

Taper / ouvre la palette des skills (l'autocomplétion) ; une ligne sélectionnée insère son slug sans envoyer. À l'envoi, le client analyse les arguments et expédie l'appel avec le message tapé :

  • Positionnel : /translate Ciao mondo - les tokens fusionnent dans le premier champ requis.
  • Paires : /translate input=Ciao - remplissements nommés ; les étendues entre guillemets (input="Ciao mondo") portent les espaces.
  • Mixte et défauts : input="Ciao" targetLang=Spanish, ou une clé requise qui prend son défaut déclaré.

Deux comportements rendent la grammaire digne de confiance plutôt que rusée :

  • La règle jamais-erreur - un /mot sans correspondance, un nom builtin, ou une ligne désactivée glisse vers un message normal : pas d'appel, pas d'erreur, rien de caché.
  • Le serveur re-valide tout - les clés inconnues s'ignorent ; le slug se re-résout côté serveur avec le même filtre de périmètre à chaque invocation (la garde IDOR).

La liste des slugs réservés (les commandes fixes du chat + les noms de skills builtin) rend l'usurpation impossible : un skill personnalisé ne peut jamais prendre un nom réservé.

Le chemin de la requête : résoudre, masquer, compiler, ensacher

text
"/slug params" → POST chat stream { message, skillCall }
→ resolveInvocableSkill (côté serveur, filtré par périmètre)
→ masquage DLP AVANT la compilation (valeurs scannerisées quand le DLP est actif)
→ compileTemplate (remplacement en liste blanche, une passe) + l'emballage spotlight
→ UNE entrée de contexte utilisateur surlignée dans l'orchestrateur

L'ordre avec le DLP est le détail porteur : les paramètres se masquent avant la compilation. Compilés dans l'ordre inverse, un texte écrit dans le modèle pourrait cacher un paramètre au scanner ; avec cet ordre, le prompt compilé ne contient que des valeurs masquées. (La pipeline DLP elle-même - les masques numérotés, les originalaux chiffrés, l'unmask admin - est sa propre histoire.)

La frontière de privilège

L'output compilé d'un modèle est une donnée fournie par l'utilisateur, pas des instructions. Il entre dans la conversation comme une entrée de contexte surlignée - étiquetée, comptée au budget comme tout tool result, et pinnée par un test de privilège : un skill personnalisé peut ajouter du contexte mais ne peut jamais ajouter d'outils, de permissions, ou d'entrées de registre. La transcription garde le message tapé ; seul le prompt compilé entre comme contexte du skill.

Voilà pourquoi « pas de sandbox » est une fonctionnalité et non une limite : la surface de pouvoir du modèle - ce qu'il peut influencer - est exactement une entrée de contexte, et rien d'autre.

Périmètre : trois niveaux, dérivés côté serveur

  • Personnel - seul l'auteur le résout (tout utilisateur avec la permission de création).
  • Workspace - quiconque tchatte dans ce workspace (créé par éditeur+ ; un viewer reçoit un 403 explicite). La ligne persistée porte un user id à null - une ligne timbrée par le créateur fuirait à travers les workspaces, et le schéma ferme cette classe.
  • Global - tout le monde dans l'organisation (créé par les admins seulement).

Le périmètre revendiqué par le client n'est jamais de confiance : le serveur dérive le niveau de la forme persistée et filtre la résolution par chat. Les éditions et suppressions s'appliquent au chat suivant - pas de hooks, pas de redémarrages. La suppression est une tombe douce, et le slug se libère immédiatement : le nom d'un skill supprimé est recréable sur place.

Le fait freemium : max_skills

La limite de licence reflète les autres barrières numériques de la plateforme : 3 skills personnalisés en Community, illimités en Enterprise - la ligne de census F-SKILL-1, fondée sur les constantes de licence :

  • Appliquée au CREATE seulement - gate-keeper les invocations interromprait le chat à mi-voie pour une raison de licence, un design rejeté ; le compte vit au provisioning.
  • Le compte est org-échu sur les lignes personnalisées - les builtin et les lignes de marketplace ne comptent jamais.
  • Au-delà de la limite, la forme paywall standard nomme la fonction et le tier - et la sortie est en libre-service : chaque utilisateur peut supprimer ses propres skills pour libérer du quota, sans intervention d'admin.

Cette dernière propriété est le contrat freemium fait honnêtement : la limite est visible dans l'interface de gestion, le 402 s'explique, et la voie de sortie ne coûte pas de ticket de support.

Pour continuer

Le guide porte la référence opérateur complète - endpoints, le dialog du formulaire, les sémantiques de test-preview - sur /fr/docs/custom-skills :

  • Custom skills - le guide
  • Faits de ce billet : lignes FACT-CENSUS.md F-SKILL-1 (les skills personnalisés + le freemium max_skills) et F-DLP-1 (la couche de masquage que ce chemin voyage)

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) - à l'état documentaire épinglé par le census (SHA aca5b8ff).

Articles associés