Repository navigation
Service Hermes
Port: 63072 (API); 63073 is reserved for dashboard compatibility, without a container host publish.
SOURCE variable: HERMES_SOURCE
SOURCE options: container, localhost, disabled
Hermes is a programmable AI-agent runtime from Nous Research. It adds the agent loop between raw LLM chat and channel adapters such as OpenClaw. It exposes an OpenAI-compatible API on container port 8642 and a web dashboard on container port 9119.
Key facts:
-
File-based persistence — all state lives under
/opt/data(thehermes-datanamed volume). Hermes needs no Postgres or Redis. -
No GPU — Hermes orchestrates; the model endpoint in
model.base_urldoes the inference. The stack default is the LiteLLM gateway. - MCP client — Hermes can call any MCP server.
-
64K context floor — Hermes checks the model's context window at
preflight.
HERMES_DEFAULT_MODELmust have a context window of at least 64K. Stock Ollama default contexts depend on VRAM (4K/32K/256K) and are often lower. SetOLLAMA_CONTEXT_LENGTH=65536on the Ollama server, run/set parameter num_ctx 65536+/save <model>insideollama run, or use a cloud model. -
Disk — the image is multi-arch (
linux/amd64,linux/arm64) and large: about 3.7 GB unpacked on arm64. -
Bundled skills — the image ships its skills under
/opt/hermes/skills.hermes-initadds the companion skillcreative/atlas-comfyui-host/SKILL.mdunder/opt/data/skills/. It gives the agent the in-network ComfyUI host; it is guidance, not an override.
| Path | URL | Notes |
|---|---|---|
| OpenAI-compatible API (direct) |
http://localhost:${HERMES_API_PORT} (default 63072) |
Bearer token: ${HERMES_API_KEY}. Same surface as OpenAI's /v1/chat/completions. |
| Dashboard (container) |
http://hermes:9119 on the backend network |
Web admin UI for skills, sessions, model config. No container dashboard host port is published. |
| Dashboard (Kong) |
http://hermes.localhost:${KONG_HTTP_PORT} (default 63000) |
Requires ./start.sh --setup-hosts. Kong asks for the dashboard credential (DASHBOARD_USERNAME / DASHBOARD_PASSWORD); the dashboard itself has no login. |
| Internal DNS (other containers) | http://hermes:8642 |
Reachable from LiteLLM, n8n, jupyterhub; Backend and OpenClaw get the endpoint but do not call it yet (see §6). |
See the canonical port table at Ports and Routes.
Hermes is wired into the stack in two directions:
-
Hermes → LiteLLM (outbound) — Hermes calls
http://litellm:4000/v1/chat/completionsfor every LLM operation. Pick the model viaHERMES_DEFAULT_MODEL(any name LiteLLM exposes). -
LiteLLM → Hermes (inbound) —
services/litellm/init/scripts/init.pyappends ahermes-agentrow to LiteLLM'smodel_listwhenHERMES_SOURCE != disabled. Consequence: Open WebUI, n8n, backend and JupyterHub seehermes-agentin their model lists automatically — no per-consumer wiring. OpenClaw sees it through itslitellmprovider (see the OpenClaw README).
The loop is intentional. Hermes is the agent runtime above raw chat; LiteLLM is the single front door for LLM traffic.
services/hermes/init/scripts/init-hermes.sh renders /opt/data/config.yaml from
environment. When the underlying service is enabled, Hermes gets:
| Hermes feature | Stack service | Mechanism |
|---|---|---|
| LLM reasoning | LiteLLM |
model.provider: custom, base_url: http://litellm:4000/v1
|
| TTS (text-to-speech) | Speaches (Kokoro/Piper, default) / Chatterbox (voice cloning) |
tts.provider: openai, tts.openai.base_url: ${TTS_ENDPOINT}/v1, set from the active TTS engine (for example http://speaches:8000/v1 or http://chatterbox:4123/v1). The key is the placeholder VOICE_TOOLS_OPENAI_KEY=not-required, so audio never reaches api.openai.com, even when OPENAI_API_KEY is set. With TTS disabled, init omits the block. Hermes then uses its default edge provider, which sends the text to Microsoft's Edge TTS cloud service. |
| STT (speech-to-text) | Speaches (Faster-Whisper, default) / Parakeet (NVIDIA NeMo) / whisper.cpp (Apple Silicon) |
stt.provider: openai, stt.openai.base_url: ${STT_ENDPOINT}/v1, stt.openai.api_key: ${STT_INTERNAL_API_KEY}, all set from the active STT engine. Hermes ignores the base URL without a key, so init writes the placeholder not-required when the key is blank. |
| Web search | SearXNG |
SEARXNG_URL=http://searxng:8080 on the hermes container plus web.search_backend: searxng
|
| Image generation | ComfyUI | Companion skill /opt/data/skills/creative/atlas-comfyui-host/SKILL.md tells the agent that ComfyUI is at http://comfyui:18188. The bundled creative/comfyui skill still uses 127.0.0.1:8188, so this is guidance, not a config override. |
When an optional speech or web-search dependency is disabled, init omits its
integration block from config.yaml. Hermes can still use upstream
defaults.
Without a TTS block, Hermes selects the default edge provider. If edge-tts
is available, Edge synthesis sends text to Microsoft's cloud service. Startup
does not fail because an optional integration is omitted.
HERMES_SOURCE=container # container | localhost | disabled
HERMES_IMAGE=nousresearch/hermes-agent:v2026.6.19
HERMES_API_PORT=63072
HERMES_DASHBOARD_PORT=63073 # reserved compatibility allocation; no container host publish
HERMES_DASHBOARD_ENABLED=true # requested setting
HERMES_DASHBOARD_INSECURE=true
HERMES_DASHBOARD_OAUTH_CLIENT_ID= # provisioned upstream client, if using OAuth
HERMES_DASHBOARD_TUI=1 # inert in v2026.6.19 — the Chat tab is always on (see below)
HERMES_DEFAULT_MODEL= # blank = hermes-init auto-picks from LiteLLM's model_list
HERMES_CONTEXT_LENGTH=65536 # hard floor; leave alone
HERMES_API_KEY= # auto-generated if empty
STT_INTERNAL_API_KEY= # Parakeet token, compatibility dummy, or blank; auto-derived
HERMES_MEMORY_LIMIT=4g
HERMES_CPU_LIMIT=2.0Auto-default model. When HERMES_DEFAULT_MODEL is blank, hermes-init
queries http://litellm:4000/v1/models at startup. It picks the first match
from a priority list: ollama/qwen3.8:latest → claude-sonnet-4-6 →
claude-opus-4-7 → gpt-5 → gpt-5-codex → gpt-5-mini. If none matches,
it picks the first remaining chat model, excluding hermes-agent, lightrag
and embedding models. If no chat model is available, hermes-init fails and
Hermes does not start; set HERMES_DEFAULT_MODEL. An operator value is never
overridden.
Dashboard launch policy. Atlas recomputes HERMES_DASHBOARD_EFFECTIVE from the requested setting and launch sources.
For container Hermes with KONG_API_GATEWAY_SOURCE=container, Atlas forwards HERMES_DASHBOARD_ENABLED; missing or empty requests retain the true default.
Missing Kong source retains the fixed-container default. Other Kong sources, including blank or unsupported values, disable the managed dashboard.
Localhost Hermes is an operator-run process; Atlas does not manage its dashboard.
An ordinary Atlas launch enables the default dashboard through Kong. Raw Compose defaults the effective flag to false until Atlas computes it.
This configuration check does not monitor gateway health. It preserves the requested flag when the managed dashboard cannot run.
Dashboard Chat tab. Hermes v2026.6.19 always embeds a hermes --tui
terminal as a Chat tab (/api/pty, /api/ws) and ignores
HERMES_DASHBOARD_TUI. The tab is a full agent with a terminal tool. While
HERMES_DASHBOARD_INSECURE=true, backend-network peers need no login. Kong requires the dashboard credential;
the container publishes no dashboard host port. The agent can read LITELLM_MASTER_KEY from its
environment and from /opt/data/config.yaml.
To remove the tab, set HERMES_DASHBOARD_ENABLED=false.
Nous Portal OAuth requires an upstream authentication provider with a provisioned client ID before setting HERMES_DASHBOARD_INSECURE=false.
Atlas passes through HERMES_DASHBOARD_OAUTH_CLIENT_ID unchanged but does not provision clients.
The upstream provider uses a nonempty environment client ID before dashboard.oauth.client_id in persistent configuration.
With HERMES_DASHBOARD_INSECURE=false and no configured provider, the default container refuses dashboard startup and restarts.
See provider registration, client resolution, and the startup guard. Reference: upstream Web-Dashboard docs.
Use ./start.sh for the guided wizard, or pass --hermes-source <option>
for scripted changes.
STT_INTERNAL_API_KEY resolves to PARAKEET_API_TOKEN for a Parakeet source, to sk-unused for other enabled STT engines, and to empty when STT is disabled. hermes-init writes it into the server-side provider configuration. It is not a browser credential; do not emit it in tool output.
| Scenario | Recommended SOURCE |
|---|---|
| Hermes consumed by Open WebUI / n8n / OpenClaw (default) | container |
| Hermes operates your real machine (real shell, real browser) | localhost |
| Microphone-driven live voice mode |
localhost (container mic passthrough is non-trivial) |
| Resource-constrained machine | disabled |
With localhost, hermes.localhost targets HERMES_LOCALHOST_DASHBOARD_PORT (default 9119) when container Kong and the requested dashboard flag permit routing.
Set the host dashboard port in .env; the wizard edits only HERMES_LOCALHOST_PORT.
With localhost, LiteLLM's hermes-agent route sends Atlas's HERMES_API_KEY as the bearer token. Set HERMES_API_KEY in .env to the host instance's own API_SERVER_KEY, or every call through LiteLLM returns 401. Atlas renders none of the config above (TTS, STT, SearXNG, ComfyUI) for a host instance; configure it on the host.
-
HERMES_UIDmust be 1–65534 — the image's boot hook (/opt/hermes/docker/stage2-hook.sh) ignores0and other out-of-range values and keeps thehermesuser at UID 10000. The stack default is10000. -
Gateway warning on first boot — the gateway logs
WARNING gateway.run: No user allowlists configured. All unauthorized users will be denied.This applies to the messaging-platform allowlists (Telegram, Discord and others), not to the OpenAI-compatible API. When you connect messaging channels, setGATEWAY_ALLOW_ALL_USERS=truein/opt/data/.env(HERMES_HOMEin the container). Or configure per-platform allowlists (TELEGRAM_ALLOWED_USERS=...,DISCORD_ALLOWED_USERS=...). -
Image pin — Atlas pins the dated release
nousresearch/hermes-agent:v2026.6.19. Upstream also publishes immutablesha-<commit>tags. To pin one build, setHERMES_IMAGEto asha-tag:# In .env — pin a specific build HERMES_IMAGE=nousresearch/hermes-agent:sha-e85592591e8028cceecb0ea2b4992a1643b52f93Tags are listed at https://hub.docker.com/r/nousresearch/hermes-agent/tags.
-
ComfyUI hard-coded URL — the bundled
creative/comfyuiskill uses127.0.0.1:8188. The companion skillcreative/atlas-comfyui-hostunder/opt/data/skills/gives the in-network host. If a workflow ignores it, add asocatsidecar that maps127.0.0.1:8188 → comfyui:18188. -
Open WebUI model-list cache — Open WebUI caches the LiteLLM model list for 5 minutes (
MODELS_CACHE_TTL=300). After first start,hermes-agentcan take up to 5 minutes to appear in the dropdown. SetOPEN_WEB_UI_MODEL_CACHE_TTL=0to disable the cache while you develop.
Hermes needs LiteLLM. TTS, STT, ComfyUI and SearXNG are optional (§3.1).
Open WebUI, n8n and JupyterHub reach Hermes as the hermes-agent LiteLLM
model. n8n and JupyterHub also get HERMES_ENDPOINT for direct calls. Backend
and OpenClaw receive HERMES_ENDPOINT and HERMES_API_KEY but do not call
Hermes yet.
Hermes has no direct LightRAG tool. This release cannot declare an HTTP tool in config.yaml; its tools: section holds only built-in tool settings. LightRAG is reachable as the lightrag model through LiteLLM. LIGHTRAG_INTERNAL_URL and LIGHTRAG_API_KEY reach hermes-init for a future MCP or skill integration.
Hermes can trigger Airflow DAG runs through the Airflow REST API. Airflow
3.x's public /api/v2/ uses JWT bearer tokens, not HTTP basic auth.
Exchange the admin password for a JWT, then trigger the run.
Run from the repository root with the bootstrapper dependencies available through uv.
The parser reads .env by default; set ATLAS_ENV_FILE for a custom active file.
It preserves literal values and stops before a request if the password is missing or blank:
PYTHONPATH=bootstrapper uv run --project bootstrapper python - <<'PYAIRFLOW'
import json
import subprocess
import sys
from core.config_parser import ConfigParser
parser = ConfigParser(root_dir=".")
if not parser.env_file_path.is_file():
sys.exit("The active Atlas env file does not exist.")
env = parser.parse_env_file()
password = env.get("AIRFLOW_ADMIN_PASSWORD", "")
if not password:
sys.exit("Set a nonempty AIRFLOW_ADMIN_PASSWORD in the active Atlas env file.")
base = f"http://airflow.localhost:{env.get('KONG_HTTP_PORT') or '63000'}"
auth = subprocess.run(
["curl", "-fsS", "-X", "POST", "-H", "Content-Type: application/json",
"--data-binary", "@-", f"{base}/auth/token"],
input=json.dumps({"username": "admin", "password": password}),
text=True, stdout=subprocess.PIPE, check=True,
)
token = json.loads(auth.stdout).get("access_token")
if not isinstance(token, str) or not token or any(c.isspace() for c in token):
sys.exit("Airflow did not return an access token; the DAG was not triggered.")
subprocess.run(
["curl", "-fsS", "-X", "POST", "-H", f"Authorization: Bearer {token}",
"-H", "Content-Type: application/json", "--data-binary", "@-",
f"{base}/api/v2/dags/example_etl_with_llm/dagRuns"],
input=json.dumps({"logical_date": None, "conf": {}}), text=True, check=True,
)
PYAIRFLOWIn this pattern, Hermes decides that a request needs a long-running pipeline and triggers an Airflow DAG. See the Airflow README for the example DAG.
- Upstream repo — https://github.com/NousResearch/hermes-agent
- Official docs — https://hermes-agent.nousresearch.com/docs/
- Open WebUI integration guide — https://hermes-agent.nousresearch.com/docs/user-guide/messaging/open-webui
- Docker / bridged-network compose form — https://hermes-agent.nousresearch.com/docs/user-guide/docker
Support tier: experimental — Capability contract declared; no cited cold-start, workflow, or upgrade qualification run yet (evidence at v0.1.0).
| Capability | Status | Verification | Notes |
|---|---|---|---|
| LiteLLM-backed programmable agent | supported | tested | Atlas renders one usable default chat model from LiteLLM, filters recursive aliases, and exposes Hermes back through LiteLLM as hermes-agent. |
| Source-aware agent tools | partial | tested | Init renders enabled speech and SearXNG settings in the shape Hermes reads, plus a ComfyUI host companion skill. Unavailable services are omitted. LightRAG is reachable only through LiteLLM. Airflow triggering remains a user-authored skill or curl pattern. |
| Container and operator-host lifecycle | partial | tested | Atlas manages the container and its generated config. Localhost mode only resolves an operator-run API and dashboard, and cannot guarantee their setup, tools, or process supervision. |
| Hermes API and dashboard authentication | partial | documented | The API uses HERMES_API_KEY. Configured container Kong protects the dashboard with Basic auth. No dashboard host port is published. Backend-network peers remain unauthenticated in insecure mode. |
| Agent workspace persistence | partial | documented | Sessions, memories, skills, auth, and logs persist in one hermes-data volume, with no shared database, tenant isolation, replication, or cross-service artifact store. |
| Backend and OpenClaw direct bridges | stubbed | tested | Atlas injects Hermes endpoint credentials into Backend and OpenClaw, but neither currently calls the promised direct bridge. Open WebUI and n8n use LiteLLM or manual HTTP paths instead. |
| Untrusted autonomous tool isolation | not-supported | documented | Hermes skills can execute tools and code with the runtime's mounted workspace and network access. Atlas provides no per-user sandbox or policy engine for untrusted agent execution. |
Run from the repository root with the bootstrapper dependencies available through uv.
The HTTP checks read the active Atlas env file: .env by default, or ATLAS_ENV_FILE.
Use your deployment’s usual Compose options for the Docker commands.
env_get() {
PYTHONPATH=bootstrapper uv run --project bootstrapper python - "$@" <<'PYENV'
import sys
from core.config_parser import ConfigParser
parser = ConfigParser(root_dir=".")
if not parser.env_file_path.is_file():
sys.exit("The active Atlas env file does not exist.")
key = sys.argv[1]
fallback = sys.argv[2] if len(sys.argv) == 3 else ""
value = parser.parse_env_file().get(key) or fallback
if not value:
sys.exit(f"Set a nonempty {key} in the active Atlas env file.")
print(value)
PYENV
}
HERMES_API_PORT=$(env_get HERMES_API_PORT 63072) || exit 1
LITELLM_PORT=$(env_get LITELLM_PORT 63040) || exit 1
HERMES_API_KEY=$(env_get HERMES_API_KEY) || exit 1
LITELLM_MASTER_KEY=$(env_get LITELLM_MASTER_KEY) || exit 1
# Service status
docker compose ps hermes hermes-init
# Logs
docker compose logs -f hermes
docker compose logs hermes-init # one-shot config rendering
# Verify the OpenAI-compatible API is up
curl -fsS http://localhost:${HERMES_API_PORT}/v1/models \
-H "Authorization: Bearer ${HERMES_API_KEY}" | jq .
# Verify hermes-agent appears in LiteLLM's model_list
curl -fsS http://localhost:${LITELLM_PORT}/v1/models \
-H "Authorization: Bearer ${LITELLM_MASTER_KEY}" | jq '.data[].id' | grep hermes
# Inspect the rendered config Hermes is using
docker compose exec hermes cat /opt/data/config.yamlFor general startup and routing issues, see Troubleshooting.
Rows marked planned are documented or intended, not wired yet.
| Service | Category | Status |
|---|---|---|
| litellm ↔ | llm | current |
| comfyui | media | current |
| searxng | media | current |
| stt-provider | media | current |
| tts-provider | media | current |
| airflow | agents | planned |
| lightrag | agents | planned |
| Service | Category |
|---|---|
| kong | infra |
| litellm ↔ | llm |
| n8n | agents |
| jupyterhub | apps |

Open the full-size diagram for a full-screen view.
-
hermes ↔ neo4j — Why: Adds durable cross-session episodic memory (entities, relations) queryable from other services, replacing flat-file state under
/opt/data. Mechanism: Custom skill overbolt://neo4j-graph-db:7687exposed as amemory.graphtool. Effort: medium. Confidence: medium. -
hermes ↔ weaviate — Why: Semantic recall across sessions and ingested docs, reusing the in-stack
multi2vec-clipvectorizer. Mechanism: Skill callinghttp://weaviate:8080/v1/objectsagainst aHermesMemoryclass. Effort: medium. Confidence: medium. -
hermes ↔ minio — Why: Skill outputs (ComfyUI images, STT transcripts) get shareable URLs other services can fetch instead of being trapped in a bind mount. Mechanism: New
hermes-artifactsbucket via the existingminio-initIAM pattern; S3 SigV4 againsthttp://minio:9000. Effort: small. Confidence: high. -
hermes ↔ n8n — Why: Reverses the one-way edge so Hermes can call n8n workflows as tools. The 400+ n8n connectors then become Hermes capabilities without per-platform skills. Mechanism: Generic "call-n8n" skill POSTing to
http://n8n:5678/webhook/<id>withN8N_WEBHOOK_TOKEN. Effort: small. Confidence: high. -
hermes ↔ doc-processor — Why: Lets Hermes answer questions about uploaded PDFs by routing them through the in-stack Docling parser before context or vector ingest. Mechanism: Skill POSTing multipart to
http://docling-gpu:8000/v1/document/convert. Effort: small. Confidence: high. -
hermes ↔ supabase — Why: A JWT-scoped shared session store lets one Hermes session follow a user across Open WebUI, JupyterHub and OpenClaw. Sessions are then not pinned to single-tenant
/opt/data. Mechanism: Skill writing tohermes_sessionsvia PostgREST athttp://supabase-api:3000, keyed by Supabase JWTsub. Effort: medium. Confidence: medium.
No high-confidence opportunities identified.
- MCP server mode — Why pursue: Unlocks tool-use over Neo4j/Weaviate/MinIO/n8n via a uniform protocol instead of bespoke skills, leveraging Hermes's existing MCP-client support. Effort: medium.
-
Messaging-platform allowlists — Why pursue: Wiring
GATEWAY_ALLOW_ALL_USERS,TELEGRAM_ALLOWED_USERS, andDISCORD_ALLOWED_USERSis required before OpenClaw can safely bridge Hermes to Telegram/Discord/WhatsApp without an open relay. Effort: small. -
Per-user / multi-tenant sessions — Why pursue: Needed for any shared deployment beyond a single developer's laptop; current
/opt/datalayout is single-tenant. Effort: large. -
Voice mode (mic passthrough) — Why pursue: Enables true voice agent UX in-stack, currently gated on running Hermes via
localhostSOURCE for mic access. Effort: large. - Skill marketplace / dynamic skill install — Why pursue: Lets users add capabilities without rebuilding the image; Hermes upstream already supports dynamic skill loading. Effort: medium.
Atlas documentation.
- 1. Overview
- 2. Quick Start
- 3. Core Concepts
- 4. Tracks
-
5. Services
- 5.1. Service Catalog
-
5.2. Service Guides
- 5.2.1. airflow
- 5.2.2. asset-baker
- 5.2.3. asset-worker
- 5.2.4. backend
- 5.2.5. backup
- 5.2.6. blender-mcp
- 5.2.7. celery
- 5.2.8. chatterbox
- 5.2.9. cloud-providers
- 5.2.10. cloudflared
- 5.2.11. comfyui
- 5.2.12. crawl4ai
- 5.2.13. doc-processor
- 5.2.14. docling
- 5.2.15. docling-lightrag-adapter
- 5.2.16. fal
- 5.2.17. globals
- 5.2.18. grafana
- 5.2.19. hermes
- 5.2.20. iceberg-rest
- 5.2.21. jenkins
- 5.2.22. jupyterhub
- 5.2.23. kong
- 5.2.24. label-studio
- 5.2.25. langfuse
- 5.2.26. lightrag
- 5.2.27. litellm
- 5.2.28. llm-graph-builder
- 5.2.29. local-deep-researcher
- 5.2.30. loki
- 5.2.31. mcp-servers
- 5.2.32. minio
- 5.2.33. mlflow
- 5.2.34. multi2vec-clip
- 5.2.35. n8n
- 5.2.36. neo4j
- 5.2.37. ollama
- 5.2.38. open-webui
- 5.2.39. openclaw
- 5.2.40. otel-collector
- 5.2.41. parakeet
- 5.2.42. prometheus
- 5.2.43. ray
- 5.2.44. redis
- 5.2.45. redpanda
- 5.2.46. searxng
- 5.2.47. spark
- 5.2.48. speaches
- 5.2.49. stt-provider
- 5.2.50. supabase
- 5.2.51. supavisor
- 5.2.52. tei-reranker
- 5.2.53. tempo
- 5.2.54. tika
- 5.2.55. trino
- 5.2.56. trueforge
- 5.2.57. tts-provider
- 5.2.58. verba
- 5.2.59. vllm-metal
- 5.2.60. weaviate
- 5.2.61. zeppelin
- 5.3. Provider And Extension Guides
- 6. Configuration
- 7. Operations
- 8. Architecture
- 9. Development
- 10. Reference