9e8f1bbeed
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
284 lines
17 KiB
Markdown
284 lines
17 KiB
Markdown
# Hermes Agent
|
|
|
|
ODS ships **Hermes Agent** — the [Nous Research open-source agent](https://github.com/nousresearch/hermes-agent) packaged as a ODS service. Hermes is a self-improving generalist agent with persistent memory, autonomous skill creation, and 70+ tools built in.
|
|
|
|
When enabled, Hermes runs in a container alongside the rest of the stack, serves its own browser dashboard on internal port 9119, and talks to the selected model provider through an OpenAI-compatible API. End users should enter through `hermes-proxy` on port 9120; direct host access to 9119 is intentionally not bound in the default stack.
|
|
|
|
## What you get
|
|
|
|
Hermes ships its own complete web UI — ODS is just packaging it. After `ods enable hermes` + `ods enable hermes-proxy`, you can browse to `http://<device>:9120` (or `hermes.<device>.local:9120` once mDNS announcement lands — see "Roadmap" below). The proxy is the LAN-facing entry; it gates access on ODS's magic-link cookie before forwarding to Hermes's internal port 9119. See [docs/HERMES-SSO.md](HERMES-SSO.md) for the full auth flow. Once past the proxy you find pages for:
|
|
|
|
- **Chat** — conversational interface with streaming responses + inline tool calls
|
|
- **Sessions** — list, switch between, prune past conversations
|
|
- **Skills** — view skills Hermes has autonomously created from your interactions; edit or delete
|
|
- **Memories** — persistent facts Hermes has learned about you
|
|
- **Profiles** — per-user agent contexts (a built-in alternative to running multiple Hermes containers)
|
|
- **Cron** — schedule recurring agent tasks
|
|
- **Models** — pick which LLM Hermes uses (defaults to your llama-server)
|
|
- **Config / Env** — Hermes's own settings
|
|
- **Logs / Analytics** — operational visibility
|
|
|
|
### Dashboard feature cards
|
|
|
|
The ODS dashboard exposes two separate Hermes entry points:
|
|
|
|
- **Hermes Agent** opens the authenticated Hermes runtime through `hermes-proxy` on port 9120. It must never link to a raw inference endpoint such as llama-server or LiteLLM.
|
|
- **Hermes Single Sign-On** opens the dashboard's **Setup / Owner** page at `/invites`, where operators manage owner cards and temporary support magic links. It is an access-management surface, not a second link to the Hermes runtime.
|
|
|
|
Hermes readiness is provider-neutral. A healthy local `llama-server` or a healthy LiteLLM route can satisfy the inference dependency, so the same feature contract works for local, cloud, and external-provider installations on Linux, macOS, Windows, and WSL.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Browser
|
|
│ http://<device>:9120
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ ods-hermes-proxy │
|
|
│ forward_auth → dashboard-api │
|
|
│ reverse_proxy → ods-hermes │
|
|
└─────────────────────────────────────┘
|
|
│
|
|
│ Docker network: ods-hermes:9119
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ ods-hermes container │
|
|
│ │
|
|
│ hermes gateway run │
|
|
│ - HERMES_DASHBOARD=1 → │
|
|
│ React SPA + /api endpoints │
|
|
│ - scheduler tick() every 60s → │
|
|
│ fires cron jobs │
|
|
│ - no messaging adapters │
|
|
│ │
|
|
│ State: /opt/data (HERMES_HOME) │
|
|
│ mounted from data/hermes/ │
|
|
│ │
|
|
│ Tool sandbox: local (in-container)│
|
|
└─────────────────────────────────────┘
|
|
│
|
|
│ OpenAI-compatible API
|
|
▼
|
|
┌─────────────────────────────────────┐
|
|
│ llama-server (existing) │
|
|
│ llama.cpp at :8080/v1 │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
State layout under `data/hermes/`:
|
|
|
|
```
|
|
data/hermes/
|
|
├── config.yaml # Bootstrapped from our cli-config.yaml.template on first start
|
|
├── .env # Bootstrapped from upstream's .env.example
|
|
├── SOUL.md # Bootstrapped from our SOUL.md.template
|
|
├── sessions/ # Per-session chat history
|
|
├── memories/ # Persistent agent memories
|
|
├── skills/ # Agent-authored skills
|
|
├── cron/ # Scheduled tasks
|
|
├── plans/ # Active multi-step plans
|
|
├── workspace/ # Sandboxed workspace for file ops
|
|
├── hooks/ # Custom lifecycle hooks
|
|
├── home/ # Per-profile $HOME for subprocesses (git, ssh, npm…)
|
|
└── logs/ # Hermes's own logs (separate from Docker logs)
|
|
```
|
|
|
|
## Setup
|
|
|
|
```bash
|
|
# 1. (One-time) Verify ODS's llama-server is running:
|
|
ods status llama-server
|
|
|
|
# 2. Pull + start Hermes and its auth proxy:
|
|
ods enable hermes
|
|
ods enable hermes-proxy
|
|
|
|
# 3. Open the auth-gated dashboard:
|
|
xdg-open http://localhost:9120
|
|
```
|
|
|
|
The first start takes a minute — image is ~3GB, Hermes runs its `skills_sync.py` bootstrap, and llama-server may cold-load the model on Hermes's first request. Subsequent starts are fast.
|
|
|
|
## Defaults ODS applies
|
|
|
|
- **Provider:** `custom` (OpenAI-compatible) pointing at `llama-server:8080/v1`
|
|
- **Context:** Hermes requires at least 64K tokens. Local installers run the
|
|
bootstrap model at 64K so the agent works immediately, then move the full model
|
|
and config to the model selector's chosen context after the background model
|
|
upgrade completes. Large-context tiers still use 128K when they select a
|
|
128K-capable model; constrained tiers can remain at a smaller context.
|
|
- **Compression:** enabled at `compression.threshold: 0.50` with `target_ratio: 0.20` so long sessions compact before the backend hard-rejects an over-window request.
|
|
- **Model name:** `qwen3.5-9b` (ODS's default LLM — to switch models, edit `model.default` in `data/hermes/config.yaml` after first start; there is no env-var hook for this)
|
|
- **Persona (`SOUL.md`):** a generalist ODS-aware persona (see `extensions/services/hermes/SOUL.md.template`)
|
|
- **Messaging gateways DISABLED:** Telegram / Discord / Slack / WhatsApp / Signal / Teams / Google Chat / Matrix / Mattermost / SMS — all off by default. ODS owner-card users reach Hermes through the ODS Talk mobile portal, while advanced users can still open the full Hermes web dashboard. WhatsApp is pre-seeded as disabled with `bridge_port: 3010` so upstream's default `3000` bridge does not collide with Open WebUI when users intentionally enable it. To enable any platform, see [upstream messaging docs](https://hermes-agent.nousresearch.com/docs/user-guide/messaging/).
|
|
- **Network exposure:** Hermes is **not directly LAN-reachable**. Once the `hermes-proxy` extension is enabled (see [docs/HERMES-SSO.md](HERMES-SSO.md)), the proxy at port 9120 fronts Hermes and gates access on ODS's magic-link cookie. Hermes's own port 9119 is internal-only. To restore direct access (e.g. for testing without auth), re-add a `ports:` binding to `extensions/services/hermes/compose.yaml`.
|
|
- **Resource caps:** 4 CPUs / 4GB RAM hard limit, 0.5 CPU / 1GB reservation. Hermes's playwright + ML deps can be hungry; adjust in `extensions/services/hermes/compose.yaml` if needed.
|
|
|
|
## Configuration
|
|
|
|
Three layers, highest to lowest precedence:
|
|
|
|
1. **Edit `data/hermes/config.yaml`** directly — Hermes's own config file, copied from our template on first start. Survives container restarts. Reset by deleting and restarting. **The model name lives here**, not in env.
|
|
2. **Set env vars in ODS's `.env`** — `HERMES_LLM_BASE_URL`, `HERMES_LLM_API_KEY`, `HERMES_LANGUAGE`, optional `WHATSAPP_*` gateway settings, and the `HERMES_PROXY_*` proxy settings. Hermes itself has no host-port env knob in the auth-gated stack; the LAN-facing port is `HERMES_PROXY_PORT`.
|
|
3. **Fall back to ODS's defaults** — defined in `extensions/services/hermes/cli-config.yaml.template`.
|
|
|
|
To bring up Hermes pointing at a different LLM (e.g. OpenRouter, OpenAI, Anthropic), edit `data/hermes/config.yaml`'s `model.provider` and `model.base_url` and restart. The whole gamut of provider options is listed in the upstream config — Hermes supports OpenRouter / Anthropic / OpenAI / Hugging Face / NVIDIA NIM / z.ai / Kimi / Gemini / Ollama Cloud / LM Studio / etc. out of the box.
|
|
|
|
For local backends, keep `model.context_length` and `auxiliary.compression.context_length` aligned with `.env`'s `CTX_SIZE` / `MAX_CONTEXT`. Values below 64000 will make Hermes reject prompts; values above the server's real context can produce `context length exceeded` / `max compression attempts reached` loops.
|
|
|
|
## Security posture
|
|
|
|
- **`--insecure` is enabled inside the container.** Hermes's dashboard refuses non-loopback binds without it. ODS accepts that trade-off only because port 9119 is not host-bound in the default stack; the LAN-facing entry is the magic-link-gated proxy on port 9120. Do not add a public 9119 host binding.
|
|
- **The container runs as a non-root user** (UID 10000 by default, remappable via `HERMES_UID`). The entrypoint drops privileges via `gosu` before any agent code runs.
|
|
- **The container has full network access** within ODS's bridge net — Hermes can make outbound HTTP requests for tools like `web_search`. If you want to restrict this, add an iptables firewall rule on the host or run Hermes behind a forward proxy.
|
|
- **No APE policy enforcement yet.** Hermes's 70+ tools include shell + file write. The base config defaults toward less-risky tools, but Hermes can still execute shell commands inside its sandbox container. APE policy wrapping is a planned follow-up; until then, the trust model is "the user authenticated to Hermes is trusted to use the local container."
|
|
|
|
## How to bump the image pin
|
|
|
|
Hermes is a young, fast-moving project. ODS pins a reviewed upstream image tag in `compose.yaml` instead of auto-tracking `:latest`. Operators can temporarily override it with `HERMES_AGENT_IMAGE`, and can provide `HERMES_AGENT_IMAGE_FALLBACK` for registry hotfixes, but changing the shipped default is a deliberate review-and-smoke-test pass:
|
|
|
|
```bash
|
|
# 1. Pick a published upstream image tag.
|
|
curl -s 'https://hub.docker.com/v2/repositories/nousresearch/hermes-agent/tags?page_size=25' \
|
|
| jq -r '.results[] | [.name, .last_updated] | @tsv'
|
|
|
|
# 2. Verify Docker can resolve the tag on a clean machine.
|
|
docker manifest inspect nousresearch/hermes-agent:<new-tag> >/dev/null
|
|
|
|
# 3. Review upstream release notes / commits. Skim breaking changes,
|
|
# config-format migrations, removed env vars, and dashboard changes.
|
|
|
|
# 4. Update:
|
|
# - extensions/services/hermes/compose.yaml
|
|
# - installers/phases/08-images.sh
|
|
# - config/dependency-lock.json
|
|
# - this bump-history table
|
|
|
|
# 5. Smoke test:
|
|
# ods restart hermes
|
|
# docker inspect --format '{{.State.Health.Status}}' ods-hermes
|
|
# curl http://localhost:9120/health
|
|
# # NOTE: most /api/* routes are auth-gated; /api/status is the public
|
|
# # JSON-backed endpoint used by ODS's health metadata and Docker probe.
|
|
# open http://localhost:9120, sign in, send a chat, verify tool call
|
|
|
|
# 5. If it works, commit. If config.yaml format has changed, document the
|
|
# migration in this file's "Bump history" section below.
|
|
```
|
|
|
|
## Roadmap (deferred from v1)
|
|
|
|
These were in the original integration plan but cut once we discovered Hermes ships a complete browser surface:
|
|
|
|
- **mDNS announcement** — register `hermes.<device>.local` in the ODS mDNS announcer. ✅ shipped as [#1167](https://github.com/Light-Heart-Labs/ODS/pull/1167) (stacked on [#1152](https://github.com/Light-Heart-Labs/ODS/pull/1152)).
|
|
- **Magic-link SSO** — magic-link cookie gates access to Hermes via the new `hermes-proxy` Caddy sidecar. ✅ shipped — see [docs/HERMES-SSO.md](HERMES-SSO.md). Known limitation: single shared Hermes for all users; real per-user isolation would require per-user containers.
|
|
- **APE policy integration** — route Hermes's tool calls through APE for allow/deny + audit. APE is already in the stack; needs a small adapter inside or in front of Hermes.
|
|
- **Voice in/out from ODS's whisper + kokoro** — Hermes has its own audio pipeline (the image bundles ffmpeg + playwright); verify whether it already proxies to local TTS/STT services or whether we need to wire that ourselves.
|
|
- **ODS-side status panel** — surface Hermes's session count + skill inventory in the ODS dashboard. Lower priority since Hermes has its own `AnalyticsPage`.
|
|
- **Per-user Hermes containers** — if true multi-user becomes a felt need, spawn one Hermes per magic-link target_username and have the proxy route based on the redeemed identity. See [docs/HERMES-SSO.md](HERMES-SSO.md#future-option-b--per-user-hermes).
|
|
|
|
## Troubleshooting
|
|
|
|
### `docker inspect ods-hermes` is not healthy, or `localhost:9120` returns 502
|
|
|
|
Hermes hasn't finished bootstrapping. Watch the logs:
|
|
|
|
```bash
|
|
docker logs -f ods-hermes
|
|
```
|
|
|
|
First start does a ~30s skills sync; subsequent starts are fast.
|
|
|
|
### Chat slows down after many ODS Talk or Hermes sessions
|
|
|
|
Some upstream Hermes builds can leave `tui_gateway.slash_worker` child
|
|
processes running after sessions that use `/slash` commands such as
|
|
`/no_think`. Each worker can hold model memory, so a long day of owner-card or
|
|
fleet-test sessions may create memory pressure even though the main Hermes
|
|
container looks healthy.
|
|
|
|
Check with:
|
|
|
|
```bash
|
|
ods doctor
|
|
docker exec ods-hermes sh -c "ps -eo pid=,etimes=,args= | grep '[t]ui_gateway[.]slash_worker'"
|
|
```
|
|
|
|
Clean up workers that exceed the age/count policy:
|
|
|
|
```bash
|
|
ods repair hermes-workers
|
|
```
|
|
|
|
Policy knobs:
|
|
|
|
```bash
|
|
HERMES_SLASH_WORKER_MAX_COUNT=8
|
|
HERMES_SLASH_WORKER_MAX_AGE_SECONDS=3600
|
|
```
|
|
|
|
`ods doctor` reports high worker counts. `ods repair hermes-workers` is
|
|
explicit and manual; ODS does not run an automatic process killer for
|
|
active Hermes sessions.
|
|
|
|
### Hermes can't reach the LLM
|
|
|
|
Inside the container, `llama-server:8080` should resolve to the llama-server container. Test with:
|
|
|
|
```bash
|
|
docker exec ods-hermes curl -fs http://llama-server:8080/v1/models
|
|
```
|
|
|
|
If that fails, the most likely cause is that the Hermes container isn't on ODS's docker network. Check `docker network inspect ods_default`.
|
|
|
|
### WhatsApp reports port 3000 already in use
|
|
|
|
ODS's bundled Hermes config uses `platforms.whatsapp.extra.bridge_port: 3010`, and the Hermes container does not bind that bridge to the host. If you see a WhatsApp bridge conflict on port 3000, you are probably running Hermes Agent natively, running upstream Hermes with host networking, or using an older `data/hermes/config.yaml` copied before this default existed.
|
|
|
|
Set the WhatsApp bridge port in `data/hermes/config.yaml` and restart Hermes:
|
|
|
|
```yaml
|
|
platforms:
|
|
whatsapp:
|
|
enabled: true
|
|
extra:
|
|
bridge_port: 3010
|
|
```
|
|
|
|
### Hermes says context length exceeded or max compression attempts reached
|
|
|
|
Check that the running backend and Hermes agree on context:
|
|
|
|
```bash
|
|
grep -E "^(CTX_SIZE|MAX_CONTEXT)=" .env
|
|
grep -n "context_length\|threshold\|target_ratio" data/hermes/config.yaml
|
|
```
|
|
|
|
For Hermes, `CTX_SIZE` / `MAX_CONTEXT`, `model.context_length`, and `auxiliary.compression.context_length` should be at least `65536`. Fresh local installs use `65536` during bootstrap, then keep the model selector's chosen full-model context after the swap. On larger tiers that may be `131072`; on constrained tiers it can stay lower.
|
|
|
|
### Sessions / memories / skills disappeared after upgrade
|
|
|
|
The image pin protects you from accidental version drift, but if you DID bump and lose data, it's almost certainly because Hermes's config format changed. Check `data/hermes/config.yaml` against upstream's current `cli-config.yaml.example`. The container's first start regenerates `config.yaml` from our template only if it doesn't exist — your old config sticks around.
|
|
|
|
### "I don't want Hermes anymore"
|
|
|
|
```bash
|
|
ods disable hermes # stops the container
|
|
rm -rf data/hermes # wipes all sessions / memories / skills
|
|
```
|
|
|
|
The container image stays cached — `docker image prune` removes it.
|
|
|
|
## Upstream attribution
|
|
|
|
Hermes Agent is © 2026 Nous Research, MIT-licensed. ODS's contribution is the packaging layer (`extensions/services/hermes/`) — no code is forked from upstream. The pinned image is pulled directly from `docker.io/nousresearch/hermes-agent`.
|
|
|
|
When promoting / talking about this extension, the convention is: "Hermes Agent (from Nous Research) — packaged for ODS."
|
|
|
|
## Bump history
|
|
|
|
| Date | Pinned image | Notes |
|
|
|---|---|---|
|
|
| 2026-06-01 | `nousresearch/hermes-agent:v2026.5.16` | Replace removed upstream `sha-*` tag with a published version tag; add `HERMES_AGENT_IMAGE` override/fallback path. |
|
|
| 2026-05-12 | `dd0923bb89ed2dd56f82cb63656a1323f6f42e6f` | Initial integration. |
|