Files
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

340 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: hermes-webui-docker
description: "Deploy and troubleshoot Hermes WebUI inside Docker containers — volume mounts, agent source resolution, compose wiring, and the 'AIAgent not available' failure path."
version: 1.0.0
author: Hermes Agent
platforms: [linux, macos]
metadata:
hermes:
tags: [hermes, webui, docker, deployment, troubleshooting]
related_skills: [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
```bash
# 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
```bash
# 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:
```yaml
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:
```bash
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 `chown`s 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 models** — `get_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:
```bash
# 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
```bash
# 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:
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
```bash
# 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
```bash
# 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
```bash
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`
```bash
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)
```bash
# 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
```bash
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
```bash
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)
```bash
# 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
```bash
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
```bash
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.