Aller au contenu

Les garde-fous d'exécution

Ce qui borne une session d'agent : bac à sable, politique, validation humaine, bornes de session, détection de boucle et délégation.

Une session d’agent est une suite d’actions proposées par le modèle et exécutées, ou non, par le système. Lemniscate part du principe que l’agent peut mal se comporter, par erreur ou sous l’effet d’un contenu piégé, et construit l’environnement de sorte que ce comportement reste sans conséquence hors du bac à sable (livre blanc sécurité V3, section 4).

Le confinement borne ce que l’agent peut atteindre. La politique borne ce qu’il peut faire à l’intérieur. Cette page décrit les deux, puis les garde-fous qui arrêtent un agent qui tourne trop longtemps, tourne en rond ou sort du projet.

Sept garanties sont tenues par la passerelle et le bac à sable, et non par le modèle. Elles sont indépendantes les unes des autres (livre blanc sécurité V3, section 4.1).

GarantieÉnoncéOù elle est décrite
G01périmètre limité à la copie du dépôt autorisé par la politique du projetCe que l’agent voit
G02validation humaine de chaque écriture retenue, présentée en différenceLa validation humaine
G03politique de commandes définie par l’administrateur, appliquée par la passerelleLa politique de projet et de commandes
G04exécution confinée dans un bac à sable jetableLe bac à sable d’exécution
G05aucune destination réseau accessible, ni externe ni interneLe bac à sable d’exécution
G06traçabilité : consigne, actions proposées, décisions, résultatsLe cycle d’une session agentique
G07arrêt par le développeur et révocation par l’administrateur, à tout momentL’arrêt et la révocation

La frontière de sécurité est le bac à sable, non la liste des commandes autorisées. Une liste de commandes relève de l’hygiène : un interpréteur autorisé exécute n’importe quoi. Ce qui borne l’agent est l’environnement dans lequel il s’exécute (livre blanc sécurité V3, section 4.2).

AspectCe que le bac à sable impose
Isolationun bac à sable par session, créé à la demande et détruit en fin de session ; aucun état partagé entre sessions ni entre projets ; exécution sans privilège, sous profils seccomp et AppArmor
Fichiersla copie de travail du dépôt est le seul volume en écriture ; le système de fichiers racine est en lecture seule ; les écritures sont restituées sous forme de différence
Réseauaucune interface, aucune sortie, aucune résolution de noms ; les échanges avec le modèle sont relayés par la passerelle ; un seul canal de contrôle, ouvert par la passerelle
Ressourcesdurée, processeur, mémoire et processus plafonnés ; arrêt automatique au dépassement, journalisé ; rien ne subsiste après la session

L’absence de sortie réseau est imposée par l’isolation elle-même, non par une configuration que l’agent pourrait modifier. Les profils de confinement sont fournis et s’adaptent à vos standards. Sur un hôte d’exécution partagé entre plusieurs projets, l’isolation peut être portée par une micro-machine virtuelle ou un noyau en espace utilisateur, selon votre référentiel.

La préparation de l’image utilisée par le bac à sable est décrite dans Préparer le conteneur de session.

Un agent qui ne détient aucun secret ne peut pas en faire fuiter (livre blanc sécurité V3, section 4.3).

Ce que l’agent voitCe qu’il ne porte pas
les fichiers du dépôt ouvert, hors exclusions de contexteaucun compte, aucune identité sur la forge
les sorties des commandes qu’il exécuteaucune clé, aucun jeton, aucun secret monté
la consigne du développeuraucune variable d’environnement du poste
rien d’autre sur l’hôte ni sur le posteaucun accès à un autre dépôt ou à un autre projet

Les exclusions de contexte désignent les fichiers que l’agent ne voit pas dans la copie de travail : fichiers d’environnement, répertoires de secrets, certificats. Le répertoire personnel du développeur, ses clés, ses identifiants de forge et ses variables d’environnement ne sont pas montés dans le bac à sable.

L’agent n’a pas de compte et ne pousse rien. La différence validée est appliquée au dépôt du développeur, qui l’intègre sous sa propre identité, par ses outils habituels et dans ses circuits de revue.

Le bac à sable s’exécute sur un hôte dédié ou sur le poste. Le mode de référence est l’hôte dédié (livre blanc sécurité V3, section 4.4).

Hôte d’exécution dédié, mode de référenceBac à sable sur le poste, variante
bacs à sable sur un hôte distinct de la passerelleconteneur sans privilège et sans réseau, sur le poste
aucun moteur de conteneurs sur les postesdépôt local monté, seul volume en écriture
environnement unique, durci par l’administrateurchaînes de compilation du poste disponibles
retenu pour les périmètres sensibles et Diffusion Restreintepour les socles qui admettent un moteur de conteneurs

Dans le mode de référence, l’agent n’a pas les droits de l’utilisateur sur son poste, et le poste ne porte que l’extension. L’hôte d’exécution ne conserve aucun état. La contrepartie : l’hôte concentre les bacs à sable de plusieurs projets, ce qui justifie une isolation renforcée, et une copie du dépôt y transite le temps de la session. Le choix se fait par périmètre, avec le responsable de la sécurité. Le réglage qui porte ce choix est décrit dans Le cycle d’une session agentique.

La politique est écrite par l’administrateur dans la console, évaluée par la passerelle avant chaque action, et n’est pas interprétée par le modèle. Elle ne peut pas être désactivée depuis un poste (livre blanc sécurité V3, section 5.1).

Libres, si l’administrateur le décideSoumises à la validation du développeur
compilation, tests unitaires, formatage et analysetoute commande absente de la liste
lecture de fichiers du dépôt ouverttoute écriture retenue, présentée en différence
commandes listées par l’administrateurtout outil marqué sensible par la politique
commande déjà validée, si la politique le permettoute modification d’un fichier exécuté par un tiers

Par défaut, aucune commande n’est libre : chaque modification et chaque commande sont présentées au développeur. L’administrateur de projet peut :

  • autoriser les commandes usuelles de compilation, de test et d’analyse, et toute commande qu’il liste ;
  • désactiver un outil de l’agent pour un projet, sans redéploiement ;
  • autoriser le développeur à ne pas revalider une commande déjà validée, ou interdire cette dispense.

Toute modification de politique est journalisée. La façon de l’écrire est dans Régler la politique des postes depuis la console.

La défense contre l’injection d’instructions est structurelle : elle ne repose pas sur un filtre (livre blanc sécurité V3, section 5.2).

  • La consigne du développeur est la seule source de contrôle.
  • Le contenu lu dans le dépôt, dans une sortie de commande ou dans une dépendance est marqué comme non fiable et présenté au modèle dans une zone distincte.
  • Les outils disponibles sont fixés par la politique avant toute lecture. Un contenu lu ne peut pas en activer de nouveaux.
  • La politique s’applique à chaque appel d’outil.

Le modèle raisonne sur tout le contexte, mais il ne tient pas les droits. Une injection reste possible : l’architecture garantit que son effet ne sort pas du bac à sable et passe devant un humain (livre blanc sécurité V3, section 11.4).

Un agent confiné peut modifier un fichier que quelqu’un d’autre exécutera plus tard, avec d’autres droits. Ces fichiers forment une classe à part (livre blanc sécurité V3, section 5.3) :

  • la configuration d’intégration continue et les scripts de build ;
  • les hooks du gestionnaire de versions et les fichiers de configuration du dépôt ;
  • les manifestes de dépendances et les fichiers de verrouillage ;
  • les fichiers binaires et les différences au-delà d’un seuil de taille.

Leur modification est interdite par défaut, ou soumise à une validation dédiée, distincte de la validation ordinaire d’une différence et signalée comme telle au développeur. La liste des classes est définie par projet dans la politique.

Toute action à effet de bord est validée par un humain (livre blanc sécurité V3, section 5.4) :

  • toute écriture retenue est présentée sous forme de différence et acceptée par le développeur ;
  • toute commande non listée par l’administrateur est soumise à validation avant exécution ;
  • l’administrateur peut retirer un outil à l’agent, par projet, sans redéploiement ;
  • le développeur peut arrêter la session à tout moment, et l’administrateur peut la révoquer.

La demande de validation montre la différence ou la commande exacte, et signale les fichiers exécutés par des tiers.

Le code proposé peut contenir des vulnérabilités : la revue humaine reste nécessaire, et le code accepté suit les mêmes circuits de revue que toute contribution (livre blanc sécurité V3, section 11.4).

Le développeur dispose d’un arrêt en un geste depuis son environnement. Il interrompt le travail en cours, détruit le bac à sable et rejette les écritures non validées. L’administrateur dispose d’une révocation depuis la console, par session ou par utilisateur, effective sans redémarrage et journalisée (livre blanc sécurité V3, section 4.5).

Toute session a une fin prévue. Quatre budgets la bornent : la durée, le volume d’échanges avec le modèle, le nombre de commandes et les ressources du bac à sable. Leur dépassement provoque un arrêt propre (livre blanc sécurité V3, section 4.5).

Chaque message de l’utilisateur ouvre une session bornée en actions (appels d’outils et compactions de contexte confondus, 150 par défaut) et en durée (45 minutes par défaut). Le message suivant repart avec un budget entier.

Quand une borne est atteinte :

  1. En session interactive, une extension peut être demandée à l’humain. Chaque extension accorde une dotation limitée (50 actions ou 15 minutes), leur nombre est plafonné (5 par défaut), et chacune laisse une trace dans le journal d’audit.
  2. Sans extension, l’agent produit un résumé de son travail (fait, en cours, reste à faire) dans un dernier tour de modèle où aucun outil n’est disponible : rien ne s’exécute après l’épuisement.
  3. Le message d’arrêt dit qu’il s’agit d’une borne, pas d’une fin de tâche.

En mode non interactif (lemni -p), il n’y a personne à qui demander une extension : la session s’arrête avec le code de sortie 3, distinct d’une réussite (0) et d’un plantage (1). Un script sait ainsi que le travail n’est pas fini sans le confondre avec une erreur.

Les bornes se règlent dans le fichier de configuration, sous la clé execution :

execution:
sessionMaxActions: 150
sessionMaxMinutes: 45
sessionMaxExtensions: 5

Les variables d’environnement LEMNISCATE_SESSION_* sont lues en repli, et la configuration prend le pas. Ce repli vaut sur les deux surfaces : dans l’éditeur comme au terminal, la précédence est défauts, puis environnement, puis configuration. Une valeur illisible est refusée avec un message ; elle n’est pas remplacée par le défaut. Le repli d’environnement est résolu une fois par processus : après avoir corrigé la variable, redémarrez l’éditeur pour qu’elle soit relue.

Le plafond borne la longueur ; il ne voit pas un agent qui relit vingt fois le même fichier. La détection de boucle attrape ce cas : quand le même appel d’outil, avec exactement les mêmes arguments, revient une quatrième fois de suite, l’appel est bloqué avant de s’exécuter et la session est rendue à l’utilisateur, avec ce qui bouclait.

Deux choix sont à connaître :

  • Le modèle reçoit un message explicite, « bloqué pour répétition, ne réessaie pas ». Sans ce message, il réessaierait et le garde-fou deviendrait lui-même la boucle.
  • La fenêtre est consécutive : relire un fichier après l’avoir modifié, ou le relire trois fois au fil d’une longue session, ne déclenche rien.

En mode non interactif, le code de sortie est le même, 3 : du point de vue d’un script, un garde-fou a arrêté l’agent.

execution:
toolLoopThreshold: 3 # répétitions identiques consécutives tolérées
toolLoopDetection: false # désactivation possible, mais toujours explicite

Le périmètre de l’agent est la copie du dépôt autorisé par la politique du projet (garantie G01). Les outils de fichiers appliquent ce périmètre avant toute lecture ou écriture, en plus du confinement du bac à sable. « Le projet » est la racine du dépôt git (le répertoire de travail hors dépôt), les liens symboliques sont résolus avant contrôle, et un fichier à créer est jugé sur son chemin.

  • En écriture comme en lecture (Read, List, Search), un chemin hors du projet est refusé, avec la raison, sans demande de permission : le terminal tourne souvent sans personne pour répondre, et une demande sans interlocuteur est un blocage.
  • Les commandes shell (Bash) ne passent pas par ce contrôle de chemin : elles sont analysées par leur propre garde-fou (@lemniscate/terminal-security), et bornées par le bac à sable.

Les agents : un seul format, des droits qui se restreignent

Section intitulée « Les agents : un seul format, des droits qui se restreignent »

Un agent est un fichier Markdown : le frontmatter YAML porte la configuration, le corps du fichier est le prompt système.

---
description: Relit le code avant une PR
tools: Read, Search
sessionMaxActions: 25
---
Tu es un relecteur de code exigeant…
  • Le nom du fichier est le nom de l’agent. revue.md s’invoque par lemni --agent revue. Un champ name dans le frontmatter est ignoré avec un avertissement, et une version future le refusera.
  • Les agents sont découverts dans .lemniscate/agents/ du projet et dans ~/.lemniscate/agents/. En cas de collision de noms, celui du dépôt l’emporte. Un agent découvert est disponible ; il n’est pas actif de lui-même.
  • Charger un agent n’élargit pas les droits. Un agent sans clé tools hérite des permissions de l’utilisateur, une liste tools déclarée ferme le catalogue au lieu de l’ouvrir, et le mode plan survit au chargement d’un agent.
  • sessionMaxActions dans le frontmatter surcharge le plafond global pour les sessions que cet agent pilote.
  • disable: true retire un agent sans supprimer son fichier ; hidden: true le masque de l’autocomplétion tout en le laissant invocable par son nom.

Depuis VS Code et JetBrains, un agent découvert s’active par le sélecteur d’agent de la barre de saisie, à côté des sélecteurs de mode et de modèle. Le sélecteur affiche le nom de l’agent actif tant qu’il pilote la session, et No agent au repos. Il ne propose que les agents activables, et ne s’affiche pas quand aucun agent n’est découvert.

Une activation applique, à parité avec lemni --agent :

  • la persona (le corps du fichier), qui devient le prompt système de la session ;
  • la liste tools, qui ferme le catalogue : un outil non listé, outils MCP compris, n’est pas proposé au modèle, et l’agent ne peut que restreindre (un outil désactivé par vos réglages reste exclu quoi que déclare l’agent, le mode chat reste sans outils, le mode plan reste en lecture seule) ;
  • sessionMaxActions, qui borne la session pilotée, avec la même précédence qu’au terminal (défauts, environnement, configuration, agent) ;
  • le model déclaré, sélectionné s’il correspond à un modèle configuré. S’il est introuvable, un avertissement s’affiche et le modèle courant est conservé.

Les refus sont ceux du terminal : un agent mode: subagent est réservé à l’outil de délégation, disable: true n’est ni listé ni activable, et un homonyme des noms réservés general et explore est écarté et signalé. L’activation est un état de session : une nouvelle session repart sans agent, et un rechargement de configuration qui fait disparaître l’agent actif (fichier supprimé, passé disable, devenu mode: subagent) le désactive en l’annonçant.

Deux limites sont annoncées à l’activation :

  • la clé rules (paquets de règles du registre) n’est pas chargée par l’éditeur : la persona et les restrictions d’outils s’appliquent, les règles du registre non ;
  • une référence MCP de la liste tools (owner/paquet ou URL) est résolue par l’éditeur contre le nom du serveur configuré, pas contre le slug du registre comme au terminal. Si aucun serveur configuré ne porte ce nom, tous les outils de la référence restent exclus : la fermeture joue dans le sens de la restriction.

L’agent dispose d’un outil de délégation (Task) : il confie une consigne à un sous-agent, qui tourne dans sa propre session, avec son propre contexte et ses propres garde-fous, et rend un rapport final. Le modèle mental est développé dans Déléguer à des sous-agents, et la marche à suivre dans Déléguer une tâche à un sous-agent. Les règles :

  • Rien n’est délégable sans mode explicite. Un agent découvert dans un dépôt ou sur le poste n’est pas délégable par sa seule présence : son frontmatter doit déclarer mode: subagent (délégable par un agent seulement) ou mode: all (délégable et invocable par l’utilisateur). Sans mode, un agent reste invocable par l’utilisateur seulement.
  • Deux sous-agents intégrés au produit sont délégables sans installation : general (généraliste) et explore (explorateur en lecture seule, qui ne déclare que Read, List, Search). Leurs noms sont réservés sur les deux entonnoirs : un fichier découvert homonyme est signalé et écarté de la délégation, et il n’est pas non plus invocable par --agent general. Charger un tel fichier reste possible en passant son chemin .md explicite.
  • Un sous-agent n’élargit pas les droits du parent. Sa politique est celle du parent, restreinte par sa persona : un outil exclu chez le parent reste exclu chez lui quoi que déclare son frontmatter, le mode plan du parent le laisse en lecture seule, et un serveur MCP non déclaré ne lui est ni décrit ni accessible.
  • La cascade est bornée par des filets globaux, réglables sous la clé execution : subagentMaxDepth (profondeur, défaut 3 ; 1 interdit toute imbrication), subagentMaxPerSession (défaut 200) et subagentMaxConcurrent (défaut 20). Quand une limite est atteinte, l’agent reçoit une erreur explicite et finit le travail lui-même. Zéro est refusé sur ces clés : un filet de cascade se règle, il ne se retire pas.
  • Le budget d’un sous-agent est propre (rien ne se décompte du parent) et sans plafond d’actions par défaut ; un sessionMaxActions posé dans son frontmatter s’applique, et la détection de boucle reste active dans chaque sous-agent. Un arrêt sur garde-fou dans le sous-agent est marqué dans le rapport rendu au parent, qui distingue « travail coupé » de « travail fini ».
  • La borne de durée d’un sous-agent vient de la configuration du poste (défaut 45 minutes), pas du temps déjà consommé par le parent : chaque session fille repart avec son budget de durée entier, et sans extension possible, faute d’humain dans la session fille.
  • Les demandes de permission remontent à vous, pas au modèle. En session interactive, un outil en ask chez un sous-agent s’affiche sur l’écran d’approbation habituel, le point d’approbation unique : il n’y a qu’une surface humaine par processus, toutes les demandes de l’arbre y convergent, et chacune est étiquetée du nom de l’agent qui demande. Ce nom est le nom de fichier de l’agent, tel quel : seuls les deux noms intégrés (general, explore) sont protégés contre l’homonymie ; un agent découvert au nom trompeur reste affiché sous ce nom. En non-interactif (mode -p, lemni serve, tâche d’arrière-plan), la demande devient un refus sec et annoncé, sans auto-approbation par le modèle, et le journal des appels d’outils attribue ce refus à l’automate.
  • L’outil Question est offert à la session principale, dans un terminal interactif ; un agent délégué ne l’a pas dans son catalogue. Face à une ambiguïté, la session principale vous pose une question au point d’approbation, avec des options proposées ou une réponse libre. Échap la rejette, et l’agent annonce alors son hypothèse et continue. Sans utilisateur joignable, la réponse est un défaut annoncé, qui demande au modèle d’énoncer son hypothèse et de poursuivre.
  • Arrière-plan : Task avec background: true rend immédiatement un task_id et le sous-agent tourne détaché. La fin est annoncée dans la conversation (cloche du terminal en opt-in : LEMNISCATE_NOTIFY_BELL=1), et le rapport se lit avec l’outil TaskResult ou dans la session fille persistée. En exécution ponctuelle (lemni -p), l’arrière-plan se replie en exécution synchrone annoncée, parce que le processus ne survit pas au tour. Sur lemni serve, processus durable même sans écran, le détachement est réel et les événements task/started et task/settled l’annoncent sur le flux d’événements ; les demandes de permission d’une tâche détachée y restent au refus sec, comme partout où personne ne peut approuver. Une tâche détachée meurt avec le processus qui la porte : fermer le terminal (ou arrêter le serveur) interrompt les tâches en cours, sans annonce, et leur trace s’arrête dans la session fille persistée.
  • Mention @ dans le terminal : @general <consigne> lance vous-même un sous-agent délégable, en tâche détachée, sous les mêmes filets globaux que les délégations du modèle. Tout autre @ garde son sens de mention de fichier.
  • Interrompre le parent interrompt ses délégations : l’interruption d’un tour se propage aux sous-agents synchrones en cours, et leur rapport revient marqué « interrompu ». Une tâche détachée survit au tour et reste bornée par sa durée, sa détection de boucle et les filets globaux.
  • La session fille est persistée comme une session ordinaire : ce que le sous-agent a fait se relit après coup.