
Présentation
Hermes Agent, l'agent open source de Nous Research, tourne dans mon homelab. Je voulais l'avoir sur Windows sans navigateur : l'appeler à la voix sans toucher une touche, lui écrire depuis n'importe quelle application. HermesIAssistant est ce client : une application Tauri 2 avec une interface Svelte 5 en TypeScript, un moteur Rust et un programme d'écoute en Python. Le cerveau reste sur le serveur, l'app ne contient aucune clé d'API. CHANGELOG.md décrit chaque version, de la 1.0.0 du 29 septembre 2026 à la 1.20.0.
La présence pendant un appel vocal simulé dans l'aperçu navigateur : l'orbe, l'état « Hermes parle », la phrase dite, le début de la réponse et les boutons pour montrer l'écran, couper le micro et raccrocher.
Une surcouche, un Studio et un moteur Rust
La présence à l'écran est une surcouche transparente, toujours au-dessus et non focusable : elle ne vole jamais le focus d'un jeu, et les clics la traversent sauf sur l'orbe. Le Studio regroupe les conversations, la mémoire éditable de Hermes et les réglages.
L'accueil du Studio, avec les données de démonstration de l'aperçu : conversations, mémoire et projets à gauche, boutons d'action sous le champ, et en bas l'état de Hermes, de la voix et du mot d'appel.
Le moteur Rust applique lui-même les réglages : l'interface ne fait qu'écrire reglages.json, et appliquer_reglages l'applique. Les requêtes vers le homelab passent par une seule entrée nginx, qui ajoute les clés côté serveur ; son certificat auto-signé est embarqué dans l'app (client_entree). Derrière, un service mémoire en Python (memoire-api.py) sert le wiki de Hermes et les relais.
« Hey Hermes » sans rien envoyer avant le mot
Le mot d'appel tourne en local avec Vosk et son petit modèle français. Le reconnaisseur est restreint à une grammaire (grammaire.py) : des formules (« Hey », « Ok », « Dis »…) suivies de « Hermès », plus des leurres comme « hermine » ou « Thermos ». Sans eux, tout ce qui ressemblait un peu devenait « Hermès ».
J'ai réglé les seuils sur un banc d'essai (eveil/banc/) : 300 sons de synthèse, dont 180 phrases pièges. Le réglage du premier prototype donnait 26 fausses alertes sur 180. Avec les leurres, la règle « formule juste avant Hermès » et le seuil retenu (confiance 0,63, durée 0,30 s), il en reste 3. Quand le PC joue du son sur ses haut-parleurs, le critère se durcit : le moteur lit le niveau de sortie de Windows, sans capter l'audio.
Des fichiers qui reviennent, des dossiers prêtés
Depuis la 1.20.0, quand Hermes crée un fichier, il finit sa réponse par une ligne Fichier rendu : …, que l'app remplace par une carte : Aperçu, Ouvrir, Enregistrer sous. Le serveur ne sert que deux dossiers, vérifiés après résolution des liens (fichier_permis). L'aperçu d'un Word passe par mammoth, puis par assainir : liste blanche de balises, aucun attribut hormis les images intégrées et la fusion de cellules, liens réduits à du texte.
Avec Cowork, j'ajoute un dossier du PC à une conversation : Hermes peut le lister, y lire des fichiers et, avec mon accord, y écrire. Je le choisis dans une boîte de Windows ouverte par Rust ; Hermes n'en connaît que le nom. Sa demande passe par une compétence, un relais protégé par un jeton (hmac.compare_digest) et une attente longue de l'app. Rust vérifie chaque chemin relatif (extrait simplifié de composants) :
if c.starts_with(['/', '\\']) || c.contains(':') {
return Err(CHEMIN_INVALIDE);
}
let c = c.trim_end_matches(['/', '\\']);
for e in c.split(['/', '\\']) {
if e.is_empty() || e == "." || e == ".." {
return Err(CHEMIN_INVALIDE);
}
if nom_reserve(e) {
return Err(NOM_RESERVE);
}
}chemin_sur refuse ensuite les liens symboliques, les jonctions, les raccourcis .lnk et les noms courts 8.3. La racine d'un disque, Windows, AppData ou le profil entier ne peuvent pas être ouverts. Chaque écriture passe par une carte d'accord : Autoriser, Refuser, ou Toujours dans ce dossier (pour la conversation, jusqu'à la fermeture de l'app). L'ancienne version est copiée dans .hermes-anciennes-versions, puis l'écriture est atomique. Rien n'est exécuté ni supprimé.
Un agent de code derrière un filtre
Hermes Code (1.17 à 1.19) reprend les outils de Claude Code : modes, plan, tâches en arrière-plan, revue. Le moteur est OpenAI Codex 0.160.0 en mode app-server (JSON-RPC). L'app vérifie la taille et l'empreinte SHA-256 de chaque fichier du moteur (FICHIERS_MOTEUR). Ce moteur ne parle qu'à un relais local, protégé par un jeton tiré à chaque lancement.
La page y est traitée comme non fiable. Rust ne transmet que 23 méthodes du protocole, avec des clés permises par méthode (code_moteur_regles.rs). Il n'accepte une réponse d'accord que pour une demande encore en attente, une seule fois. « Ignorer les permissions » se confirme dans une boîte de Windows, qu'une page compromise ne peut pas imiter.
Essai réel des réponses d'accord, moteur Codex en mode Manuel : la ligne regle-toujours est ajoutée, l'ajout suivant est refusé et Hermes ne le retente pas.
Le côté serveur, sur Linux dans le homelab
Hermes Agent tourne dans mon homelab, dans un conteneur LXC Debian sous Proxmox. L'agent y est lancé en conteneur Docker, redémarré automatiquement, limité en mémoire et en processeur, avec ses données sur un volume persistant. Le service mémoire est une unité systemd qui démarre après le réseau et Docker, et repart seule s'il s'arrête. Le script d'installation travaille en set -euo pipefail et umask 077 : les fichiers de secrets sont créés en chmod 600 et jamais affichés.
Les mises à jour passent par un script lancé depuis l'hôte Proxmox. Il pousse chaque fichier dans le conteneur avec ses permissions (pct push), écrit les jetons de façon atomique, puis teste la configuration avec nginx -t. Si le test échoue, l'ancien site est remis et rien n'est rechargé ; sinon nginx est rechargé et le service mémoire redémarré. Devant, l'entrée nginx n'accepte que les postes du réseau local et du VPN, et vérifie le certificat de chaque service qu'elle relaie (proxy_ssl_verify).
Tester, publier, et ce qu'il reste
Les modules isolent leurs règles pures (*_regles.rs, src/lib/), testées sans lancer l'app : 62 fichiers de tests Node, cargo test --lib (310 tests à la 1.19), unittest pour l'écoute et le serveur. Dans un navigateur, pont.ts simule le moteur pour vérifier les écrans. publier.ps1 refuse une version qui ne monte pas ou un dépôt modifié, signe l'installateur, dépose le manifeste en dernier et pose un tag. À l'essai, une fausse mise à jour signée pour une autre version a été refusée.
Une séance Hermes Code dans l'aperçu navigateur, moteur simulé par pont.ts : bilan des fichiers modifiés, différence de style.css et terminal du serveur de développement (adresses locales masquées).
J'ai mené ce projet avec un agent de code IA : je fixais le besoin, je choisissais sur maquettes, je validais les essais réels. De la 1.17 à la 1.19, chaque version a suivi une spec, un plan, puis une relecture de sécurité par un agent séparé. Ces relectures ont trouvé de vrais défauts : en mode Plan, une écriture aurait pu être accordée par le relecteur automatique sans moi. Des limites restent : les accords se donnent encore dans la page, et d'anciennes commandes lui font confiance. Le script de déploiement serveur de la 1.20.0 n'a pas encore été lancé. Si je recommençais, je traiterais la page comme non fiable dès la 1.0.