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
19 KiB
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 |
|
|
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
- Agent source not mounted into container — the most common cause with a custom or adjusted
docker-compose.yml. - Wrong profile path as HERMES_HOME — if your HERMES_HOME is
~/.hermes/profiles/telegram, the agent source at~/.hermes/hermes-agentis outside that mount and invisible inside the container. - Mount path mismatch — the init script expects either
/home/hermeswebui/.hermes/hermes-agentor/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 2–5.
Quick summary of the most commonly missed items:
- Dangling
<none>image layers from rebuilds (docker image prune -f) - Per-profile
webui/state dirs (not just the global~/.hermes/webui/) providers: {}empty-key lines in profile configs (Docker write-only artifact)state-snapshots/dirs at both~/.hermes/and~/.hermes/profiles/<name>/- The
.envfile in the source tree containsHERMES_WEBUI_PASSWORDin plaintext — delete with the source tree, don't echo it
/opt/hermesmust be writable during init — the init script copies source into/app, installs into/app/venv, andchowns to the container user. Do not use:rofor this mount unless the build already happens at image-build time.- The
WANTED_UID/WANTED_GIDnumeric 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
- Fingerprint points to container path — the cache's
_source_fingerprint.config_yaml.pathis/home/hermeswebui/.hermes/config.yaml(Docker-internal) instead of the actual host config path. The WebUI can't detect config changes and never regenerates. - Raw model IDs as labels — the cache shows
kimi-k2.6:cloudinstead of the aliaskimi. The WebUI doesn't auto-resolvemodel_aliasesfrom config.yaml. - Missing badges — no "Primary" or "Recommended" indicators on model groups, so users can't tell which model is the default.
custom_providerswithdiscover_models: truenot probed for live models —get_available_models()in/app/api/config.pyonly probesmodel.base_url(the active provider) for live/v1/models. It never probes eachcustom_provider'sbase_url, even whendiscover_models: trueis set. The Ollama group shows only the static models from config.yaml, missing any models added since the config was last edited. Seereferences/custom-provider-live-discovery.mdfor 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 container — docker 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:
- Delete the cache:
rm -f ~/.hermes/profiles/<profile>/webui/models_cache.json(and equivalents in any other profile the WebUI uses) - Hit the WebUI's model-list endpoint (or just open the dropdown) to trigger a rebuild
- 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.jsonexists (single file shared by all profiles)memory.provider: hindsightin the profile'sconfig.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/memoriesshould 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:
cat ~/.hermes/hindsight/config.jsonreturns valid JSON withbank_id: hermescurl http://10.0.0.26:8000/v1/modelslists at least one modelcurl http://10.0.0.22:6333/collections/memoriesreturns 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.jsonconfig file at<HERMES_HOME>/mem0.jsonmem0_ossplugin directory at<HERMES_HOME>/plugins/mem0_oss/memory.provider: mem0_ossin config.yamlpip install mem0ai ollamain BOTH host and/app/venv/- Profile replication commands (
cp mem0.json,cp -r plugins/mem0_oss/) - Common failure:
mem0ai/ollamamissing 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:
workspace— explicit WebUI workspace keydefault_workspace— alternate explicit keyterminal.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.