Files
hermes-skills/hermes-webui-docker/SKILL.md
Hermes Agent b0d790be34 Add all 104 active skills from all 16 Hermes profiles
12 unversioned skills now versioned at 1.0.0:
agent-communication, ascii-video, external-reasoning-augmentation,
jotty-notes-api, minecraft-modpack-server, obsidian, pokemon-player,
powerpoint, social-search, songwriting-and-ai-music,
workspace-context-organization, youtube-content

Total repo: 141 skills across all profile scopes
2026-07-04 11:44:04 -05:00

19 KiB
Raw Blame History

name, description, version, author, platforms, metadata
name description version author platforms metadata
hermes-webui-docker Deploy and troubleshoot Hermes WebUI inside Docker containers — volume mounts, agent source resolution, compose wiring, and the 'AIAgent not available' failure path. 1.0.0 Hermes Agent
linux
macos
hermes
tags related_skills
hermes
webui
docker
deployment
troubleshooting
hermes-agent
lxc-container-gpu-tools

Hermes WebUI Docker Deployment

Deploy and troubleshoot Hermes WebUI inside Docker containers. Covers volume mounts, agent source resolution, compose wiring, and the common "AIAgent not available" failure.

Quick Start

# Basic single-container (uses host ~/.hermes and ~/workspace)
cd /path/to/hermes-webui
docker compose up -d
# Open http://localhost:8787

Key Volumes

Host mount Container mount Purpose
~/.hermes /home/hermeswebui/.hermes Config, sessions, state
~/workspace /workspace Filesystem browser
~/.hermes/hermes-agent /opt/hermes Agent source code (build + import dependency)

Why /opt/hermes is required

The WebUI container installs Hermes Agent at startup via uv pip install /opt/hermes (docker_init.bash line 347). Without this mount, the container has no hermes-agent code on PYTHONPATH.

At runtime the WebUI calls from run_agent import AIAgent. The startup script resolves the agent directory from sys.path; it copies the agent source from whichever path it finds into /app/ and installs it into /app/venv, so run_agent.py is importable by the server Python.

Common Failure: "AIAgent not available -- check that hermes-agent is on sys.path"

Root causes

  1. Agent source not mounted into container — the most common cause with a custom or adjusted docker-compose.yml.
  2. Wrong profile path as HERMES_HOME — if your HERMES_HOME is ~/.hermes/profiles/telegram, the agent source at ~/.hermes/hermes-agent is outside that mount and invisible inside the container.
  3. Mount path mismatch — the init script expects either /home/hermeswebui/.hermes/hermes-agent or /opt/hermes.

Diagnostic commands

# Is the container even importing the agent?
docker exec hermes-webui /app/venv/bin/python -c "from run_agent import AIAgent; print('OK')"

# Where did the init script find the agent source?
docker logs hermes-webui | grep -iE "agent dir|hermes-agent"

# Verify the mount exists in the container
docker exec hermes-webui ls /opt/hermes/agent/__init__.py

Fix: docker-compose.yml

Add the agent source volume mount and set the environment variable so the WebUI bootstrap can locate it:

services:
  hermes-webui:
    volumes:
      - ${HERMES_HOME:-${HOME}/.hermes}:/home/hermeswebui/.hermes
      - ${HERMES_WORKSPACE:-${HOME}/workspace}:/workspace
      - ${HERMES_AGENT_SRC:-${HOME}/.hermes/hermes-agent}:/opt/hermes  # <-- REQUIRED
    environment:
      - HERMES_WEBUI_AGENT_DIR=/opt/hermes                           # <-- REQUIRED

Then recreate:

cd /path/to/hermes-webui
docker compose down
docker compose up -d --force-recreate

Troubleshooting: WebUI starts but agent dir is "NOT FOUND"

Check startup log lines after "== Running hermes-webui". You want:

agent dir   : /opt/hermes  [ok]

If you see:

agent dir   : NOT FOUND  [XX]

Either the volume didn't mount, or HERMES_WEBUI_AGENT_DIR isn't pointing at it. Possible order-of-operations pitfall: if you change docker-compose.yml environment lines and do docker restart (not recreate), Docker may cache the old env. Always use --force-recreate after compose changes.

Pitfall: providers: {} Docker artifact in profile configs

Docker WebUI containers sometimes write providers: {} into profile config.yaml files. This is the write-only bug — the key is empty and useless. The correct provider config lives in custom_providers: list and model.provider. During cleanup after a mount fix, delete this key from all affected configs.

Pitfall: Stale sticky default after mount fix

When the WebUI container's profiles were copied to the local filesystem during a mount fix, ~/.hermes/active_profile may point to a Docker-origin profile (e.g., telegram). Running plain hermes then targets that profile instead of the intended CLI profile. Fix: hermes profile use general or hermes profile use default.

Full cleanup after Docker WebUI decommission

See references/webui-removal-checklist.md for the complete 8-section teardown checklist covering: inventory, pre-removal backups, container stop, Docker artifact removal, host source tree deletion, WebUI state cleanup inside bind mounts (global + per-profile webui/ dirs, state-snapshots/, providers: {} config artifacts), explicit "DO NOT REMOVE" section for shared CLI paths, post-removal verification, and a copy-paste block for steps 25.

Quick summary of the most commonly missed items:

  1. Dangling <none> image layers from rebuilds (docker image prune -f)
  2. Per-profile webui/ state dirs (not just the global ~/.hermes/webui/)
  3. providers: {} empty-key lines in profile configs (Docker write-only artifact)
  4. state-snapshots/ dirs at both ~/.hermes/ and ~/.hermes/profiles/<name>/
  5. The .env file in the source tree contains HERMES_WEBUI_PASSWORD in plaintext — delete with the source tree, don't echo it
  • /opt/hermes must be writable during init — the init script copies source into /app, installs into /app/venv, and chowns to the container user. Do not use :ro for this mount unless the build already happens at image-build time.
  • The WANTED_UID/WANTED_GID numeric values must match the filesystem owner of your bind mounts. When using profiles (e.g. HERMES_HOME=~/.hermes/profiles/telegram), the UID/GID of the host user must match what the container remaps to.

Common Failure: Stale model dropdown (wrong models, old names, missing aliases)

The WebUI model selector reads models_cache.json from the profile's webui directory. When this cache is stale, the dropdown shows wrong models, raw technical IDs instead of friendly alias names, or models from a previous configuration.

Root causes

  1. Fingerprint points to container path — the cache's _source_fingerprint.config_yaml.path is /home/hermeswebui/.hermes/config.yaml (Docker-internal) instead of the actual host config path. The WebUI can't detect config changes and never regenerates.
  2. Raw model IDs as labels — the cache shows kimi-k2.6:cloud instead of the alias kimi. The WebUI doesn't auto-resolve model_aliases from config.yaml.
  3. Missing badges — no "Primary" or "Recommended" indicators on model groups, so users can't tell which model is the default.
  4. custom_providers with discover_models: true not probed for live modelsget_available_models() in /app/api/config.py only probes model.base_url (the active provider) for live /v1/models. It never probes each custom_provider's base_url, even when discover_models: true is set. The Ollama group shows only the static models from config.yaml, missing any models added since the config was last edited. See references/custom-provider-live-discovery.md for the code fix.

Fix: rebuild models_cache.json

Delete the stale cache and rebuild it from the actual config:

# Remove stale cache
rm -f ~/.hermes/profiles/<profile>/webui/models_cache.json

# Rebuild from config.yaml (see hermes-agent skill reference: config-model-change-cleanup.md)
# Key steps:
# 1. Read config.yaml → custom_providers (group by name), model_aliases (map model→alias)
# 2. Build groups with alias names as labels, raw model IDs as ids
# 3. Set active_provider/default_model from model.default/model.provider
# 4. Add badges: "primary" for default, "recommended" for smart_model_routing.cheap_model
# 5. Set fingerprint to the actual host config path (not /home/hermeswebui/)

# Restart WebUI to pick up the new cache
docker restart hermes-webui

Verification

# Check the cache inside the container
docker exec hermes-webui cat /home/hermeswebui/.hermes/profiles/<profile>/webui/models_cache.json | python3 -m json.tool | head -30

# Verify fingerprint path is the host path, not /home/hermeswebui/
# Verify groups show alias names (kimi, deep, glm) not raw IDs
# Verify badges include "primary" and "recommended" entries

Pitfall: host config edits vs WebUI dropdown cache

The WebUI bind-mounts ~/.hermes from the host, so config.yaml changes are live for the CLI picker and any code reading the config directly inside the containerdocker exec hermes-webui grep discover_models /home/hermeswebui/.hermes/config.yaml will reflect the host edit instantly. No container restart needed for discover_models flips or new custom_providers entries.

However, the WebUI's chat-page model dropdown reads a separate cache file: ~/.hermes/profiles/<profile>/webui/models_cache.json (also mounted into the container). That file is generated once and fingerprinted; the WebUI only regenerates it when the host config path's mtime changes via the cache's own invalidation logic. If the dropdown still shows the old static list after you bulk-edited configs:

  1. Delete the cache: rm -f ~/.hermes/profiles/<profile>/webui/models_cache.json (and equivalents in any other profile the WebUI uses)
  2. Hit the WebUI's model-list endpoint (or just open the dropdown) to trigger a rebuild
  3. Verify the new cache shows live-discovered models: docker exec hermes-webui cat /home/hermeswebui/.hermes/profiles/<profile>/webui/models_cache.json | python3 -c 'import sys,json; d=json.load(sys.stdin); print([m["id"] for g in d.get("groups",[]) for m in g.get("models",[])][:20])'

If discover_models: true is set and the dropdown still shows the old static 6, the cache is stale — not the config.

Hindsight Memory in WebUI (canonical)

The canonical memory provider for all 20 Hermes profiles is Hindsight (since 2026-06-29). It runs against a local daemon (Qdrant for storage at 10.0.0.22:6333 + vLLM for LLM at 10.0.0.26:8000) and requires no per-container install — the host's ~/.hermes/hindsight/config.json is read directly.

Requirements

  • ~/.hermes/hindsight/config.json exists (single file shared by all profiles)
  • memory.provider: hindsight in the profile's config.yaml
  • vLLM reachable from host and any WebUI containers: curl http://10.0.0.26:8000/v1/models
  • Qdrant reachable: curl http://10.0.0.22:6333/collections/memories should report ~24K points

Common Failure: Hindsight tools not appearing

Root cause: The Hindsight daemon isn't running, OR ~/.hermes/hindsight/config.json is missing/malformed, OR vLLM at 10.0.0.26:8000 is unreachable. Check the daemon log and verify all three endpoints from the host and from the WebUI container (docker exec hermes-webui curl ...).

Fix: See references/hindsight-setup-verification.md (when it exists) for the full troubleshooting playbook. For now, verify:

  1. cat ~/.hermes/hindsight/config.json returns valid JSON with bank_id: hermes
  2. curl http://10.0.0.26:8000/v1/models lists at least one model
  3. curl http://10.0.0.22:6333/collections/memories returns points_count > 20000

Then /reset or start a new session — memory provider loads at session start.

Profile replication for Hindsight

Hindsight config is a single file (~/.hermes/hindsight/config.json) shared by all profiles. There is no per-profile replication needed — each profile just needs memory.provider: hindsight in its config.yaml. See devops/hermes-config-bulk-update/references/memory-provider-switch.md for the bulk-migration playbook.


Mem0 OSS Memory in WebUI — LEGACY (deprecated 2026-06-29)

The mem0_oss plugin was the canonical memory provider for all 20 profiles until 2026-06-29. It has been replaced by Hindsight. This section is preserved for historical reference and rollback only — do not use these instructions for new setups.

The original content covered:

  • mem0.json config file at <HERMES_HOME>/mem0.json
  • mem0_oss plugin directory at <HERMES_HOME>/plugins/mem0_oss/
  • memory.provider: mem0_oss in config.yaml
  • pip install mem0ai ollama in BOTH host and /app/venv/
  • Profile replication commands (cp mem0.json, cp -r plugins/mem0_oss/)
  • Common failure: mem0ai/ollama missing in WebUI container's venv
  • End-to-end verification script (docker exec hermes-webui /app/venv/bin/python3 -c "...")

The full original section was replaced with the Hindsight version above. Original references still in tree:

  • references/mem0-webui-verification.md — full original verification script (preserved as legacy reference)
  • references/mem0-cross-profile-replication.md — original June 2026 replication procedure (preserved as legacy reference)

Per-Profile Workspace Binding

The WebUI's file browser resolves each profile to its own workspace directory via the workspace: key in config.yaml. Without this key, every profile falls back to the global /workspace mount — all profiles share the same file browser view.

How it works

api/workspace.py::_profile_default_workspace() checks config.yaml keys in priority order:

  1. workspace — explicit WebUI workspace key
  2. default_workspace — alternate explicit key
  3. terminal.cwd — agent working directory (most common fallback)

The WebUI container mounts ~/workspace at /workspace via docker-compose, so config values must use /workspace/<name> (absolute container path), not ~/workspace/<name> (host-relative).

Setup: bind all profiles to their workspaces

# 1. Create workspace directories on host
for p in default ai automation coding comfy dgx experimental general llm minimal people personal research telegram tts work; do
  mkdir -p ~/workspace/$p
done

# 2. Add workspace key to every profile config (use execute_code for bulk)
# Pattern: insert `workspace: /workspace/<profile_name>` before `terminal:` section
# in base config.yaml AND every ~/.hermes/profiles/<name>/config.yaml

# 3. Create AGENTS.md in each workspace (see workspace-context-organization skill)

Verification

# Test per-profile resolution inside the container
docker exec hermes-webui /app/venv/bin/python3 -c "
from api.profiles import set_request_profile, clear_request_profile
from api.workspace import _profile_default_workspace
for p in ['general', 'telegram', 'ai', 'comfy']:
    set_request_profile(p)
    ws = _profile_default_workspace()
    print(f'{p}: {ws}')
    clear_request_profile()
"
# Expected: general: /workspace/general, telegram: /workspace/telegram, etc.

Pitfall: ~ expansion in container

Config values like workspace: ~/workspace/general expand to /home/hermeswebui/workspace/general inside the container — NOT /workspace/general. Always use absolute /workspace/<name> paths.

Pitfall: missing workspace directories

If a profile's workspace directory doesn't exist on the host, the WebUI file browser shows an empty or error state. Create the directory on the host before adding the config key.

WebUI-Host Mismatch Audit

When the WebUI shows wrong models, missing features, or stale state compared to the CLI, run this systematic audit. All checks read from the host filesystem (bind-mounted into the container).

1. providers: {} Docker Artifact

grep -rn 'providers: {}' ~/.hermes/profiles/*/config.yaml ~/.hermes/config.yaml

The WebUI container writes this empty key into config files. It's harmless clutter but indicates the container touched those files. Delete it during cleanup. The correct provider config lives in custom_providers: list and model.provider.

2. Missing models_cache.json

find ~/.hermes/profiles -name 'models_cache.json'

If no cache files exist, the WebUI model dropdown may be empty or using a fallback. The cache is generated on first dropdown open. If the dropdown shows nothing, trigger a rebuild by opening the model selector in the WebUI, or delete any stale cache to force regeneration.

3. Stale Model Dropdown (Wrong Models)

# Check cache fingerprint — must point to host path, not /home/hermeswebui/
docker exec hermes-webui cat /home/hermeswebui/.hermes/profiles/<profile>/webui/models_cache.json | python3 -m json.tool | grep -A5 fingerprint

If the fingerprint path is /home/hermeswebui/.hermes/config.yaml (container-internal), the WebUI can't detect host config changes and never regenerates. Delete the cache to force rebuild.

4. Profile Mount Leak

find ~/.hermes/profiles -name 'profiles' -type d

Nested profiles/<name>/profiles/ directories indicate the WebUI container created profile trees inside the bind mount. These persist on the host after decommission. Remove them.

5. Sticky Default Profile

cat ~/.hermes/active_profile

If this points to a Docker-origin profile (e.g., telegram), running plain hermes targets the wrong profile. Fix: hermes profile use general.

6. Hindsight Config (single-file, all profiles)

# Verify ~/.hermes/hindsight/config.json exists
test -f ~/.hermes/hindsight/config.json && echo "OK" || echo "MISSING"
# Verify every profile has memory.provider: hindsight
for p in ~/.hermes/profiles/*/config.yaml; do
  grep -q "memory.provider: hindsight" "$p" || echo "NOT HINDSIGHT: $p"
done

Hindsight config is a single file (~/.hermes/hindsight/config.json) shared by all profiles — no per-profile replication needed. Each profile only needs memory.provider: hindsight in its config.yaml.

7. Per-Profile Workspace Binding

for p in default ai automation coding comfy dgx experimental general llm minimal people personal research telegram tts work; do
  cfg="$HOME/.hermes/profiles/$p/config.yaml"
  test "$p" = "default" && cfg="$HOME/.hermes/config.yaml"
  grep "^workspace:" "$cfg" 2>/dev/null || echo "MISSING: $p"
done

Without a workspace: key, all profiles share the global /workspace mount in the WebUI file browser.

Quick Audit Script

echo "=== 1. providers:{} ===" && grep -rn 'providers: {}' ~/.hermes/profiles/*/config.yaml ~/.hermes/config.yaml | wc -l
echo "=== 2. models_cache.json ===" && find ~/.hermes/profiles -name 'models_cache.json' | wc -l
echo "=== 3. Stale fingerprint ===" && docker exec hermes-webui cat /home/hermeswebui/.hermes/profiles/general/webui/models_cache.json 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('_source_fingerprint',{}).get('config_yaml',{}).get('path','N/A'))" 2>/dev/null || echo "(no cache)"
echo "=== 4. Mount leak ===" && find ~/.hermes/profiles -name 'profiles' -type d | wc -l
echo "=== 5. Sticky default ===" && cat ~/.hermes/active_profile 2>/dev/null || echo "(default)"
echo "=== 6. Hindsight config ===" && test -f ~/.hermes/hindsight/config.json && echo "OK" || echo "MISSING"
echo "=== 7. Workspace keys ===" && for p in default general telegram; do cfg="$HOME/.hermes/profiles/$p/config.yaml"; test "$p" = "default" && cfg="$HOME/.hermes/config.yaml"; echo -n "$p: "; grep "^workspace:" "$cfg" 2>/dev/null || echo "MISSING"; done

Docker Profile Mount Leak

When the WebUI container creates profile directories inside the bind-mounted HERMES_HOME, those directories persist on the host after the container is decommissioned or the mount is fixed. This leaves nested profiles/<name>/profiles/ trees, stale state-snapshots/, and container-internal fingerprint paths in caches. See references/profile-mount-leak-migration.md for identification and cleanup steps.