En bref
- Problème : je voulais installer Hermès localement sans transformer mon Mac en terrain vague pour un agent qui lit et écrit partout.
- Ce que j’ai testé : une session Codex chargée d’installer Hermès Agent, de le relier à Open WebUI, de le lancer comme service utilisateur macOS, et de limiter les accès filesystem.
- Résultat : l’installation locale fonctionne avec une API sur
127.0.0.1:8642, Open WebUI sur127.0.0.1:3000, un LaunchAgent utilisateur, un workspace writable, et les repos montés en lecture seule. - À retenir : le plus gros du travail n’était pas « installer un agent », mais raccorder
proprement Hermès, Codex CLI, Docker, Open WebUI et
launchdsans perdre les limites de sécurité. - Limite : je n’ai pas prouvé le reboot complet, la voix, la charge, l’usage multi-utilisateur, ni une exposition réseau publique.
Pourquoi j’ai voulu tester ça
Hermès m’intéressait pour une raison très simple : je voulais voir si un agent local pouvait devenir une interface de travail confortable, pas seulement une ligne de commande de plus.
Sur le papier, Hermes Agent coche plusieurs cases utiles : CLI, API compatible OpenAI, mémoire, skills, intégrations, et la possibilité de brancher un frontend comme Open WebUI. Mais mon besoin était plus étroit que la documentation officielle.
Je voulais un Hermès local, piloté par Codex, utilisable depuis une interface web, avec un accès filesystem clair :
- un dossier de travail où Hermès peut écrire ;
- mes repos visibles en lecture seule ;
- des secrets locaux, pas dans l’article ni dans un repo public ;
- un service qui redémarre proprement après un
restart; - aucun port exposé hors de
localhost.
La question n’était donc pas : « est-ce qu’Hermès existe ? » La question était plutôt : « est-ce que Codex peut assembler une installation locale exploitable sans que je perde le contrôle des bords ? »
Ce que Codex a réellement installé
La session a travaillé dans une arborescence locale sanitizée ici comme
/Users/<LOCAL_USER>/repos/Hermes.
Le cœur du setup était celui-ci :
Open WebUI container
-> host.docker.internal:8642/v1
-> Hermes Gateway API
-> Codex CLI app-server
-> backend Codex/ChatGPT
La version locale vérifiée était Hermes Agent v0.15.1 (2026.5.29), avec
codex-cli 0.130.0. Open WebUI tournait dans Docker, depuis l’image
ghcr.io/open-webui/open-webui:main.
La partie intéressante n’était pas une seule commande magique. Codex a dû construire une petite installation autour d’Hermès :
/Users/<LOCAL_USER>/repos/Hermes
├── AGENTS.md
├── README.local.md
├── allowed-folders.yaml
├── app/
│ ├── agent/transports/codex_app_server.py
│ └── gateway/platforms/api_server.py
├── bin/
│ ├── hermes-local
│ ├── hermes-service
│ ├── run-hermes-service
│ ├── start-hermes-gateway
│ └── start-open-webui
├── home/
│ ├── .env
│ ├── auth.json
│ ├── config.yaml
│ ├── logs/
│ ├── sessions/
│ └── *.db
├── open-webui/
└── workspace/
app/ contient le code Hermès. bin/ contient les wrappers locaux. home/ contient
l’état, les logs, les sessions, les bases SQLite et les secrets. workspace/ est le
seul espace de travail prévu pour l’écriture.
Ce découpage m’a plu parce qu’il force une séparation simple : ce qui sert à faire tourner Hermès, ce qui contient de l’état sensible, et ce que l’agent peut modifier.
Le chemin heureux
À la fin, le contrôle local ressemble à ça :
bin/hermes-service install
bin/hermes-service status
bin/hermes-service restart
bin/hermes-service logs
bin/hermes-service uninstall
Le service macOS est un LaunchAgent utilisateur :
com.pezzos.hermes.local
Il lance un supervisor local, démarre Hermès Gateway, attend l’API, puis démarre ou
recrée le conteneur Open WebUI. Le conteneur pointe vers Hermès avec
host.docker.internal, ce qui évite de faire écouter l’API sur une adresse réseau plus
large.
Les checks minimaux sont assez directs :
curl http://127.0.0.1:8642/health
curl http://127.0.0.1:3000/
docker ps --filter name=hermes-open-webui
Et le test qui comptait vraiment, après correction, était le streaming :
curl -N \
-H "Authorization: Bearer <API_SERVER_KEY>" \
-H "Content-Type: application/json" \
http://127.0.0.1:8642/v1/chat/completions \
-d '{"model":"gpt-5.5","stream":true,"messages":[{"role":"user","content":"Réponds exactement: OK"}]}'
La trace finale indiquait une réponse SSE avec delta.content, puis un message de
validation : SERVICE RESTART OK.
La première erreur utile : une réponse vide dans Open WebUI
Le bug le plus parlant n’était pas spectaculaire.
Open WebUI affichait des titres et des suivis, mais pas le contenu de la réponse
assistant. C’est le genre de panne qui donne envie d’accuser l’interface, le modèle, ou
le frontend. Codex a vérifié plus bas, avec un curl -N sur
/v1/chat/completions.
Le diagnostic était plus précis : les chunks SSE arrivaient, mais sans delta.content.
Open WebUI recevait donc un flux, mais pas le morceau de texte qu’il attendait pour
afficher la réponse.
La correction a été faite côté Hermès, dans app/gateway/platforms/api_server.py : si
aucun delta texte n’a été émis pendant le streaming, l’API backfill le
final_response.
Ce n’est pas une grande leçon abstraite. C’est juste une bonne trace de terrain : brancher une API « compatible OpenAI » à une interface ne veut pas dire que tous les détails de streaming tombent juste du premier coup.
La deuxième erreur utile : launchd ne trouvait pas Codex
Le deuxième problème venait de macOS.
Lancé à la main, Codex était disponible. Lancé depuis le service, Hermès tombait sur :
FileNotFoundError: [Errno 2] No such file or directory: 'codex'
Ce n’était pas un problème Hermès au sens strict. C’était un problème d’environnement :
un service launchd n’a pas le même PATH qu’un shell interactif. Homebrew peut être
visible dans mon terminal et invisible pour le LaunchAgent.
La correction a été d’arrêter de supposer que codex serait trouvable partout. Le code
résout maintenant le binaire dans cet ordre :
HERMES_CODEX_BIN;codexsur lePATH;/opt/homebrew/bin/codex;/usr/local/bin/codex.
Ce détail vaut presque plus que l’installation elle-même. Dès qu’un agent local devient un service, les petits implicites de shell deviennent des bugs de production locale.
Le cloisonnement qui m’importait
Je ne voulais pas donner à Hermès un accès en écriture à tous mes repos.
La configuration finale garde une règle simple :
/workspace -> workspace/ # write
/output -> home/cache/documents/ # write
/mnt/repos -> /Users/<LOCAL_USER>/repos # read-only
En clair : Hermès peut travailler dans son workspace, produire des documents dans une sortie dédiée, et lire mes repos. Pour écrire ailleurs, il faudrait changer explicitement la configuration.
C’est ce que je veux garder de cette expérience. Le confort d’un agent local ne doit pas dépendre d’un accès large par défaut. Un bon setup doit dire où l’agent peut écrire avant que l’agent commence à écrire.
La sécurité, sans faire semblant
Le setup est local. Les deux ports importants écoutent sur localhost :
127.0.0.1:8642pour l’API Hermès ;127.0.0.1:3000pour Open WebUI.
Il n’y a pas de TLS, pas de reverse proxy, pas d’exposition publique. C’est acceptable pour ce que j’ai testé, précisément parce que je n’ai pas ouvert ces ports à l’extérieur.
Les secrets restent locaux :
home/auth.jsoncontient l’auth Codex, avec permissions600;home/.envcontient des variables sensibles éventuelles, avec permissions600;home/config.yamlcontientAPI_SERVER_KEYen clair ;- Open WebUI reçoit cette clé via variable d’environnement Docker.
Il y a des garde-fous utiles : home/ en 700, fichiers sensibles en 600, repos
montés en lecture seule, workspace writable limité. Mais ça ne transforme pas
l’installation en système durci.
Je ne copierais pas aveuglément quatre choses :
- réutiliser une auth Codex personnelle sur une machine partagée ;
- laisser une API key en clair si le contexte dépasse un poste local personnel ;
- exposer
3000ou8642sur le réseau ; - ajouter des volumes writable larges pour « gagner du temps ».
Le setup marche parce qu’il reste local. Si je voulais le rendre accessible depuis un autre appareil, il faudrait repenser l’auth, le réseau, les logs et les permissions. Je ne traiterais pas ça comme une simple option Docker à ajouter.
Ce qui a manqué
Il y a plusieurs choses que je n’ai pas prouvées.
Je n’ai pas fait de reboot complet du Mac. Le service redémarre après restart, mais je
n’ai pas encore validé toute la séquence login, Docker Desktop, LaunchAgent, API, WebUI.
Je n’ai pas testé la voix. La documentation Hermès mentionne des fonctionnalités TTS, mais ce run ne prouve pas un usage voix de bout en bout.
Je n’ai pas testé la charge, les longues sessions, les appels concurrents depuis Open WebUI, ni un vrai usage multi-utilisateur.
Je n’ai pas non plus figé Open WebUI sur une version propre. L’image utilisée était
ghcr.io/open-webui/open-webui:main. Pour un setup reproductible, je préférerais une
version ou un digest assumé, pas un tag mouvant.
Enfin, je n’ai pas audité les logs, bases SQLite, sessions et plists comme s’ils devaient être publiés. Pour un article, la trace sanitizée suffit. Pour un repo public, ce serait un autre niveau de nettoyage.
Ce que je retiens
Je pensais que l’article raconterait surtout une installation automatisée. En réalité, ce que je veux garder, c’est la liste des interfaces qui ont dû devenir explicites.
Hermès devait parler à Open WebUI dans le bon format. Open WebUI devait joindre l’API
depuis Docker. launchd devait trouver Codex sans hériter de mon shell. Le workspace
devait être writable, mais les repos non. Les secrets devaient rester locaux. Les logs
ne devaient pas devenir des sources publiques par accident.
C’est là que Codex a été utile. Pas parce qu’il a lancé une commande d’installation à ma place, mais parce qu’il a pu suivre les erreurs une par une, vérifier les endpoints, modifier les wrappers, corriger le streaming et rendre le service redémarrable.
Le résultat est partiel, mais concret : une installation locale d’Hermès utilisable depuis Open WebUI, lancée comme service utilisateur, avec des limites visibles.
Ce que tu peux reprendre
Si tu veux refaire quelque chose de similaire, je garderais surtout la méthode :
- installer localement d’abord, sans exposition réseau ;
- vérifier
/health,/v1/modelset/v1/chat/completionsavant de juger l’UI ; - tester le streaming avec
curl -N, pas seulement dans le navigateur ; - lancer le service depuis
launchdtôt, pour voir les différences dePATH; - séparer
home/,workspace/, repos en lecture seule et logs ; - écrire un
uninstallavant de considérer le setup fini.
Ce que je ne reprendrais pas sans réfléchir : l’image Open WebUI main, la clé API en
clair dans une config personnelle, et l’idée d’exposer le service hors localhost.
Pour moi, Hermès devient intéressant à partir du moment où il reste inspectable. Pas quand il sait tout faire, mais quand je peux dire précisément où il tourne, ce qu’il peut lire, ce qu’il peut écrire, comment il tombe, et comment je l’arrête.
Le repo compagnon
Le vrai complément de cet article ne devrait pas être mon dossier local. Il contient une auth Codex, une config avec clé API, des logs, des bases SQLite et des chemins personnels. Ce n’est pas partageable, et ce ne serait pas sain de demander à quelqu’un de le recopier.
L’artefact utile est plutôt un petit repo compagnon macOS, encore local, avec deux
usages : garder le prompt Codex qui installe Hermès avec les garde-fous, et fournir des
scripts/templates pour rejouer le setup sans réinventer launchd, Open WebUI, les
checks et l’uninstall.
Son ossature actuelle ressemble à ça :
prompts/codex-install-hermes-local.md
prompts/codex-verify-hermes-local.md
scripts/bootstrap-hermes-local.sh
scripts/check-hermes-local.sh
scripts/hermes-service.sh
templates/allowed-folders.yaml
templates/com.pezzos.hermes.local.plist.template
templates/hermes.env.example
RESULTATS.md
Le repo local passe déjà les checks statiques et les dry-runs. Il n’est pas encore publié. Tant qu’il reste local, cet article demeure un retour d’expérience, pas une base reproductible à copier.