agentspocsécuritémesure

Hermes : ce qu'un POC de trois heures a montré

En bref

Neuf critères, huit tenus, un partiel. Et surtout trois comportements qu'aucune fiche produit n'annonce : des paquets installés à chaque démarrage, trois réglages là où la documentation en donne un, et une panne visible uniquement sur le canal qu'on n'avait pas testé.

Un POC d’agent qui se contente de vérifier que l’agent répond ne mesure presque rien. Celui-ci a été construit pour mesurer autre chose : ce que le produit fait quand on ne le lui demande pas. Neuf critères, chacun avec un seuil écrit avant la mesure, et un environnement volontairement contraint. C’est la contrainte qui a rendu visible l’essentiel.

POC mené sur Hermes, agent conversationnel open source en version 0.20.5 sous licence MIT, dans la nuit du 22 au 23 août 2026, sur un mini-PC dédié, adossé à un modèle servi par notre propre infrastructure. Toutes les valeurs citées sont des mesures de cette nuit-là.

Le tableau, sans arrondi

Huit critères tenus, un partiel, aucun échec.

Le service redevient actif 17 secondes après le démarrage de la machine, pour un seuil fixé à 60. La mémoire persiste : deux faits mémorisés, service redémarré, deux faits restitués. Une tâche planifiée à cinq minutes est bien délivrée sur le canal de messagerie. Aucune compétence ne s’est auto-créée. Un sous-agent a été correctement délégué et a rendu son résultat en 29,2 secondes.

La latence mérite un mot, parce qu’elle illustre une règle de méthode. La première mesure a donné 22,6 secondes. Elle est inexploitable, et nous l’avons écartée en disant pourquoi : la session contenait encore un message resté sans réponse, et l’agent a traité deux demandes dans ce tour, ce que sa réponse prouve. Reprise après redémarrage complet, sur service froid : 14,0 secondes, à plus ou moins une seconde près puisque le canal horodate à la seconde entière. Le tour suivant, à chaud : 7,4 secondes.

Le critère était fixé à quinze secondes. Il est donc tenu avec une seconde de marge sur le cas froid. Sur un modèle plus lent ou une invite système plus longue, il basculerait. Une mesure qui passe de justesse doit être présentée comme telle, sinon elle sera citée plus tard comme une marge confortable.

Côté charge : 130 Mo au repos pour moins d’un pour cent d’un cœur, et 139 Mo en génération pour un pic à 209 % de CPU, soit un peu plus de deux cœurs. L’empreinte mémoire est modeste, la pointe processeur ne l’est pas.

Ce qu’aucune fiche produit n’annonce

Des paquets installés à chaque démarrage

C’est le constat principal, et il n’était pas cherché.

Le produit exécute une installation de dépendances à chaque lancement, deux paquets tirés depuis le dépôt public du langage. Sur une machine dont la sortie réseau est ouverte, cela signifie qu’un agent télécharge du code en cours d’exploitation, sans que rien ne le signale, à chaque redémarrage du service.

Ici, la sortie réseau était fermée par une liste blanche stricte. Le comportement s’est donc traduit par une centaine de secondes d’attente de délais d’expiration à chaque lancement et 192 paquets rejetés. Neutralisé par un réglage dédié, le démarrage est passé de 102 secondes à 4,9 secondes.

Le point à retenir dépasse ce produit : c’est la liste blanche qui a rendu le comportement visible. Sans elle, l’installation aurait réussi silencieusement, le démarrage aurait été rapide, et personne n’aurait jamais su qu’un agent tirait du code à chaque réveil. Un contrôle restrictif n’a pas seulement une valeur de protection, il a une valeur de révélateur.

La liste blanche n'a pas seulement protégé, elle a révélé Sortie réseau ouverte Installation de 2 paquets à chaque lancement Elle réussit, sans erreur, sans trace Démarrage rapide Comportement invisible un agent tire du code à chaque réveil, personne ne le sait Liste blanche stricte Même installation, bloquée 192 paquets rejetés par lancement une centaine de secondes d'attente Comportement bruyant, donc trouvé le contrôle sert d'instrument de mesure Temps de démarrage du service 102 s 4,9 s après neutralisation du réglage
Le même comportement produit, selon l'environnement, un démarrage rapide et silencieux ou une anomalie de 102 secondes impossible à ignorer.

Trois réglages là où la documentation en donne un

Empêcher un agent de fabriquer lui-même de nouvelles compétences semblait tenir en une option, celle qui est documentée et présente dans la configuration livrée.

Il en faut trois. Les deux autres sont deux chemins autonomes de mise à jour de la bibliothèque de compétences ; ils sont actifs par défaut, absents de la configuration livrée, et l’un d’eux est conçu pour continuer en cas d’erreur plutôt que de s’arrêter. Ne désactiver que l’option documentée aurait donné une fausse assurance : l’inventaire aurait pu bouger malgré un réglage explicitement posé pour l’en empêcher.

Les trois, relevés dans la configuration de la machine de POC, qui tourne encore :

POC Hermes | configuration de l'hote d'evaluation
skills:
  creation_nudge_interval: 0      # le seul que la documentation mentionne
auxiliary:
  background_review:
    enabled: false                # actif par defaut, et concu pour continuer en cas d'erreur
curator:
    enabled: false                # actif par defaut lui aussi

Le résultat se vérifie du dehors, sur l’inventaire lui-même :

POC Hermes | inventaire des competences
$ ls -A ~hermes/.hermes/skills/ | wc -l
0
$ ls -ld ~hermes/.hermes/skills/
dr-xr-xr-x 2 root root 4096 .../skills/

Zéro entrée, et un répertoire que le compte de service ne peut pas écrire. Le réglage dit ce qui devrait arriver, les droits garantissent ce qui peut arriver.

Détail aggravant : la commande de configuration officielle répond « clé non reconnue » sur l’option documentée, alors que le code la lit correctement. Un avertissement faux est pire qu’un silence, parce qu’il pousse à défaire un réglage juste.

Une panne visible seulement sur le canal qu’on n’avait pas testé

Un paramètre envoyé par la passerelle de messagerie était refusé par notre proxy de modèles. Résultat : la passerelle tombait, et l’interface en ligne de commande fonctionnait parfaitement pendant ce temps.

Un POC validé au terminal seul aurait donc déclaré le produit fonctionnel, et découvert la panne en production, sur le seul canal que les utilisateurs empruntent.

Le correctif retenu a été posé côté agent. L’autre correctif possible, côté proxy de modèles, aurait modifié une ressource partagée de production pour arranger un POC. C’est un arbitrage qu’il vaut la peine d’expliciter : la bonne correction n’est pas la plus rapide, c’est celle dont le rayon d’action est le plus petit.

L’adhérence aux consignes, mesurée plutôt que supposée

L’agent portait un fichier d’identité définissant son rôle, son périmètre et ses interdictions. Sur le fond, la tenue est bonne : confronté à une demande hors périmètre, il a refusé en citant deux interdictions, et a formulé de lui-même la phrase qui résume toute la doctrine, à savoir qu’il n’a pas accès aux fichiers ni au terminal de la machine, et que c’est un contrôle technique qui bloque l’action, pas une décision de sa part.

Sur la forme, c’est une autre affaire. Le même fichier interdisait explicitement le gras et les listes à puces sur le canal de messagerie : l’agent a utilisé les deux, dans chacune de ses réponses. Et sur une consigne de préfixe applicable à tous ses messages, la tenue est de quatre sur quatre sur le canal de messagerie, mais de deux sur cinq en ligne de commande.

La leçon est directement transposable : une instruction de forme donnée en langage naturel n’est pas un mécanisme. Elle est tenue souvent, pas toujours, et pas également selon le canal. Tout ce qui doit être garanti doit sortir du texte et entrer dans le code.

Une consigne n’est pas un contrôle. Une instruction écrite dans une invite est une suggestion adressée à un système probabiliste : elle sera suivie souvent, jamais toujours, et pas également selon le canal. Ce qui doit être garanti se déplace du texte vers le code, ou n’est pas garanti.

Pourquoi Python compte ici

Le langage n’est pas un détail de mise en oeuvre quand on choisit un agent, et il explique une partie de ce qui précède.

Python est l’écosystème dominant de l’IA : les bibliothèques de modèles, les connecteurs, les intégrations y arrivent en premier, et c’est ce qui permet à un produit comme celui-ci d’ajouter un fournisseur ou un canal en quelques jours. La vitesse d’itération est réelle, et elle se voit dans le rythme des versions.

La contrepartie s’est vue dans ce POC, sans qu’on la cherche. Tirer ses dépendances au moment de l’exécution est une pratique courante de cet écosystème : c’est exactement ce que fait l’installation de deux paquets à chaque lancement. Ce qui est banal pour un script d’analyse devient une question de sécurité pour un service qui tourne en continu et qui parle à un modèle. Et le pic à 209 % d’un coeur en génération, contre 18 % pour le runtime compilé que nous avons évalué le lendemain, rappelle que la couche d’exécution a un coût.

Aucune de ces deux choses n’est un défaut du produit. Ce sont les propriétés de l’écosystème dans lequel il vit, et elles se paient ou se neutralisent en connaissance de cause.

Le critère partiel, et pourquoi il n’a pas été arrondi

Le seul critère non pleinement tenu concerne la sortie réseau. Il faut y distinguer deux natures de trafic.

Les actions de l’agent n’ont produit aucun paquet hors de la liste autorisée. Le critère est donc tenu pour l’agent.

Le runtime du produit, lui, émet à chaque démarrage seize paquets vers des résolveurs DNS publics, dans une tentative de découverte de points d’entrée de repli. Ce trafic n’est annoncé nulle part. Bloqué, il n’empêche pas la connexion d’aboutir.

Il se lit encore aujourd’hui dans le journal du service, relevé sur la machine de POC. Les identifiants d’hôte et le nom du canal de messagerie sont remplacés par des chevrons ; le reste est la ligne telle qu’elle a été écrite :

POC Hermes | journal du service, decouverte non declaree
21:57:46 <hote-poc> python[760]: WARNING [<messagerie>] Discovering
                                 <messagerie> API fallback IPs
                                 via DNS-over-HTTPS...
21:57:50 <hote-poc> python[760]: WARNING [<messagerie>] Connecting to
                                 <messagerie> (attempt 1/8)...
21:58:10 <hote-poc> python[760]: WARNING [<messagerie>] Connect attempt 1/8
                                 failed: Timed out...
21:58:11 <hote-poc> python[760]: WARNING [<messagerie>] Connecting to
                                 <messagerie> (attempt 2/8)...

La première ligne est celle qui compte : le produit part interroger des résolveurs publics avant même d’essayer de se connecter, et aucune documentation ne l’annonce. Les suivantes sont l’effet de la liste blanche, qui refuse ce trafic. Vingt secondes d’attente par tentative, huit tentatives : c’est le même mécanisme qui coûtait cent secondes au démarrage.

Le critère est donc tenu pour l’agent et pas pour le produit. Nous l’avons noté partiel plutôt que vert, parce que la distinction entre « l’agent respecte sa politique » et « rien ne sort de cette machine » est exactement celle qu’un client aura en tête, et qu’il vaut mieux la porter soi-même que se la faire opposer.

Ce que je retiens

Un POC doit être mené sur un canal identique à celui de la production. Pas sur la production elle-même, ce serait imprudent : sur une pré-production qui emprunte la même chaîne. Le terminal, lui, ment par omission, parce qu’il ne la traverse pas, et il aurait ici validé un produit en panne.

Fermer la sortie réseau est un instrument de mesure. Le comportement le plus important de cette nuit n’a pas été cherché, il est apparu parce qu’un contrôle strict l’a rendu bruyant.

Une valeur par défaut vaut une décision. Trois réglages là où la documentation en annonce un, deux d’entre eux actifs et non listés dans la configuration livrée : ce qui n’est pas explicitement fermé est ouvert, et personne ne vous le dira.

Une mesure qui passe de justesse se déclare de justesse. Une seconde de marge sur un seuil est une information, pas un détail de présentation.

Ce POC est mis en regard des deux autres runtimes évalués, sur le même matériel et le même modèle, dans le comparatif des trois architectures.

Retour à la liste des articles