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 sur 127.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 launchd sans 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 :

  1. HERMES_CODEX_BIN ;
  2. codex sur le PATH ;
  3. /opt/homebrew/bin/codex ;
  4. /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:8642 pour l’API Hermès ;
  • 127.0.0.1:3000 pour 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.json contient l’auth Codex, avec permissions 600 ;
  • home/.env contient des variables sensibles éventuelles, avec permissions 600 ;
  • home/config.yaml contient API_SERVER_KEY en 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 3000 ou 8642 sur 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 :

  1. installer localement d’abord, sans exposition réseau ;
  2. vérifier /health, /v1/models et /v1/chat/completions avant de juger l’UI ;
  3. tester le streaming avec curl -N, pas seulement dans le navigateur ;
  4. lancer le service depuis launchd tôt, pour voir les différences de PATH ;
  5. séparer home/, workspace/, repos en lecture seule et logs ;
  6. écrire un uninstall avant 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.

Sources