--- title: 'Hermes Agent : un agent autonome sur Telegram' url: https://blog.guigpap.com/fr/hermes/ url_md: https://blog.guigpap.com/fr/hermes.md category: hermes date: '2026-08-04' maturite: production techno: - docker - n8n - telegram - odoo application: - ai - knowledge - operations --- # Hermes Agent : un agent autonome sur Telegram > Agent Hermes (Nous Research) en conteneur, interface Telegram unique, 32 outils MCP servis par N8N et mémoire longue Mnemosyne ## 1. Quoi ? — Définition et contexte **Hermes Agent** est un agent conversationnel autonome publié par [Nous Research](https://hermes-agent.nousresearch.com/) sous licence MIT. Contrairement aux briques IA déjà en place sur le VPS — qui sont des *passerelles* appelées par des workflows — Hermes est un **processus long qui garde le fil** : il conserve sa mémoire entre les conversations, choisit lui-même quels outils appeler, et n'a besoin d'aucun workflow pour décider quoi faire. Sur cette infrastructure, il tourne dans un conteneur de l'`ai-stack` avec **un bot Telegram dédié comme seule interface**. Aucun port publié, aucune route Caddy, aucun dashboard. ### Composants | Brique | Détail | |--------|--------| | **Conteneur** | `nousresearch/hermes-agent:latest` — aucun port publié, réseaux `n8n-internal` + `mcp-backend` | | **LLM** | Abonnement ChatGPT/Codex via le provider natif `openai-codex` (`gpt-5.5`) | | **Outils métier** | 32 outils MCP servis par le workflow N8N `Hermes MCP Tools` | | **Outils debug** | 4 outils N8N en lecture seule (`n8n-mcp`), réservés au diagnostic de workflows | | **Mémoire** | Provider externe Mnemosyne — SQLite local, recherche hybride vecteur + FTS5 | | **Plugins** | 3 plugins maison greffés sur les hooks du gateway | > **Note - Agent, pas passerelle** > > `cli-ollama` traduit une requête HTTP en un appel CLI et rend la main : rien ne persiste entre deux appels côté modèle. Hermes fait l'inverse — il maintient un état (sessions, mémoire, profil utilisateur) et décide de son propre parcours d'outils. Les deux coexistent sur le VPS et ne servent pas les mêmes usages. ### Architecture visuelle ```mermaid flowchart TD TG(["Telegram · bot dédié · allowlist stricte"]) subgraph Cont["Conteneur hermes · aucun port publié"] direction TB GW["Gateway Hermes · gpt-5.5 via openai-codex"] MEM["Mnemosyne · SQLite + FTS5 · /opt/data"] PLG["Plugins · voice, message editor, usage alert"] GW --- MEM GW --- PLG end subgraph N8N["N8N"] direction TB Trig["MCP Server Trigger · /mcp/hermes-tools"] Adm["n8n-mcp · 4 outils diagnostic"] end subgraph Cibles["Cibles métier"] direction TB Odoo["Odoo · projets, tâches, contacts, CRM, timesheets"] Dock["Docker · status, logs, start/stop/restart"] Prom["Prometheus · alertes, PromQL"] Vault["Vault Obsidian vps-vault · lecture + écriture confinée"] end Plaud["Plaud · serveur MCP officiel · stdio"] TG <--> GW GW -->|"Bearer · flow normal · 32 outils"| Trig GW -.->|"Bearer · debug uniquement"| Adm GW -->|"MCP stdio · hors N8N"| Plaud Trig --> Cibles ``` ### Dans cette section Cet article couvre le déploiement : conteneur, configuration, modèle de sécurité et exploitation. Trois articles détaillent ce qui s'y greffe. | Article | Contenu | |---------|---------| | [Les 32 outils MCP](/fr/hermes/outils-mcp/) | Catalogue complet, gateways à entrées typées, écriture confinée dans le vault, pièges de la couche MCP N8N | | [La mémoire longue Mnemosyne](/fr/hermes/memoire-mnemosyne/) | Mode wrapper, recherche hybride, 20 outils de mémoire, consolidation et exploitation | | [Les skills sur mesure](/fr/hermes/skills/) | Le système de skills et les douze procédures écrites pour ce déploiement | | [Les plugins et leurs hooks](/fr/hermes/plugins/) | Quatre plugins Python greffés sur le cycle de vie du gateway | --- ## 2. Pourquoi ? — Enjeux et motivations ### Six décisions structurantes Prises en session de cadrage avant la première ligne de configuration, elles expliquent la quasi-totalité de la forme du déploiement. | Décision | Raison | |----------|--------| | LLM **en direct** via `openai-codex`, sans passer par `cli-ollama` | L'abonnement est consommé nativement par le provider Hermes. Interposer la passerelle ajouterait un saut, un timeout et une perte de contexte pour zéro gain | | Accès N8N en **double canal** | Un canal d'outils métier curés, un canal de diagnostic en lecture seule. La séparation est explicite, pas implicite | | **Bot Telegram uniquement** | Pas de port, pas de route Caddy, pas de dashboard : la surface d'attaque HTTP est nulle | | Service dans `ai-stack/docker-compose.yaml` | Cohabitation avec Qdrant et `cli-ollama`, mêmes réseaux internes, même cycle de vie | | **Réactif seulement** en v1 | Le scheduler et les automations restent désactivés : un agent qui agit sans qu'on le lui demande est difficile à auditer tant qu'on n'a pas confiance dans ses garde-fous | | Data dir dans le **backup quotidien** | Toute la valeur accumulée (mémoire, sessions, skills) vit dans un seul répertoire — le perdre, c'est repartir de zéro | ### Pourquoi deux canaux MCP plutôt qu'un seul ? Le serveur MCP administrateur (`n8n-mcp`) expose **24 outils**, dont `n8n_delete_workflow` et `n8n_manage_credentials`. Les brancher tels quels reviendrait à donner à l'agent les clés de l'automatisation entière pour lui permettre de lire un log d'exécution. Le déploiement les sépare donc : - **`n8n-tools`** — le flow normal. Des outils métier écrits à la main, à entrées typées, exposés par un workflow N8N dédié. C'est ce que l'agent utilise 99 % du temps. - **`n8n-admin`** — filtré à **4 outils de diagnostic en lecture seule** (`n8n_list_workflows`, `n8n_get_workflow`, `n8n_executions`, `n8n_validate_workflow`), et réservé par une règle du system prompt au seul débogage de workflows. ### Pourquoi une mémoire externe ? La mémoire native de Hermes est un fichier `MEMORY.md` réinjecté dans le prompt. Simple, mais elle plafonne : pas de recherche, et un budget de caractères qui force à arbitrer entre tout garder et rester lisible. Mnemosyne remplace ce mécanisme par une base SQLite locale avec recherche hybride (vecteur + FTS5) et capture en tâche de fond à chaque tour. Local-first : aucune donnée de conversation ne quitte le VPS pour être vectorisée. --- ## 3. Comment ? — Mise en œuvre technique ### Le service Docker ```yaml hermes: image: nousresearch/hermes-agent:latest container_name: hermes command: gateway run networks: - n8n-internal # MCP Server Trigger (n8n:5678) - mcp-backend # n8n-mcp:3000 (debug uniquement) environment: - PUID=${CLAUDE_USER_ID:-1000} - PGID=${CLAUDE_GROUP_ID:-1000} - TELEGRAM_BOT_TOKEN=${HERMES_TELEGRAM_BOT_TOKEN} - TELEGRAM_ALLOWED_USERS=${HERMES_ALLOWED_CHAT_ID} - HERMES_N8N_MCP_TOKEN=${HERMES_N8N_MCP_TOKEN} - N8N_MCP_AUTH_TOKEN=${N8N_MCP_AUTH_TOKEN} volumes: - ./hermes/data:/opt/data security_opt: - no-new-privileges:true cap_drop: - ALL cap_add: - CHOWN - SETUID - SETGID - DAC_OVERRIDE deploy: resources: limits: memory: 3G cpus: '2' healthcheck: test: ['CMD-SHELL', 'pgrep -f "bin/hermes[ ]gateway run" > /dev/null || exit 1'] interval: 30s ``` Aucune section `ports` : c'est volontaire et c'est le cœur du modèle de sécurité. Le seul chemin entrant est le long-polling Telegram sortant. > **Caution - cap_drop et s6-overlay** > > Le jeu `CHOWN, SETUID, SETGID` seul casse le boot : s6-supervise boucle sur « unable to open supervise/lock: Permission denied ». L'image utilise s6-overlay, dont le stage 2 tourne en root pour remapper PUID/PGID — sans `DAC_OVERRIDE`, root ne peut plus toucher aux fichiers appartenant à l'utilisateur remappé. Le jeu minimal validé sur conteneurs jetables est **`CHOWN, SETUID, SETGID, DAC_OVERRIDE`** ; `FOWNER` et `KILL` sont inutiles. > **Tip - Le crochet du healthcheck** > > Le motif `bin/hermes[ ]gateway run` s'écrit avec une classe de caractères pour une raison précise : sans elle, `pgrep -f` matche aussi le shell qui exécute le healthcheck lui-même, et le conteneur se déclare sain quel que soit l'état du gateway. ### La configuration Hermes génère et migre son `config.yaml` tout seul au premier démarrage ; seules les clés de ce déploiement sont reportées à la main. ```yaml model: provider: "openai-codex" default: "gpt-5.5" base_url: "https://chatgpt.com/backend-api/codex" mcp_servers: n8n-tools: # flow normal url: "http://n8n:5678/mcp/hermes-tools" headers: Authorization: "Bearer ${HERMES_N8N_MCP_TOKEN}" timeout: 120 n8n-admin: # debug de workflows UNIQUEMENT url: "http://n8n-mcp:3000/mcp" headers: Authorization: "Bearer ${N8N_MCP_AUTH_TOKEN}" tools: include: [n8n_list_workflows, n8n_get_workflow, n8n_executions, n8n_validate_workflow] memory: provider: "mnemosyne" plugins: enabled: - telegram-voice-transcriptor gateway: platforms: telegram: extra: allow_from: - "" # valeur LITTÉRALE obligatoire ``` > **Caution - allow_from ne fait pas d'expansion de variable** > > `gateway.platforms.telegram.extra.allow_from` est lu **brut** par le plugin Telegram : un `${VAR}` n'y est jamais résolu, il est comparé tel quel au chat_id. Pire, dès que cette liste est définie, elle devient la seule autorité et **remplace** l'allowlist passée en variable d'environnement `TELEGRAM_ALLOWED_USERS`. Le premier message réel a été bloqué exactement là-dessus. Le chat_id doit donc être écrit en littéral dans le fichier de config. > **Danger - Ne jamais partager le login Codex** > > Hermes importe automatiquement `~/.codex/auth.json` s'il le trouve. Monter le `~/.codex` du host — ou celui de `cli-ollama` — dans le conteneur provoque des rotations de token concurrentes qui invalident les deux côtés. Le déploiement utilise un **login OAuth dédié**, obtenu une fois par device flow dans le conteneur, et rien d'autre : > > ```bash > docker compose -f ai-stack/docker-compose.yaml exec hermes hermes auth add codex-oauth > ``` ### Le system prompt comme couche de sécurité Certaines règles ne peuvent pas être imposées par la plomberie : elles vivent dans `SOUL.md`, occupant le premier slot du system prompt. - **Flow normal** : toute action métier passe par `n8n-tools`. - **`n8n-admin` = debug uniquement**, jamais pour une action métier, et jamais pour modifier un workflow. - **`docker_manage` = action sensible** : aucune chaîne d'approbation n'existe en aval, donc l'agent doit demander une confirmation explicite avant chaque `start`/`stop`/`restart`/`update`, en nommant la stack et l'action. - **Vault** : toujours citer le chemin du fichier source ; écriture uniquement via `vault_write`, confinée, et jamais de sa propre initiative. - **Pas d'action non sollicitée** : l'agent exécute ce qui est demandé et propose le reste. > **Note - Le prompt n'est pas un contrôle d'accès** > > Ces règles sont des garde-fous comportementaux, pas des barrières techniques. Les vraies barrières sont ailleurs : Bearer token obligatoire, allowlist Telegram fail-closed, filtrage `tools.include` côté `n8n-admin`, whitelist d'actions et blocage des actions destructrices sur `security-stack` codés dans le workflow `Docker Actions`, confinement des chemins dans le gateway vault. Le prompt ajoute une couche de prudence par-dessus ; il ne la remplace pas. ### Ce qui se greffe sur le gateway Trois mécanismes d'extension cohabitent, et la distinction entre eux structure tout le déploiement. | Mécanisme | Nature | Où il vit | Détail | |-----------|--------|-----------|--------| | **Outils MCP** | Nodes N8N | Workflow `Hermes MCP Tools` | [32 outils](/fr/hermes/outils-mcp/) sur 8 domaines | | **Skills** | Markdown | `/opt/data/skills/` | [145 actives](/fr/hermes/skills/), dont 12 sur mesure | | **Plugins** | Python | `/opt/data/plugins/` | [4 plugins](/fr/hermes/plugins/), greffés sur les hooks | La règle de partage : un outil MCP rend une action **possible**, une skill la rend **bien faite**, un plugin la déclenche **sans que l'agent décide**. Et la mémoire longue — [Mnemosyne](/fr/hermes/memoire-mnemosyne/) — s'installe par le troisième mécanisme, en mode wrapper. > **Tip - Tout ce qui doit durer vit dans /opt/data** > > C'est le principe commun aux trois. L'arbre applicatif `/opt/hermes` appartient à l'image : ce qu'on y modifie disparaît à la prochaine mise à jour, silencieusement. Skills, plugins, venv Mnemosyne, configuration et sessions vivent donc tous dans le bind mount — ils survivent aux recreate comme aux updates, et partent dans le backup quotidien. ### Sécurité | Surface | Protection | |---------|-----------| | Endpoint MCP N8N | Bearer obligatoire — 403 sans token, vérifié en E2E | | Endpoint MCP depuis Internet | `/mcp/*` et `/mcp-test/*` renvoient 404 côté Caddy ; seul le chemin interne `n8n:5678` fonctionne | | Bot Telegram | Allowlist stricte fail-closed — un chat_id inconnu est bloqué sans réponse | | Actions destructrices Docker | Whitelist d'actions dans `Docker Actions`, `security-stack` non actionnable | | `docker_manage`, `crm_delete_lead`, `timesheet_delete` | Pas de chaîne d'approbation en aval → confirmation exigée par le system prompt | | Écriture vault | Confinée à `research/` et `inbox/`, extension `.md`, anti-traversal, plafond 150 000 octets | | Secrets | Passés par l'environnement compose, jamais écrits dans `/opt/data` (qui part en backup) | Le confinement de `vault_write` mérite un mot, parce qu'il résout un problème classique des agents qui écrivent : l'écrasement à l'aveugle. Une lecture via `vault_read` renvoie un `contentHash` ; modifier un fichier existant exige de repasser ce hash en `Base_Hash`. Sans hash, la réponse est `__EXISTS__` ; si le fichier a bougé depuis la lecture, `__STALE_READ__`. Et les erreurs ne renvoient **jamais** le hash courant — sinon le modèle pourrait le récupérer et écraser sans avoir lu. [Détail de l'implémentation](/fr/hermes/outils-mcp/). ### Sauvegarde Le répertoire de données part dans le backup quotidien vers Google Drive, après exclusions : les credentials OAuth (`auth.json`, `google_token.json`, `google_client_secret.json`), les credentials Git, le venv Mnemosyne et les caches de modèles. La base de mémoire, elle, **est** incluse. L'archive tombe ainsi à 8,2 Mo au lieu de 58 Mo. Après une restauration complète, trois gestes manuels : refaire le device flow Codex, refaire le flow OAuth Google, réinstaller le venv Mnemosyne. ### Commandes d'exploitation ```bash docker exec hermes hermes mcp test n8n-tools # connexion MCP live + liste d'outils docker exec hermes hermes auth list # credential OAuth chargé ? docker exec hermes hermes plugins list # plugins enabled/disabled docker exec hermes hermes memory status # provider mémoire actif docker exec hermes hermes gateway status docker exec hermes hermes -z "ping" # tour LLM one-shot (consomme du quota) ``` > **Danger - Un seul conteneur par répertoire de données** > > Deux conteneurs Hermes montant le même `/opt/data` corrompent les sessions. Cela vaut aussi pour un conteneur de test resté en vie : c'est le piège qui a suivi la migration du PoC vers la production. --- ## 4. Et si ? — Perspectives et limites ### Limites actuelles | Limite | Impact | Mitigation | |--------|--------|------------| | **Quota ChatGPT partagé** | L'abonnement sert Hermes, Codex CLI et les workflows N8N. Plafond atteint = le bot renvoie des erreurs jusqu'au reset | Plugin `codex-usage-alert` sur seuil ; router les usages non interactifs vers `gemini-flash` | | **Garde-fous portés par le prompt** | `docker_manage` et les suppressions n'ont pas de validation technique en aval | Whitelist d'actions dans `Docker Actions` ; une vraie chaîne d'approbation Telegram reste à câbler | | **v1 réactive** | Aucune tâche planifiée : l'agent ne fait rien sans sollicitation | Volontaire tant que les garde-fous ne sont pas durcis | | **`employee_id` figé** | `timesheet_log_hours` écrit toujours sur l'employé 1 | Sans objet aujourd'hui (un seul employé), à paramétrer si l'équipe grandit | | **Mapping des étapes CRM** | Les IDs d'étapes sont encodés dans les descriptions `$fromAI` | `crm_list_stages` fournit désormais la source dynamique | | **Sous-nodes MCP non désactivables** | Désactiver un outil dans `Hermes MCP Tools` fait exécuter le mauvais node à tous les outils suivants, en silence | Supprimer le node plutôt que le désactiver ([détail](/fr/hermes/outils-mcp/)) | ### Scénarios d'évolution **Si l'agent devient proactif** : - Déposer des jobs dans `/opt/data/cron/` active le scheduler (inactif tant qu'il est vide). - Prérequis : une chaîne d'approbation réelle sur les actions sensibles, pas seulement une règle de prompt. - Candidats naturels : digest matinal des alertes Prometheus, relance des leads CRM dormants. **Si la mémoire doit sortir du VPS** : - Export des mémoires Mnemosyne en notes markdown dans le vault, pour les rendre lisibles et versionnées. - Puis synchronisation bidirectionnelle VPS ↔ poste de travail, pour une mémoire unique entre l'agent et l'assistant local. [Détail](/fr/hermes/memoire-mnemosyne/). **Si le périmètre d'écriture s'élargit** : - Le confinement `research/` + `inbox/` est une liste blanche de préfixes : l'étendre est trivial, mais chaque nouveau préfixe doit venir avec sa justification. - La séquence commit + push immédiat propage déjà vers les clients Obsidian ; augmenter le débit d'écriture augmentera le bruit dans l'historique Git. **Si un second utilisateur doit y accéder** : - L'allowlist accepte plusieurs chat_id, mais la mémoire et le profil utilisateur sont globaux au conteneur. - Il faudrait un conteneur par utilisateur (répertoires de données distincts) plutôt qu'une allowlist élargie. ### Commandes de dépannage ```bash # Le bot ne répond pas docker logs hermes --tail 100 docker inspect hermes --format='{{.State.Health.Status}}' # Message ignoré → allowlist docker logs hermes 2>&1 | grep -i "unauthorized" # Outils MCP absents ou périmés docker exec hermes hermes mcp test n8n-tools docker compose -f ai-stack/docker-compose.yaml restart hermes # Mémoire indisponible docker exec hermes hermes memory status docker exec --user hermes -e HOME=/opt/data -e HERMES_HOME=/opt/data hermes \ /opt/data/.mnemosyne/venv/bin/mnemosyne stats # Vérifier que l'endpoint MCP reste bien fermé de l'extérieur curl -so /dev/null -w '%{http_code}\n' https://n8n.guigpap.com/mcp/hermes-tools # attendu : 404 ``` --- ## Pages liées ### Hermes - [Les 32 outils MCP](/fr/hermes/outils-mcp/) — Catalogue, gateways et écriture confinée - [La mémoire longue Mnemosyne](/fr/hermes/memoire-mnemosyne/) — Mode wrapper, rappel hybride - [Les skills sur mesure](/fr/hermes/skills/) — Douze procédures écrites pour ce déploiement - [Les plugins et leurs hooks](/fr/hermes/plugins/) — Quatre extensions Python du gateway ### Infrastructure - [AI Stack](/fr/infrastructure/ai-stack/) — Qdrant, CLI Ollama et la gateway MCP - [Architecture VPS](/fr/infrastructure/architecture-vps/) — Vue d'ensemble et topologie réseau - [Security Stack](/fr/infrastructure/security-stack/) — Caddy, blocage externe des endpoints internes - [Backup des bases de données](/fr/infrastructure/database-backup/) — Sauvegarde du répertoire de données ### Workflows - [Content Pipeline](/fr/workflows/content-pipeline/) — Le vault Obsidian `vps-vault` et ses miroirs - [Système conversationnel](/fr/workflows/systeme-conversationnel/) — L'autre agent Telegram, via CLI Ollama - [Codex CLI Integration](/fr/workflows/codex-cli-integration/) — Le login Codex de `cli-ollama`, distinct de celui d'Hermes - [Error Handler](/fr/workflows/error-handler/) — Workflow d'erreur des gateways ### Référence - [Glossaire](/fr/reference/glossary/) — MCP, Agent autonome, Mémoire à long terme, LLM ## Métadonnées agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0