aller au contenu
FRFlo
Table des matières

An AI agent is more than a model that receives a prompt and returns text. As soon as it needs to stay available, respond through a messaging platform, access tools, and keep an execution environment, it becomes a distributed system.

In my infrastructure, Hermes Agent acts as the gateway and orchestrator. It receives Discord messages, manages conversations, and calls the configured model. External capabilities are not added directly to Hermes: they go through Executor, a self-hosted integration layer that exposes an MCP endpoint.

This separation is the key architectural idea. Hermes reasons and communicates; Executor centralizes access to tools; MCP servers provide specialized capabilities. Everything runs in a Docker stack, with services isolated from direct external access.

The three layers

flowchart LR
    User["Discord user"] -->|message| Hermes["Hermes Agent\ngateway run"]
    Hermes -->|model call| Models["AI model"]
    Hermes -->|MCP HTTP + OAuth| Executor["Executor\nHermes MCP toolkit"]
    Executor --> GTFS["GTFS-MCP"]
    Executor --> Moodle["Moodle-MCP"]
    Executor --> Storage["Executor /data\npersistent volume"]

Hermes: the agent layer

Hermes runs from the nousresearch/hermes-agent:latest image. The container starts with gateway run, rather than as a simple interactive session. This distinction matters: the process stays alive, listens for Discord events, and maintains conversation sessions.

Its configuration is mounted from config/hermes/config.yaml to /opt/data/config.yaml. It describes:

  • the model provider;
  • the /workspace working directory;
  • the Discord interface and its authorization rules;
  • dedicated system message and status notification routing (system_channel);
  • the persistent Google Chrome browser endpoint (browser.cdp_url: "http://chrome:9222");
  • the remote MCP server Hermes must connect to.

The Discord gateway is deliberately restricted. The configuration allows one specific user and channel, disables general mentions, and does not require the bot to be mentioned in that channel. This layer is powered by the native hermes-discord-py adapter plugin (plugins.enabled: [discord-frflo]), providing robust real-time synchronization:

  • Strict platform identity alignment (Platform("discord-frflo")): rather than a generic Platform.DISCORD fallback, the adapter is initialized and registered matching the configured platform name (discord-frflo). This ensures session keys, command routing, and pre-dispatch checks align reliably without message drops or dispatch ambiguities.
  • Clutter-free channels & silent reasoning (show_reasoning: false): the model’s internal chain-of-thought is suppressed both globally and on Discord. To maintain a clean chat UI and prevent layout glitches or accidental interaction states, reasoning display buttons have been completely removed from the adapter.
  • Silenced intermediate execution noise (tool_progress: 'off'): verbose tool execution streaming is turned off (tool_progress: 'off', tool_preview_length: 0), allowing Hermes to chain multi-step tool calls silently before posting its complete, verified answer.
  • Stream reliability and message integrity: continuous WebSocket connectivity, heartbeat and latency tracking, routed logging towards a dedicated system channel (system_channel), automatic outbound link preview suppression (suppress_embeds), robust incremental editing (msg.edit), and markdown-preserving chunking for long outputs.

Alongside interactive chat, Hermes runs an autonomous cron scheduler backed by a dedicated persistent backup volume (hermes-cron) paired with cross-session persistent memory. It orchestrates dynamic two-stage smart routines: an evening predictive run analyzing upcoming classes via Naurio and public transit schedules via GTFS, which programmatically schedules a minute-accurate one-shot morning alert for wakeup and commute timing.

The model: a separated dependency

Hermes does not host the model. The configuration points to:

model:
provider: custom
default: gemini-3.8-flash-high
base_url: https://<cliproxyapi host>/v1
api_key: not-needed
api_mode: chat_completions
fallback_providers:
- provider: custom
model: gpt-5.6-luna
base_url: https://<cliproxyapi host>/v1
api_key: not-needed
auxiliary:
vision:
provider: custom
model: gpt-5.6-luna
base_url: https://<cliproxyapi host>/v1
api_key: not-needed

The cli-proxy-api service also runs in Docker with separated versioned configuration. It serves as a unified multi-provider facade, decoupling Hermes from specific upstream model implementations.

Executor: the integration layer

Executor runs from ghcr.io/rhyssullivan/executor-selfhost:latest. It centralizes integrations and exposes an MCP surface that Hermes can consume.

Its state is stored in the Docker executor volume, mounted at /data. Persistence matters: integrations, configuration, and service state should not disappear whenever the container is recreated.

Hermes does not reference Executor’s general interface, but a dedicated MCP toolkit:

mcp_servers:
executor:
url: https://<executor-host>/mcp/toolkits/hermes
auth: oauth

The name executor therefore becomes a capability boundary in Hermes. The agent does not necessarily know how each downstream tool is implemented; it discovers a surface published by Executor.

MCP as a capability boundary

The Model Context Protocol standardizes how a client discovers and calls external tools. In this setup, Hermes is the MCP client and Executor is the MCP server visible to Hermes.

Hermes supports both local servers and remote MCP servers. My Executor integration uses a remote endpoint authenticated with auth: oauth.

This organization provides a useful separation:

  1. Hermes keeps the conversation and decision-making logic.
  2. Executor presents a consistent tool surface to the agent.
  3. Specialized servers remain independent services.
  4. Integration credentials and policies can be centralized in Executor.

The Hermes configuration explicitly declares only Executor. Executor is therefore responsible for presenting Hermes with the unified list of available integrations and tools.

The MCPs exposed to Hermes

These are the integrations currently exposed by Executor’s Hermes toolkit:

Integration Executor identifier Role in the setup
Naurio naurio_mcp Academic timetable and schedule planning tools.
GTFS gtfs Public transit routes and schedule analysis.
Open-Meteo open-meteo Weather forecasts and historical meteorological data.
Google Maps google-maps Geocoding, places, routes, traffic, and timezone data.
Moodle moodle Integration with the digital learning platform.
oCIS & Qdrant RAG ocis_qdrant_indexer Real-time semantic vector retrieval across ownCloud document storage.
ownCloud Infinite Scale ocis_mcp_server File management, share administration, metadata, and workspace tools (80+ tools).
Dockhand dockhand_api Management and orchestration of the VPS Docker stack.
Arkeep arkeep_api Docker volume backup orchestration, snapshot validation, and restore tools.
Cloudflare cloudflare_mcp Management of Cloudflare services and security rules.
Cloudflare Browser Rendering cloudflare_browser_rendering Edge-rendered headless web crawling, DOM snapshots, and content extraction.
Google Chrome google_chrome_mcp Full remote Chrome session with DevTools Protocol and VNC visual control.
OpenLegi (Légifrance, RNE, BODACC, BOFiP) openlegi_* Access to French legal codes, case law, company registry, and tax doctrine.
French Open Data (Data.gouv) data_gouv_mcp Querying and analyzing French open data catalogs.
Pocket ID pocket_id_api Interaction with the OIDC/SSO identity provider.
GitHub github_graphql Inspecting and managing repositories, issues, and pull requests.
Pelican Application / Admin pelican_api_application Administration of the Pelican panel and its nodes.
Pelican Client pelican_api_client Control of servers hosted through Pelican.
Spotify spotify Currently playing track, search, and playlist management.
Firecrawl firecrawl_mcp Scraping, content extraction, and website crawling.
GrepApp grep_mcp Searching code across public repositories.
Excalidraw excalidraw_app_demo Creating diagrams, mockups, and whiteboards.
Context7 context7_mcp Looking up context and documentation for the tools in use.

This list gives a better idea of what it means to give Hermes tools. The agent does not merely get access to one utility: it gets a cross-functional catalog covering daily life, infrastructure, development, documentation, and diagram creation.

The boundary remains clear: Hermes decides when to use a tool and presents the result in the conversation; Executor handles the integrations and required permissions; each service keeps its own domain logic.

Specialized MCP servers

GTFS-MCP

gtfs-mcp runs from the ghcr.io/frflo/gtfs-mcp:latest image. Its configuration is mounted read-only from config/gtfs-mcp/config.json, and its persistent data lives in the gtfs-data volume mounted at /data.

For example, GTFS can work with data from SNCF Voyageurs and Île-de-France Mobilités. It illustrates the value of a specialized MCP server: transport data becomes agent-friendly tools without adding that domain logic to Hermes itself.

Moodle-MCP

moodle-mcp is another internal MCP server, built from ghcr.io/frflo/moodle-mcp:latest. It is not directly exposed externally.

Its Moodle endpoint and token are injected through environment variables. The token does not appear in versioned configuration. The service is therefore separate from the gateway: Hermes does not need to know Moodle’s implementation details, and secrets remain in the deployment layer designed for them.

Isolation and security

The components are isolated from direct external access. Internal services are not exposed as public ports; whenever external access is required, it goes through the infrastructure’s control and authentication mechanisms.

This separation reduces the attack surface and means that every MCP server does not have to become a public application. Hermes sees only the tools exposed to it by Executor, while specialized services remain behind that boundary.

The path of a request

A typical interaction follows this conceptual chain:

  1. A message arrives in the authorized Discord channel.
  2. The Hermes gateway checks the user and channel.
  3. Hermes restores the corresponding session and prepares the context.
  4. The model analyzes the request and may choose a tool.
  5. Hermes sends the call to Executor’s MCP surface.
  6. Executor applies its integration logic and may forward the call to a specialized server.
  7. The result returns to the Hermes loop, which can continue reasoning.
  8. The final response is sent to Discord.

The model therefore does not become a direct administrator of the VPS. It operates within a set of tool surfaces exposed by Hermes and Executor. The quality of the system depends as much on this capability selection as on the model itself.

What this architecture enables

The main benefit is decoupling. A new tool can be added downstream without turning Hermes into a monolith. Another MCP client could also consume Executor, while Hermes remains the conversation-oriented entry point through Discord.

Centralization also provides a natural place to handle integrations, their secrets, and their exposure policies. On the other hand, it introduces another dependency: if Executor is unavailable, the tools it publishes are unavailable too, even if Hermes and the model are still working.

Finally, using latest images makes deployment simple but less reproducible. To reproduce this setup exactly, the image versions or digests, Hermes version, and MCP server versions should also be recorded. The current configuration describes an operational architecture, not a complete infrastructure lockfile.

In summary

Hermes Agent is the interaction and orchestration layer: it receives Discord messages, manages sessions, and drives the model. Executor is the MCP gateway that exposes a centralized tool surface to Hermes. Naurio, GTFS, Open-Meteo, Google Maps, Moodle, the oCIS/Qdrant document RAG, the ownCloud Infinite Scale MCP server, Dockhand, Arkeep, Cloudflare, Cloudflare Browser Rendering, persistent Google Chrome (DevTools/VNC/CDP), OpenLegi, Data.gouv, Pocket ID, GitHub, Pelican, Spotify, Firecrawl, GrepApp, Excalidraw, and Context7 form the comprehensive tool catalog available at runtime.

The value of this setup does not come from one component, but from the boundaries between them. The agent can reason without knowing every integration; tools can evolve without rewriting the gateway; and the infrastructure retains control over persistence and exposure.