mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-07-16 20:50:18 +00:00
Merge pull request #3302 from nesquena/docs/markdown-refresh
docs: refresh markdown, regenerate contributor tally (137→194), README IA pass
This commit is contained in:
+46
-32
@@ -7,10 +7,12 @@
|
||||
>
|
||||
> Keep this document updated as architecture changes are made.
|
||||
|
||||
> Current shipped build: `v0.51.54` (May 13, 2026).
|
||||
> Automated coverage: 5303 tests via `pytest tests/ --collect-only -q`. CI runs on Python 3.11, 3.12, and 3.13 against every PR.
|
||||
> Current shipped build: `v0.51.192` (May 31, 2026).
|
||||
> Automated coverage: ~7,150 tests via `pytest tests/ --collect-only -q`. CI runs on
|
||||
> Python 3.11, 3.12, and 3.13 (3 parallel shards each) against every PR, plus a ruff
|
||||
> lint gate, a headless browser smoke test, and a Docker smoke test.
|
||||
>
|
||||
> Notable architecture state as of v0.51.54: the bootstrap and first-run onboarding flow own setup discovery; the default WebUI state directory is `~/.hermes/webui`; `ctl.sh` provides a daemon wrapper for homelab installs; chat streaming is still WebUI-owned SSE with stream-ownership guards, cancellation, async manual compression, and turn-journal audit plumbing; provider/model discovery is profile-aware with live-model cache invalidation and custom-provider scoping.
|
||||
> Notable architecture state: the bootstrap and first-run onboarding flow own setup discovery; the default WebUI state directory is `~/.hermes/webui`; `ctl.sh` provides a daemon wrapper for homelab installs; chat streaming is still WebUI-owned SSE with stream-ownership guards, cancellation, async manual compression, and turn-journal audit plumbing; provider/model discovery is profile-aware with live-model cache invalidation and custom-provider scoping. (Version/test-count numbers above are a periodic snapshot — the authoritative source is the latest git tag and `pytest --collect-only`.)
|
||||
|
||||
---
|
||||
|
||||
@@ -43,51 +45,63 @@ actions. The topbar remains focused on conversation context and the workspace/fi
|
||||
## 2. File Inventory
|
||||
|
||||
<repo>/
|
||||
server.py Thin routing shell + HTTP Handler + auth middleware. ~446 lines.
|
||||
server.py Thin routing shell + HTTP Handler + auth middleware.
|
||||
Delegates all route handling to api/routes.py.
|
||||
bootstrap.py One-shot launcher: optional agent install, deps, health wait, browser open.
|
||||
start.sh Thin wrapper around bootstrap.py for shell-based startup.
|
||||
Dockerfile python:3.12-slim container image (~89 lines)
|
||||
docker-compose.yml Compose config with named volume and optional auth (~57 lines)
|
||||
ctl.sh Daemon lifecycle wrapper (start/stop/restart/status/logs) for homelab installs.
|
||||
pyproject.toml Tooling config (ruff lint gate). NOT a packaged distribution.
|
||||
Dockerfile python:3.12-slim container image
|
||||
docker-compose.yml Compose config with named volume and optional auth
|
||||
.dockerignore Excludes .git, tests/, .env* from Docker builds
|
||||
api/
|
||||
__init__.py Package marker
|
||||
auth.py Optional password authentication, signed cookies (~366 lines)
|
||||
config.py Discovery, globals, model detection, reloadable config (~4139 lines)
|
||||
helpers.py HTTP helpers: j(), bad(), require(), safe_resolve(), security headers (~302 lines)
|
||||
models.py Session model + CRUD, per-session profile tracking (~1927 lines)
|
||||
profiles.py Profile state management, hermes_cli wrapper (~1056 lines)
|
||||
onboarding.py First-run onboarding status, real provider config writes, OAuth linking, and readiness detection (~1002 lines)
|
||||
routes.py All GET + POST route handlers (~9772 lines)
|
||||
startup.py Startup helpers: auto_install_agent_deps() (~128 lines)
|
||||
streaming.py SSE engine, run_agent, cancel, HERMES_HOME save/restore (~4420 lines)
|
||||
upload.py Multipart parser, file upload handler (~284 lines)
|
||||
workspace.py File ops: list_dir, read_file_content, workspace helpers (~810 lines)
|
||||
auth.py Optional password authentication, signed cookies, passkeys/WebAuthn
|
||||
config.py Discovery, globals, model detection, reloadable config
|
||||
helpers.py HTTP helpers: j(), bad(), require(), safe_resolve(), security headers
|
||||
models.py Session model + CRUD, per-session profile tracking, CLI/state.db bridge
|
||||
profiles.py Profile state management, hermes_cli wrapper
|
||||
onboarding.py First-run onboarding status, real provider config writes, OAuth linking, readiness detection
|
||||
routes.py All GET + POST route handlers (if/elif dispatch, no decorators)
|
||||
startup.py Startup helpers: auto_install_agent_deps()
|
||||
state_sync.py /insights sync — message_count to the agent's state.db
|
||||
streaming.py SSE engine, run_agent, cancel, compression, HERMES_HOME save/restore
|
||||
updates.py Self-update check and release notes
|
||||
upload.py Multipart parser, file upload handler
|
||||
workspace.py File ops: list_dir, read_file_content, git detection, workspace helpers
|
||||
static/
|
||||
index.html HTML template (~1323 lines)
|
||||
style.css All CSS incl. mobile responsive (~3767 lines)
|
||||
ui.js DOM helpers, renderMd, tool cards, model dropdown, file tree (~7216 lines)
|
||||
workspace.js File preview, file ops, loadDir, clearPreview (~369 lines)
|
||||
sessions.js Session CRUD, list rendering, search, SVG icons, dropdown actions (~3517 lines)
|
||||
messages.js send(), SSE event handlers, approval, transcript (~2301 lines)
|
||||
panels.js Cron, skills, memory, workspace, profiles, todo, settings (~6480 lines)
|
||||
commands.js Slash command registry, parser, autocomplete dropdown (~1302 lines)
|
||||
onboarding.js First-run wizard overlay, provider setup flow, and settings/workspace orchestration.
|
||||
boot.js Event wiring, mobile sidebar/workspace nav, voice input, boot IIFE (~1607 lines)
|
||||
index.html HTML template
|
||||
style.css All CSS incl. mobile responsive, themes + skins, KaTeX
|
||||
ui.js DOM helpers, renderMd, tool cards, context indicator, file tree
|
||||
workspace.js File preview, file ops, git badge, central api() fetch wrapper
|
||||
sessions.js Session CRUD, list rendering, collapsible groups, search, SSE sync
|
||||
messages.js send(), SSE event handlers, approval/clarify, transcript, recovery
|
||||
panels.js Cron, skills, memory, profiles, todo, settings (Control Center)
|
||||
commands.js Slash command registry, parser, autocomplete dropdown
|
||||
boot.js Event wiring, mobile nav, voice input, theme/skin boot, bfcache handler
|
||||
onboarding.js First-run wizard overlay, provider setup flow
|
||||
i18n.js Localization catalog (en, es, de, zh, zh-Hant, ru, …)
|
||||
login.js Login page + open-redirect guard
|
||||
icons.js Lucide icon path registry
|
||||
sw.js Service worker: offline shell cache, version-pinned assets
|
||||
tests/
|
||||
conftest.py Isolated test server/state fixtures (~644 lines)
|
||||
488 test files 5303 tests collected via pytest
|
||||
test_regressions.py Permanent regression gate (~976 lines)
|
||||
conftest.py Isolated test server/state fixtures
|
||||
~700 test files ~7,150 tests collected via pytest (run `pytest --collect-only -q` for exact)
|
||||
test_regressions.py Permanent regression gate
|
||||
CONTRIBUTING.md Contributor workflow and PR expectations.
|
||||
ROADMAP.md Feature and product roadmap document.
|
||||
SPRINTS.md Forward sprint plan with CLI + Claude parity targets.
|
||||
ARCHITECTURE.md THIS FILE.
|
||||
TESTING.md Manual browser test plan and automated coverage reference.
|
||||
CHANGELOG.md Release notes per sprint.
|
||||
BUGS.md Bug backlog and fixed items tracker.
|
||||
CHANGELOG.md Release notes per version.
|
||||
CONTRIBUTORS.md Community credit roll (regenerated via the maintainer workspace script).
|
||||
requirements.txt Python dependencies.
|
||||
.env.example Sample environment variable overrides.
|
||||
|
||||
> Per-file line counts intentionally omitted — they drift every release. Use
|
||||
> `git ls-files | xargs wc -l` (or your editor) for current sizes; the role of
|
||||
> each file above is the durable part.
|
||||
|
||||
State directory (runtime data, separate from source):
|
||||
|
||||
~/.hermes/webui/
|
||||
|
||||
+48
-41
File diff suppressed because one or more lines are too long
@@ -45,6 +45,20 @@ This gives you nearly **1:1 parity with Hermes CLI from a convenient web UI** wh
|
||||
|
||||
---
|
||||
|
||||
## Contents
|
||||
|
||||
- [Why Hermes](#why-hermes) — what it is and how it compares
|
||||
- [Quick start](#quick-start) — clone + `bootstrap.py` / `start.sh` / `ctl.sh`
|
||||
- [Features](#features) — chat, sessions, workspace, voice, profiles, security, themes, panels, mobile
|
||||
- [Configuration & access](#configuration--access) — auto-discovery, overrides, remote/Tailscale/phone, manual launch
|
||||
- [Docker](#docker) — single- and multi-container deploys
|
||||
- [Running tests](#running-tests)
|
||||
- [Architecture](#architecture) — backend/frontend layout, state dir
|
||||
- [Docs](#docs) — the full documentation index
|
||||
- [Contributors](#contributors)
|
||||
|
||||
---
|
||||
|
||||
## Why Hermes
|
||||
|
||||
Most AI tools reset every session. They don't know who you are, what you worked on, or what
|
||||
@@ -121,85 +135,9 @@ For self-hosted VM or homelab installs, `ctl.sh` wraps the common daemon lifecyc
|
||||
|
||||
`ctl.sh start` runs the bootstrap in foreground/no-browser mode behind the daemon wrapper, writes logs to `~/.hermes/webui.log`, and respects `.env` plus inline overrides such as `HERMES_WEBUI_HOST=0.0.0.0 ./ctl.sh start`.
|
||||
|
||||
### Optional session recall prefill
|
||||
### Advanced: dynamic recall prefill & Gateway-backed chat
|
||||
|
||||
WebUI can attach ephemeral prefill messages to new browser-originated
|
||||
agent turns. This is useful when a deployment already has a local recall or
|
||||
router script for Joplin, Obsidian, Notion, llm-wiki, or another third-party
|
||||
notes source and wants browser chat to know where durable context lives.
|
||||
|
||||
Prefer a compact router-style prefill (for example, "Joplin has the durable
|
||||
project context; use the available notes/search tools before answering
|
||||
detail-dependent questions") instead of dumping the full note corpus into every
|
||||
new browser session. The prefill should point the agent toward retrieval; the
|
||||
notes/search tools should provide the specific facts on demand.
|
||||
|
||||
Static JSON remains supported through `prefill_messages_file` or
|
||||
`HERMES_PREFILL_MESSAGES_FILE`. For dynamic recall, opt in explicitly with a
|
||||
WebUI-specific script hook:
|
||||
|
||||
```yaml
|
||||
webui_prefill_messages_script:
|
||||
- python3
|
||||
- /path/to/notes_recall.py
|
||||
webui_prefill_messages_script_timeout: 5
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
HERMES_WEBUI_PREFILL_MESSAGES_SCRIPT="python3 /path/to/notes_recall.py" \
|
||||
HERMES_WEBUI_PREFILL_MESSAGES_SCRIPT_TIMEOUT=5 \
|
||||
./ctl.sh restart
|
||||
```
|
||||
|
||||
The script may print either an OpenAI-style JSON message list, a JSON object with
|
||||
a `messages` list, or plain text; plain text is wrapped as one `user` prefill
|
||||
message so dynamic recall text becomes ordinary context instead of an extra
|
||||
system instruction. If the hook must provide system-level guidance, emit JSON
|
||||
messages with an explicit `role: "system"` entry instead. Script output is capped
|
||||
at 256 KiB before parsing. Parsed prefill context is then bounded by
|
||||
`webui_prefill_context_max_chars` or `HERMES_WEBUI_PREFILL_CONTEXT_MAX_CHARS`
|
||||
(default: 12,000 characters; set to `0` to disable). When a dynamic script
|
||||
exceeds the budget and a compact static prefill file is configured, WebUI falls
|
||||
back to that file. If no compact fallback is available, WebUI injects a short
|
||||
retrieval instruction instead of sending the oversized note/body payload with
|
||||
every new browser turn. The browser only receives a compact status event
|
||||
(`source`, `label`, message count, compaction metadata, and redacted errors),
|
||||
never the prefill message bodies.
|
||||
|
||||
### Optional Gateway-backed browser chat
|
||||
|
||||
By default, browser chat runs through WebUI's in-process legacy runtime. Advanced
|
||||
self-hosted deployments can opt into routing new browser turns through a running
|
||||
Hermes Gateway API server while preserving the existing WebUI `/api/chat/start`
|
||||
and `/api/chat/stream` browser contract:
|
||||
|
||||
```bash
|
||||
HERMES_WEBUI_CHAT_BACKEND=gateway \
|
||||
HERMES_WEBUI_GATEWAY_BASE_URL=http://127.0.0.1:8642 \
|
||||
HERMES_WEBUI_GATEWAY_API_KEY=... \
|
||||
./ctl.sh restart
|
||||
```
|
||||
|
||||
`HERMES_WEBUI_CHAT_BACKEND` is intentionally strict: only `gateway`,
|
||||
`api_server`, or `api-server` enable the bridge. Generic truthy values such as
|
||||
`1` or `true` are ignored so existing deployments do not change execution
|
||||
ownership accidentally. If `HERMES_WEBUI_GATEWAY_API_KEY` is omitted, WebUI falls
|
||||
back to `API_SERVER_KEY` when present. When Gateway returns HTTP 401, WebUI
|
||||
reports a `gateway_auth_error` that points at this WebUI↔Gateway key mismatch
|
||||
rather than showing the Gateway's generic provider-style "Invalid API key" body.
|
||||
`/api/health/agent` also includes a redacted `gateway_chat` block so operators can
|
||||
see whether gateway mode, base URL, and API-key presence are configured without
|
||||
exposing the key value. That `gateway_chat` field is an operator diagnostic
|
||||
payload only; it is not currently rendered as a user-facing health banner in the
|
||||
browser UI.
|
||||
|
||||
The bridge is best used by operators who already run Hermes Gateway/API Server
|
||||
locally and want browser-originated chat to use the same runtime/tool path as
|
||||
messaging surfaces. Attachments, cancellation, approvals, and clarify prompts
|
||||
still follow WebUI's current compatibility path and may not match every messaging
|
||||
surface until the runtime-adapter migration is complete.
|
||||
Two optional, self-hosted-deployment features — attaching dynamic **session-recall prefill** to browser turns (Joplin/Obsidian/Notion/llm-wiki routers), and routing browser chat through a running **Hermes Gateway** — are documented in [`docs/advanced-chat-setup.md`](docs/advanced-chat-setup.md). Most users need neither.
|
||||
|
||||
The bootstrap will:
|
||||
|
||||
@@ -226,264 +164,6 @@ If an AI assistant is helping with install, reinstall, bootstrap, provider setup
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
**Pre-built images** (amd64 + arm64) are published to GHCR on every release.
|
||||
|
||||
For a comprehensive setup guide covering all 3 compose files, common failure modes, and bind-mount migration, see [`docs/docker.md`](docs/docker.md). The README covers the 5-minute happy path.
|
||||
|
||||
### 5-minute quickstart (single container)
|
||||
|
||||
The simplest setup: one WebUI container that runs the agent in-process.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/nesquena/hermes-webui
|
||||
cd hermes-webui
|
||||
cp .env.docker.example .env
|
||||
# Edit .env if your host UID isn't 1000 (e.g. macOS where UIDs start at 501)
|
||||
docker compose up -d
|
||||
# Open http://localhost:8787
|
||||
```
|
||||
|
||||
Run Compose as the user who owns your Hermes home. `sudo docker compose up -d` can make `${HOME}` expand to the root user's home, so Docker mounts the wrong `.hermes` directory instead of your real `~/.hermes` and the WebUI starts with `config.yaml (not found, using defaults)`. Prefer adding your user to the Docker group and running `docker compose up -d`; if you must use sudo, set absolute paths first, for example `HERMES_HOME=/home/you/.hermes HERMES_WORKSPACE=/home/you/workspace sudo -E docker compose up -d`, then verify with `docker compose config`.
|
||||
|
||||
The container auto-detects your UID/GID from the mounted `~/.hermes` volume so files written by the agent stay readable by you on the host.
|
||||
|
||||
To enable password protection (required if you expose the port outside `127.0.0.1`):
|
||||
|
||||
```bash
|
||||
echo "HERMES_WEBUI_PASSWORD=change-me-to-something-strong" >> .env
|
||||
docker compose up -d --force-recreate
|
||||
```
|
||||
|
||||
### Manual `docker run` (no compose)
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/nesquena/hermes-webui:latest
|
||||
docker run -d \
|
||||
-e WANTED_UID=$(id -u) -e WANTED_GID=$(id -g) \
|
||||
-v ~/.hermes:/home/hermeswebui/.hermes \
|
||||
-e HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui \
|
||||
-v ~/workspace:/workspace \
|
||||
-p 127.0.0.1:8787:8787 \
|
||||
ghcr.io/nesquena/hermes-webui:latest
|
||||
```
|
||||
|
||||
### Build locally
|
||||
|
||||
```bash
|
||||
docker build -t hermes-webui .
|
||||
docker run -d \
|
||||
-e WANTED_UID=$(id -u) -e WANTED_GID=$(id -g) \
|
||||
-v ~/.hermes:/home/hermeswebui/.hermes \
|
||||
-e HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui \
|
||||
-v ~/workspace:/workspace \
|
||||
-p 127.0.0.1:8787:8787 \
|
||||
hermes-webui
|
||||
```
|
||||
|
||||
### Multi-container setups
|
||||
|
||||
If you want the agent and WebUI in separate containers (for isolation, or because you're already running an agent gateway elsewhere):
|
||||
|
||||
```bash
|
||||
# Agent + WebUI
|
||||
docker compose -f docker-compose.two-container.yml up -d
|
||||
|
||||
# Agent + Dashboard + WebUI
|
||||
docker compose -f docker-compose.three-container.yml up -d
|
||||
```
|
||||
|
||||
Both compose files use **named Docker volumes** by default, which solves the UID/GID problem by construction. If you need bind mounts to share an existing host directory, see [`docs/docker.md`](docs/docker.md) for the full migration recipe.
|
||||
|
||||
> **Known limitation (#681)**: in the two-container setup, tools triggered from the WebUI run in the **WebUI container**, not the agent container. If you need git/node/etc. on the WebUI's filesystem, either use the single-container setup, extend the WebUI Dockerfile, or use the community [all-in-one image](https://github.com/sunnysktsang/hermes-suite).
|
||||
>
|
||||
> **Source boundary note (#2453)**: the multi-container setup mounts `hermes-agent-src` read-only into the WebUI by default. This prevents WebUI-side source rewrites but is still an implementation-coupling bridge, not a stable Agent API boundary. See [`docs/rfcs/agent-source-boundary.md`](docs/rfcs/agent-source-boundary.md) for the current source/API decoupling inventory.
|
||||
|
||||
### Common failure modes
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---|---|---|
|
||||
| `PermissionError` at startup | UID mismatch on bind mount | Set `UID=$(id -u)` in `.env` |
|
||||
| `.env: permission denied` (#1389) | `fix_credential_permissions()` enforced 0600 | Set `HERMES_SKIP_CHMOD=1` in `.env` |
|
||||
| Workspace appears empty | UID mismatch on `/workspace` mount | Set `UID=$(id -u)` in `.env` |
|
||||
| `git: command not found` in chat | Two-container architectural limit (#681) | Use single-container or extend Dockerfile |
|
||||
| WebUI can't find agent source | `hermes-agent-src` volume misconfigured | Use the named volumes from compose files as-is |
|
||||
| Podman shared `.hermes` fails | Podman 3.4 `keep-id` limitation | Use Podman 4+ or single-container |
|
||||
| Host API at `localhost` fails from WebUI | Container `localhost` means the container, not your host (#3012) | Use `http://host.docker.internal:<port>` on Docker Desktop, or `http://host.containers.internal:<port>` on Podman |
|
||||
| WebUI can't see `~/.hermes` after `sudo docker compose` | `${HOME}` expanded to the root user's home (#3006) | Run Compose as your user, or pass absolute `HERMES_HOME`/`HERMES_WORKSPACE` with `sudo -E` |
|
||||
|
||||
For the deep dive on each of these, see [`docs/docker.md`](docs/docker.md).
|
||||
|
||||
> **Note:** By default, Docker Compose binds to `127.0.0.1` (localhost only).
|
||||
> To expose on a network, change the port to `"8787:8787"` in `docker-compose.yml`
|
||||
> and set `HERMES_WEBUI_PASSWORD` to enable authentication.
|
||||
|
||||
---
|
||||
|
||||
## What start.sh discovers automatically
|
||||
|
||||
| Thing | How it finds it |
|
||||
|---|---|
|
||||
| Hermes agent dir | `HERMES_WEBUI_AGENT_DIR` env, then `$HERMES_HOME/hermes-agent` (Windows default `%LOCALAPPDATA%\hermes\hermes-agent`, POSIX default `~/.hermes/hermes-agent`), then sibling `../hermes-agent` |
|
||||
| Python executable | Agent venv first, then `.venv` in this repo, then system `python3` |
|
||||
| State directory | `HERMES_WEBUI_STATE_DIR` env, then `$HERMES_HOME/webui` (Windows default `%LOCALAPPDATA%\hermes\webui`, POSIX default `~/.hermes/webui`) |
|
||||
| Default workspace | `HERMES_WEBUI_DEFAULT_WORKSPACE` env, then `~/workspace`, then state dir |
|
||||
| Port | `HERMES_WEBUI_PORT` env or first argument, default `8787` |
|
||||
|
||||
If discovery finds everything, nothing else is required.
|
||||
|
||||
---
|
||||
|
||||
## Overrides (only needed if auto-detection misses)
|
||||
|
||||
```bash
|
||||
export HERMES_WEBUI_AGENT_DIR=/path/to/hermes-agent
|
||||
export HERMES_WEBUI_PYTHON=/path/to/python
|
||||
export HERMES_WEBUI_PORT=9000
|
||||
export HERMES_WEBUI_AUTO_INSTALL=1 # enable auto-install of agent deps (disabled by default)
|
||||
./start.sh
|
||||
```
|
||||
|
||||
Or inline:
|
||||
|
||||
```bash
|
||||
HERMES_WEBUI_AGENT_DIR=/custom/path ./start.sh 9000
|
||||
```
|
||||
|
||||
Full list of environment variables:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `HERMES_WEBUI_AGENT_DIR` | auto-discovered | Path to the hermes-agent checkout |
|
||||
| `HERMES_WEBUI_PYTHON` | auto-discovered | Python executable |
|
||||
| `HERMES_WEBUI_HOST` | `127.0.0.1` | Bind address (`0.0.0.0` for all IPv4, `::` for all IPv6, `::1` for IPv6 loopback) |
|
||||
| `HERMES_WEBUI_PORT` | `8787` | Port |
|
||||
| `HERMES_WEBUI_STATE_DIR` | `$HERMES_HOME/webui` (Windows default `%LOCALAPPDATA%\hermes\webui`, POSIX default `~/.hermes/webui`) | Where sessions and state are stored |
|
||||
| `HERMES_WEBUI_DEFAULT_WORKSPACE` | `~/workspace` | Default workspace |
|
||||
| `HERMES_WEBUI_DEFAULT_MODEL` | *(provider default)* | Optional model override; leave unset to use the active Hermes provider default |
|
||||
| `HERMES_WEBUI_PASSWORD` | *(unset)* | Set to enable password authentication |
|
||||
| `HERMES_WEBUI_CSP_CONNECT_EXTRA` | *(unset)* | Optional space-separated `http(s)://` or `ws(s)://` origins to append to the report-only CSP `connect-src` directive for reverse-proxy or tunnel deployments |
|
||||
| `HERMES_WEBUI_EXTENSION_DIR` | *(unset)* | Optional local directory served at `/extensions/`; must point to an existing directory before extension injection is enabled |
|
||||
| `HERMES_WEBUI_EXTENSION_SCRIPT_URLS` | *(unset)* | Optional comma-separated same-origin script URLs to inject; see [WebUI Extensions](docs/EXTENSIONS.md) |
|
||||
| `HERMES_WEBUI_EXTENSION_STYLESHEET_URLS` | *(unset)* | Optional comma-separated same-origin stylesheet URLs to inject; see [WebUI Extensions](docs/EXTENSIONS.md) |
|
||||
| `HERMES_HOME` | Windows: `%LOCALAPPDATA%\hermes`; POSIX: `~/.hermes` | Base directory for Hermes state (affects all paths) |
|
||||
| `HERMES_CONFIG_PATH` | `$HERMES_HOME/config.yaml` | Path to Hermes config file |
|
||||
|
||||
---
|
||||
|
||||
## Accessing from a remote machine
|
||||
|
||||
The server binds to `127.0.0.1` by default (loopback only). If you are running
|
||||
Hermes on a VPS or remote server, use an SSH tunnel from your local machine:
|
||||
|
||||
```bash
|
||||
ssh -N -L <local-port>:127.0.0.1:<remote-port> <user>@<server-host>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
ssh -N -L 8787:127.0.0.1:8787 user@your.server.com
|
||||
```
|
||||
|
||||
Then open `http://localhost:8787` in your local browser.
|
||||
|
||||
`start.sh` will print this command for you automatically when it detects you
|
||||
are running over SSH.
|
||||
|
||||
---
|
||||
|
||||
## Accessing on your phone with Tailscale
|
||||
|
||||
[Tailscale](https://tailscale.com) is a zero-config mesh VPN built on
|
||||
WireGuard. Install it on your server and your phone, and they join the same
|
||||
private network -- no port forwarding, no SSH tunnels, no public exposure.
|
||||
|
||||
The Hermes Web UI is fully responsive with a mobile-optimized layout
|
||||
(hamburger sidebar, sidebar top tabs in the drawer, touch-friendly controls),
|
||||
so it works well as a daily-driver agent interface from your phone.
|
||||
|
||||
**Setup:**
|
||||
|
||||
1. Install [Tailscale](https://tailscale.com/download) on your server and
|
||||
your iPhone/Android.
|
||||
2. Start the WebUI listening on all interfaces with password auth enabled:
|
||||
|
||||
```bash
|
||||
HERMES_WEBUI_HOST=0.0.0.0 HERMES_WEBUI_PASSWORD=your-secret ./start.sh
|
||||
```
|
||||
|
||||
3. Open `http://<server-tailscale-ip>:8787` in your phone's browser
|
||||
(find your server's Tailscale IP in the Tailscale app or with
|
||||
`tailscale ip -4` on the server).
|
||||
|
||||
That's it. Traffic is encrypted end-to-end by WireGuard, and password auth
|
||||
protects the UI at the application level. You can add it to your home screen
|
||||
for an app-like experience.
|
||||
|
||||
### Community field report: ARM64 Android via AVF
|
||||
|
||||
A community report in [#2364](https://github.com/nesquena/hermes-webui/issues/2364)
|
||||
documents Hermes Agent + WebUI running on a mid-range ARM64 Android phone inside
|
||||
a Debian 12 VM via Android Virtualization Framework (AVF). The reported setup
|
||||
used a Xiaomi Redmi Note 13 Pro 4G, 3.8 GiB RAM allocated to the VM, 8 visible
|
||||
CPU cores, Chrome on Android at `localhost:8787`, and cloud-hosted inference.
|
||||
|
||||
This is not an official support baseline or provider/model benchmark, but it is
|
||||
a useful compatibility signal for mobile ARM64 experiments: the WebUI rendered
|
||||
smoothly in Chrome, ARM64 Debian worked for the agent stack, and the total local
|
||||
footprint was about 1.7 GB. Practical caveats from the report: first install can
|
||||
take longer when dependencies compile from source, Android browser tabs may
|
||||
reload when switching apps, and disabling battery optimization for the terminal
|
||||
or VM host may be needed for longer-running sessions.
|
||||
|
||||
> **Tip:** If using Docker, set `HERMES_WEBUI_HOST=0.0.0.0` in your
|
||||
> `docker-compose.yml` environment (already the default) and set
|
||||
> `HERMES_WEBUI_PASSWORD`.
|
||||
|
||||
---
|
||||
|
||||
## Manual launch (without start.sh)
|
||||
|
||||
If you prefer to launch the server directly:
|
||||
|
||||
```bash
|
||||
cd /path/to/hermes-agent # or wherever sys.path can find Hermes modules
|
||||
HERMES_WEBUI_PORT=8787 venv/bin/python /path/to/hermes-webui/server.py
|
||||
```
|
||||
|
||||
Note: use the agent venv Python (or any Python environment that has the Hermes agent dependencies installed). System Python will be missing `openai`, `httpx`, and other required packages.
|
||||
|
||||
Health check:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8787/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running tests
|
||||
|
||||
Tests discover the repo and the Hermes agent dynamically -- no hardcoded paths.
|
||||
|
||||
```bash
|
||||
cd hermes-webui
|
||||
pytest tests/ -v --timeout=60
|
||||
```
|
||||
|
||||
Or using the agent venv explicitly:
|
||||
|
||||
```bash
|
||||
/path/to/hermes-agent/venv/bin/python -m pytest tests/ -v
|
||||
```
|
||||
|
||||
Tests run against an isolated server with a separate state directory.
|
||||
Production data and real cron jobs are never touched. Current snapshot:
|
||||
**5303 tests collected** across **488 test files**.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Chat and agent
|
||||
@@ -605,110 +285,340 @@ Production data and real cron jobs are never touched. Current snapshot:
|
||||
|
||||
---
|
||||
|
||||
## Configuration & access
|
||||
|
||||
`start.sh` auto-detects almost everything; the subsections below cover the knobs for when it can't, and how to reach the UI remotely.
|
||||
|
||||
### What start.sh discovers automatically
|
||||
|
||||
| Thing | How it finds it |
|
||||
|---|---|
|
||||
| Hermes agent dir | `HERMES_WEBUI_AGENT_DIR` env, then `$HERMES_HOME/hermes-agent` (Windows default `%LOCALAPPDATA%\hermes\hermes-agent`, POSIX default `~/.hermes/hermes-agent`), then sibling `../hermes-agent` |
|
||||
| Python executable | Agent venv first, then `.venv` in this repo, then system `python3` |
|
||||
| State directory | `HERMES_WEBUI_STATE_DIR` env, then `$HERMES_HOME/webui` (Windows default `%LOCALAPPDATA%\hermes\webui`, POSIX default `~/.hermes/webui`) |
|
||||
| Default workspace | `HERMES_WEBUI_DEFAULT_WORKSPACE` env, then `~/workspace`, then state dir |
|
||||
| Port | `HERMES_WEBUI_PORT` env or first argument, default `8787` |
|
||||
|
||||
If discovery finds everything, nothing else is required.
|
||||
|
||||
---
|
||||
|
||||
### Overrides (only needed if auto-detection misses)
|
||||
|
||||
```bash
|
||||
export HERMES_WEBUI_AGENT_DIR=/path/to/hermes-agent
|
||||
export HERMES_WEBUI_PYTHON=/path/to/python
|
||||
export HERMES_WEBUI_PORT=9000
|
||||
export HERMES_WEBUI_AUTO_INSTALL=1 # enable auto-install of agent deps (disabled by default)
|
||||
./start.sh
|
||||
```
|
||||
|
||||
Or inline:
|
||||
|
||||
```bash
|
||||
HERMES_WEBUI_AGENT_DIR=/custom/path ./start.sh 9000
|
||||
```
|
||||
|
||||
Full list of environment variables:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `HERMES_WEBUI_AGENT_DIR` | auto-discovered | Path to the hermes-agent checkout |
|
||||
| `HERMES_WEBUI_PYTHON` | auto-discovered | Python executable |
|
||||
| `HERMES_WEBUI_HOST` | `127.0.0.1` | Bind address (`0.0.0.0` for all IPv4, `::` for all IPv6, `::1` for IPv6 loopback) |
|
||||
| `HERMES_WEBUI_PORT` | `8787` | Port |
|
||||
| `HERMES_WEBUI_STATE_DIR` | `$HERMES_HOME/webui` (Windows default `%LOCALAPPDATA%\hermes\webui`, POSIX default `~/.hermes/webui`) | Where sessions and state are stored |
|
||||
| `HERMES_WEBUI_DEFAULT_WORKSPACE` | `~/workspace` | Default workspace |
|
||||
| `HERMES_WEBUI_DEFAULT_MODEL` | *(provider default)* | Optional model override; leave unset to use the active Hermes provider default |
|
||||
| `HERMES_WEBUI_PASSWORD` | *(unset)* | Set to enable password authentication |
|
||||
| `HERMES_WEBUI_CSP_CONNECT_EXTRA` | *(unset)* | Optional space-separated `http(s)://` or `ws(s)://` origins to append to the report-only CSP `connect-src` directive for reverse-proxy or tunnel deployments |
|
||||
| `HERMES_WEBUI_EXTENSION_DIR` | *(unset)* | Optional local directory served at `/extensions/`; must point to an existing directory before extension injection is enabled |
|
||||
| `HERMES_WEBUI_EXTENSION_SCRIPT_URLS` | *(unset)* | Optional comma-separated same-origin script URLs to inject; see [WebUI Extensions](docs/EXTENSIONS.md) |
|
||||
| `HERMES_WEBUI_EXTENSION_STYLESHEET_URLS` | *(unset)* | Optional comma-separated same-origin stylesheet URLs to inject; see [WebUI Extensions](docs/EXTENSIONS.md) |
|
||||
| `HERMES_HOME` | Windows: `%LOCALAPPDATA%\hermes`; POSIX: `~/.hermes` | Base directory for Hermes state (affects all paths) |
|
||||
| `HERMES_CONFIG_PATH` | `$HERMES_HOME/config.yaml` | Path to Hermes config file |
|
||||
|
||||
---
|
||||
|
||||
### Remote access (SSH tunnel, Tailscale, phone)
|
||||
|
||||
The server binds to `127.0.0.1` by default. To reach it from another machine use an SSH tunnel (`ssh -N -L 8787:127.0.0.1:8787 user@host`, which `start.sh` prints for you over SSH), or join your server and phone to a [Tailscale](https://tailscale.com) network and browse to `http://<server-tailscale-ip>:8787` with `HERMES_WEBUI_HOST=0.0.0.0` + `HERMES_WEBUI_PASSWORD` set. Full walkthrough (incl. a community ARM64-Android field report): [`docs/remote-access.md`](docs/remote-access.md).
|
||||
|
||||
### Manual launch (without start.sh)
|
||||
|
||||
If you prefer to launch the server directly:
|
||||
|
||||
```bash
|
||||
cd /path/to/hermes-agent # or wherever sys.path can find Hermes modules
|
||||
HERMES_WEBUI_PORT=8787 venv/bin/python /path/to/hermes-webui/server.py
|
||||
```
|
||||
|
||||
Note: use the agent venv Python (or any Python environment that has the Hermes agent dependencies installed). System Python will be missing `openai`, `httpx`, and other required packages.
|
||||
|
||||
Health check:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8787/health
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
**Pre-built images** (amd64 + arm64) are published to GHCR on every release.
|
||||
|
||||
For a comprehensive setup guide covering all 3 compose files, common failure modes, and bind-mount migration, see [`docs/docker.md`](docs/docker.md). The README covers the 5-minute happy path.
|
||||
|
||||
### 5-minute quickstart (single container)
|
||||
|
||||
The simplest setup: one WebUI container that runs the agent in-process.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/nesquena/hermes-webui
|
||||
cd hermes-webui
|
||||
cp .env.docker.example .env
|
||||
# Edit .env if your host UID isn't 1000 (e.g. macOS where UIDs start at 501)
|
||||
docker compose up -d
|
||||
# Open http://localhost:8787
|
||||
```
|
||||
|
||||
Run Compose as the user who owns your Hermes home. `sudo docker compose up -d` can make `${HOME}` expand to the root user's home, so Docker mounts the wrong `.hermes` directory instead of your real `~/.hermes` and the WebUI starts with `config.yaml (not found, using defaults)`. Prefer adding your user to the Docker group and running `docker compose up -d`; if you must use sudo, set absolute paths first, for example `HERMES_HOME=/home/you/.hermes HERMES_WORKSPACE=/home/you/workspace sudo -E docker compose up -d`, then verify with `docker compose config`.
|
||||
|
||||
The container auto-detects your UID/GID from the mounted `~/.hermes` volume so files written by the agent stay readable by you on the host.
|
||||
|
||||
To enable password protection (required if you expose the port outside `127.0.0.1`):
|
||||
|
||||
```bash
|
||||
echo "HERMES_WEBUI_PASSWORD=change-me-to-something-strong" >> .env
|
||||
docker compose up -d --force-recreate
|
||||
```
|
||||
|
||||
### Manual `docker run` (no compose)
|
||||
|
||||
```bash
|
||||
docker pull ghcr.io/nesquena/hermes-webui:latest
|
||||
docker run -d \
|
||||
-e WANTED_UID=$(id -u) -e WANTED_GID=$(id -g) \
|
||||
-v ~/.hermes:/home/hermeswebui/.hermes \
|
||||
-e HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui \
|
||||
-v ~/workspace:/workspace \
|
||||
-p 127.0.0.1:8787:8787 \
|
||||
ghcr.io/nesquena/hermes-webui:latest
|
||||
```
|
||||
|
||||
### Build locally
|
||||
|
||||
```bash
|
||||
docker build -t hermes-webui .
|
||||
docker run -d \
|
||||
-e WANTED_UID=$(id -u) -e WANTED_GID=$(id -g) \
|
||||
-v ~/.hermes:/home/hermeswebui/.hermes \
|
||||
-e HERMES_WEBUI_STATE_DIR=/home/hermeswebui/.hermes/webui \
|
||||
-v ~/workspace:/workspace \
|
||||
-p 127.0.0.1:8787:8787 \
|
||||
hermes-webui
|
||||
```
|
||||
|
||||
### Multi-container setups
|
||||
|
||||
If you want the agent and WebUI in separate containers (for isolation, or because you're already running an agent gateway elsewhere):
|
||||
|
||||
```bash
|
||||
# Agent + WebUI
|
||||
docker compose -f docker-compose.two-container.yml up -d
|
||||
|
||||
# Agent + Dashboard + WebUI
|
||||
docker compose -f docker-compose.three-container.yml up -d
|
||||
```
|
||||
|
||||
Both compose files use **named Docker volumes** by default, which solves the UID/GID problem by construction. If you need bind mounts to share an existing host directory, see [`docs/docker.md`](docs/docker.md) for the full migration recipe.
|
||||
|
||||
> **Known limitation (#681)**: in the two-container setup, tools triggered from the WebUI run in the **WebUI container**, not the agent container. If you need git/node/etc. on the WebUI's filesystem, either use the single-container setup, extend the WebUI Dockerfile, or use the community [all-in-one image](https://github.com/sunnysktsang/hermes-suite).
|
||||
>
|
||||
> **Source boundary note (#2453)**: the multi-container setup mounts `hermes-agent-src` read-only into the WebUI by default. This prevents WebUI-side source rewrites but is still an implementation-coupling bridge, not a stable Agent API boundary. See [`docs/rfcs/agent-source-boundary.md`](docs/rfcs/agent-source-boundary.md) for the current source/API decoupling inventory.
|
||||
|
||||
### Common failure modes
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---|---|---|
|
||||
| `PermissionError` at startup | UID mismatch on bind mount | Set `UID=$(id -u)` in `.env` |
|
||||
| `.env: permission denied` (#1389) | `fix_credential_permissions()` enforced 0600 | Set `HERMES_SKIP_CHMOD=1` in `.env` |
|
||||
| Workspace appears empty | UID mismatch on `/workspace` mount | Set `UID=$(id -u)` in `.env` |
|
||||
| `git: command not found` in chat | Two-container architectural limit (#681) | Use single-container or extend Dockerfile |
|
||||
| WebUI can't find agent source | `hermes-agent-src` volume misconfigured | Use the named volumes from compose files as-is |
|
||||
| Podman shared `.hermes` fails | Podman 3.4 `keep-id` limitation | Use Podman 4+ or single-container |
|
||||
| Host API at `localhost` fails from WebUI | Container `localhost` means the container, not your host (#3012) | Use `http://host.docker.internal:<port>` on Docker Desktop, or `http://host.containers.internal:<port>` on Podman |
|
||||
| WebUI can't see `~/.hermes` after `sudo docker compose` | `${HOME}` expanded to the root user's home (#3006) | Run Compose as your user, or pass absolute `HERMES_HOME`/`HERMES_WORKSPACE` with `sudo -E` |
|
||||
|
||||
For the deep dive on each of these, see [`docs/docker.md`](docs/docker.md).
|
||||
|
||||
> **Note:** By default, Docker Compose binds to `127.0.0.1` (localhost only).
|
||||
> To expose on a network, change the port to `"8787:8787"` in `docker-compose.yml`
|
||||
> and set `HERMES_WEBUI_PASSWORD` to enable authentication.
|
||||
|
||||
---
|
||||
|
||||
## Running tests
|
||||
|
||||
Tests discover the repo and the Hermes agent dynamically -- no hardcoded paths.
|
||||
|
||||
```bash
|
||||
cd hermes-webui
|
||||
pytest tests/ -v --timeout=60
|
||||
```
|
||||
|
||||
Or using the agent venv explicitly:
|
||||
|
||||
```bash
|
||||
/path/to/hermes-agent/venv/bin/python -m pytest tests/ -v
|
||||
```
|
||||
|
||||
Tests run against an isolated server with a separate state directory.
|
||||
Production data and real cron jobs are never touched. Current snapshot:
|
||||
**~7,150 tests collected** across **~700 test files**, run in CI on Python 3.11,
|
||||
3.12, and 3.13 (3 parallel shards each).
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
No build step, no framework, no bundler — a Python standard-library HTTP server
|
||||
and vanilla JS. The backend lives in `api/`, the frontend in `static/`.
|
||||
|
||||
**Backend (`api/`)**
|
||||
|
||||
```
|
||||
server.py HTTP routing shell + auth middleware (~446 lines)
|
||||
server.py HTTP routing shell + auth middleware
|
||||
api/
|
||||
auth.py Optional password authentication, signed cookies (~366 lines)
|
||||
config.py Discovery, globals, model detection, reloadable config (~4139 lines)
|
||||
helpers.py HTTP helpers, security headers (~302 lines)
|
||||
models.py Session model + CRUD + CLI bridge (~1927 lines)
|
||||
onboarding.py First-run onboarding wizard, OAuth provider support (~1002 lines)
|
||||
profiles.py Profile state management, hermes_cli wrapper (~1056 lines)
|
||||
routes.py All GET + POST route handlers (~9772 lines)
|
||||
state_sync.py /insights sync — message_count to state.db (~118 lines)
|
||||
streaming.py SSE engine, run_agent, cancel support (~4420 lines)
|
||||
updates.py Self-update check and release notes (~545 lines)
|
||||
upload.py Multipart parser, file upload handler (~284 lines)
|
||||
workspace.py File ops, workspace helpers, git detection (~810 lines)
|
||||
static/
|
||||
index.html HTML template (~1323 lines)
|
||||
style.css All CSS incl. mobile responsive, themes (~3767 lines)
|
||||
ui.js DOM helpers, renderMd, tool cards, context indicator (~7216 lines)
|
||||
workspace.js File preview, file ops, git badge (~369 lines)
|
||||
sessions.js Session CRUD, collapsible groups, search, reload recovery (~3517 lines)
|
||||
messages.js send(), SSE handlers, live streaming, session recovery (~2301 lines)
|
||||
panels.js Cron, skills, memory, profiles, settings (~6480 lines)
|
||||
commands.js Slash command autocomplete (~1302 lines)
|
||||
boot.js Mobile nav, voice input, boot IIFE (~1607 lines)
|
||||
tests/
|
||||
conftest.py Isolated test server/state fixtures
|
||||
488 test files 5303 tests collected
|
||||
Dockerfile python:3.12-slim container image
|
||||
docker-compose.yml Compose with named volume and optional auth
|
||||
.github/workflows/ CI: multi-arch Docker build + GitHub Release on tag
|
||||
auth.py Optional password authentication, signed cookies, passkeys
|
||||
config.py Discovery, globals, model detection, reloadable config
|
||||
helpers.py HTTP helpers, security headers
|
||||
models.py Session model + CRUD + CLI/state.db bridge
|
||||
onboarding.py First-run onboarding wizard, OAuth provider support
|
||||
profiles.py Profile state management, hermes_cli wrapper
|
||||
routes.py All GET + POST route handlers (if/elif dispatch, no decorators)
|
||||
state_sync.py /insights sync — message_count to state.db
|
||||
streaming.py SSE engine, run_agent, cancellation, compression
|
||||
updates.py Self-update check and release notes
|
||||
upload.py Multipart parser, file upload handler
|
||||
workspace.py File ops, workspace helpers, git detection
|
||||
```
|
||||
|
||||
**Frontend (`static/`)**
|
||||
|
||||
```
|
||||
index.html HTML template
|
||||
style.css All CSS incl. mobile responsive, themes + skins
|
||||
ui.js DOM helpers, renderMd, tool cards, context indicator
|
||||
workspace.js File preview, file ops, git badge, central api() fetch wrapper
|
||||
sessions.js Session CRUD, collapsible groups, search, reload recovery
|
||||
messages.js send(), SSE handlers, live streaming, session recovery
|
||||
panels.js Cron, skills, memory, profiles, settings (Control Center)
|
||||
commands.js Slash command autocomplete
|
||||
boot.js Mobile nav, voice input, theme/skin boot, bfcache handler
|
||||
```
|
||||
|
||||
**Tests + packaging**
|
||||
|
||||
```
|
||||
tests/ Pytest suite (~7,150 tests; isolated server/state fixtures)
|
||||
pyproject.toml Tooling config (ruff lint gate) — not a packaged distribution
|
||||
Dockerfile python:3.12-slim container image
|
||||
docker-compose.yml Compose with named volume and optional auth
|
||||
.github/workflows/ CI: ruff + sharded pytest, browser smoke, Docker smoke,
|
||||
multi-arch Docker build + GitHub Release on tag
|
||||
```
|
||||
|
||||
State lives outside the repo at `~/.hermes/webui/` by default
|
||||
(sessions, workspaces, settings, projects, last_workspace). Override with `HERMES_WEBUI_STATE_DIR`.
|
||||
Full design notes and the endpoint catalog are in [`ARCHITECTURE.md`](ARCHITECTURE.md).
|
||||
|
||||
---
|
||||
|
||||
## Docs
|
||||
|
||||
- `docs/why-hermes.md` -- why Hermes, mental model, and detailed comparison to Claude Code / Codex / OpenCode / Cursor
|
||||
- `ROADMAP.md` -- feature roadmap and sprint history
|
||||
- `ARCHITECTURE.md` -- system design, all API endpoints, implementation notes
|
||||
- `TESTING.md` -- manual browser test plan and automated coverage reference
|
||||
- `CHANGELOG.md` -- release notes per sprint
|
||||
- `SPRINTS.md` -- forward sprint plan with CLI + Claude parity targets
|
||||
- `THEMES.md` -- theme system documentation, custom theme guide
|
||||
- `docs/CONTRACTS.md` -- project contract/RFC/design index for contributors and agents
|
||||
- `docs/UIUX-GUIDE.md` -- UI/UX principles sourced from existing design docs and visual inventories
|
||||
- `docs/docker.md` -- Docker compose setup, common failures, and bind-mount migration
|
||||
- `docs/supervisor.md` -- launchd, systemd, supervisord, runit, and s6 process-supervisor setup
|
||||
- `docs/onboarding.md` -- first-run wizard, provider setup, local model server Base URLs, and safe re-runs
|
||||
- `docs/onboarding-agent-checklist.md` -- safety rules, evidence commands, and pass/fail checks for assistant-led install or reinstall support
|
||||
- `docs/troubleshooting.md` -- diagnostic flows for common failures (e.g. "AIAgent not available")
|
||||
- `docs/wsl-autostart.md` -- WSL2 auto-start at Windows login
|
||||
- `docs/EXTENSIONS.md` -- administrator-controlled WebUI extension injection
|
||||
- `docs/rfcs/README.md` -- RFC index for larger architecture and durability proposals
|
||||
**Start here**
|
||||
- [`docs/why-hermes.md`](docs/why-hermes.md) — why Hermes, the mental model, and a detailed comparison to Claude Code / Codex / OpenCode / Cursor
|
||||
- [`docs/onboarding.md`](docs/onboarding.md) — first-run wizard, provider setup, local model server Base URLs, and safe re-runs
|
||||
- [`docs/troubleshooting.md`](docs/troubleshooting.md) — diagnostic flows for common failures (e.g. "AIAgent not available")
|
||||
|
||||
**Using & customizing**
|
||||
- [`THEMES.md`](THEMES.md) — theme + skin system, custom theme guide
|
||||
- [`docs/workspace-git.md`](docs/workspace-git.md) — the workspace Git controls
|
||||
- [`docs/EXTENSIONS.md`](docs/EXTENSIONS.md) — administrator-controlled WebUI extension injection
|
||||
|
||||
**Deploying & operating**
|
||||
- [`docs/remote-access.md`](docs/remote-access.md) — SSH tunnel, Tailscale, and phone access (incl. a community ARM64-Android field report)
|
||||
- [`docs/advanced-chat-setup.md`](docs/advanced-chat-setup.md) — optional dynamic recall-prefill and Gateway-backed browser chat for self-hosted deployments
|
||||
- [`docs/docker.md`](docs/docker.md) — Docker compose setup, common failures, and bind-mount migration
|
||||
- [`docs/supervisor.md`](docs/supervisor.md) — launchd, systemd, supervisord, runit, and s6 process-supervisor setup
|
||||
- [`docs/wsl-autostart.md`](docs/wsl-autostart.md) — WSL2 auto-start at Windows login
|
||||
- [`docs/onboarding-agent-checklist.md`](docs/onboarding-agent-checklist.md) — safety rules and pass/fail checks for assistant-led install/reinstall support
|
||||
|
||||
**Contributing & design**
|
||||
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — contribution style, PR expectations, and local verification
|
||||
- [`ARCHITECTURE.md`](ARCHITECTURE.md) — system design, all API endpoints, implementation notes
|
||||
- [`TESTING.md`](TESTING.md) — manual browser test plan and automated coverage reference
|
||||
- [`DESIGN.md`](DESIGN.md) — design tokens and the calm-console direction
|
||||
- [`docs/UIUX-GUIDE.md`](docs/UIUX-GUIDE.md) — UI/UX principles sourced from the design docs and visual inventories
|
||||
- [`docs/CONTRACTS.md`](docs/CONTRACTS.md) — project contract/RFC/design index for contributors and agents
|
||||
- [`docs/rfcs/README.md`](docs/rfcs/README.md) — RFC index for larger architecture and durability proposals
|
||||
|
||||
**Release history & plan**
|
||||
- [`CHANGELOG.md`](CHANGELOG.md) — release notes per version
|
||||
- [`ROADMAP.md`](ROADMAP.md) — feature roadmap and sprint history
|
||||
- [`SPRINTS.md`](SPRINTS.md) — forward sprint plan with CLI + Claude parity targets
|
||||
- [`CONTRIBUTORS.md`](CONTRIBUTORS.md) — the full community credit roll
|
||||
|
||||
---
|
||||
|
||||
## Contributors
|
||||
|
||||
Hermes WebUI is built with help from the open-source community. Every PR — whether merged directly, absorbed into a batch release, or salvaged from a larger proposal — shapes the project, and we're grateful to everyone who has taken the time to contribute.
|
||||
|
||||
**137 contributors have shipped code that landed in a release tag** as of v0.51.58. The full credit roll lives in [`CONTRIBUTORS.md`](CONTRIBUTORS.md). The highlights:
|
||||
Over **190 contributors** have shipped code that landed in a release tag. The full,
|
||||
continuously-updated credit roll — including everyone with one or two PRs and the
|
||||
special-thanks roll for design and architectural work — lives in
|
||||
[`CONTRIBUTORS.md`](CONTRIBUTORS.md). A snapshot of the most prolific contributors:
|
||||
|
||||
### Top contributors (by PR count, including absorbed/batch-released work)
|
||||
|
||||
| # | Contributor | PRs | First → latest release |
|
||||
|---|---|---:|---|
|
||||
| 1 | [@franksong2702](https://github.com/franksong2702) | 117 | `v0.49.3` → `v0.51.58` |
|
||||
| 2 | [@Michaelyklam](https://github.com/Michaelyklam) | 92 | `v0.50.240` → `v0.51.57` |
|
||||
| 3 | [@bergeouss](https://github.com/bergeouss) | 62 | `v0.48.0` → `v0.51.46` |
|
||||
| 4 | [@ai-ag2026](https://github.com/ai-ag2026) | 55 | `v0.50.279` → `v0.51.47` |
|
||||
| 5 | [@dso2ng](https://github.com/dso2ng) | 23 | `v0.50.227` → `v0.51.51` |
|
||||
| 6 | [@jasonjcwu](https://github.com/jasonjcwu) | 16 | `v0.50.227` → `v0.51.55` |
|
||||
| 7 | [@Jordan-SkyLF](https://github.com/Jordan-SkyLF) | 12 | `v0.50.18` → `v0.51.58` |
|
||||
| 8 | [@aronprins](https://github.com/aronprins) | 10 | `v0.44.0` → `v0.50.233` |
|
||||
| 9 | [@JKJameson](https://github.com/JKJameson) | 10 | `v0.50.233` → `v0.51.31` |
|
||||
| 10 | [@starship-s](https://github.com/starship-s) | 10 | `v0.50.128` → `v0.51.58` |
|
||||
| 1 | [@franksong2702](https://github.com/franksong2702) | 148 | `v0.49.3` → `v0.51.153` |
|
||||
| 2 | [@Michaelyklam](https://github.com/Michaelyklam) | 117 | `v0.50.240` → `v0.51.139` |
|
||||
| 3 | [@bergeouss](https://github.com/bergeouss) | 70 | `v0.48.0` → `v0.51.46` |
|
||||
| 4 | [@ai-ag2026](https://github.com/ai-ag2026) | 67 | `v0.50.279` → `v0.51.190` |
|
||||
| 5 | [@dso2ng](https://github.com/dso2ng) | 25 | `v0.50.227` → `v0.51.153` |
|
||||
| 6 | [@AJV20](https://github.com/AJV20) | 24 | `v0.51.93` → `v0.51.188` |
|
||||
| 7 | [@starship-s](https://github.com/starship-s) | 19 | `v0.50.123` → `v0.51.153` |
|
||||
| 8 | [@jasonjcwu](https://github.com/jasonjcwu) | 16 | `v0.50.227` → `v0.51.132` |
|
||||
| 9 | [@dobby-d-elf](https://github.com/dobby-d-elf) | 15 | `v0.51.38` → `v0.51.161` |
|
||||
| 10 | [@Jordan-SkyLF](https://github.com/Jordan-SkyLF) | 12 | `v0.50.18` → `v0.51.66` |
|
||||
|
||||
See [`CONTRIBUTORS.md`](CONTRIBUTORS.md) for the full ranked list of all 137 contributors, including everyone with one or two PRs and the special-thanks roll for design and architectural contributions.
|
||||
See [`CONTRIBUTORS.md`](CONTRIBUTORS.md) for the full ranked list of all 194 contributors, including everyone with one or two PRs and the special-thanks roll for design and architectural contributions.
|
||||
|
||||
### Notable contributions
|
||||
|
||||
**[@franksong2702](https://github.com/franksong2702)** — Most prolific external contributor (117 PRs, `v0.49.3` → `v0.51.58`)
|
||||
**[@franksong2702](https://github.com/franksong2702)** — Most prolific external contributor (148 PRs, `v0.49.3` → `v0.51.153`)
|
||||
Across the longest tenure of any external contributor: the session title guard (#301), breadcrumb workspace navigation (#302), embedded workspace terminal (#1099), worktree-backed session creation (#2053), onboarding documentation (#2052), composer footer container queries, streaming-session sidebar exemption (#1327), session sidecar repair, cron output preservation (#1295), profile default workspace persistence, manual `/compress` async start/status endpoints (#2128), worktree status surface (#2109) + guarded remove (#2156) for the lifecycle umbrella #2057, session post-render dedup (#2166), native-WebUI fast path (#2170), tail-window response trim (#2171), stale-stream guard extension (#2158), CSP report collector (#2160), and a long tail of polish across mobile/responsive, the session sidebar, and the workspace state machine.
|
||||
|
||||
**[@Michaelyklam](https://github.com/Michaelyklam)** — Most prolific contributor of recent releases (92 PRs, `v0.50.240` → `v0.51.57`)
|
||||
**[@Michaelyklam](https://github.com/Michaelyklam)** — Most prolific contributor of recent releases (117 PRs, `v0.50.240` → `v0.51.139`)
|
||||
Production Docker hardening (#1921, drops sudo-capable staging user), profile-scoped skills endpoints (#1903), gateway PID resolution under profile-scoped HERMES_HOME (#1901), profile-aware AIAgent cache (#1898/#1904), backslash LaTeX delimiters (#1848), Codex quota error surfacing (#1770), shell-route HTML 503 (#1836), stale Kanban client recovery (#1828), context auto-compression toast lifetime (#1988), `/goal` command (#1866), Kanban detail-view scrolling (#1916), CLI session tool metadata preservation (#1778), Traditional Chinese kanban locale backfill (#1979), v0.51.51 mobile Insights bucketing/layout (#2120/#2121), Hermes run adapter RFC (#2105 for #1925), fork-from-here absolute index (#2198 for #2184), opencode-go custom-provider overlap routing (#2204 for #1894).
|
||||
|
||||
**[@bergeouss](https://github.com/bergeouss)** — Provider management UI + Docker hardening (62 PRs, `v0.48.0` → `v0.51.46`)
|
||||
**[@bergeouss](https://github.com/bergeouss)** — Provider management UI + Docker hardening (70 PRs, `v0.48.0` → `v0.51.46`)
|
||||
Provider management UI for adding/editing custom providers from Settings, OAuth provider status detection (#1552), two-container Docker setup, profile isolation hardening (per-profile `.env` secrets), the bulk of what users see when they touch Settings → Providers, Reveal-in-Finder context menu (#1551), gateway status card (#1552), auto-assign session to active project filter (#1550), "What's new?" link in update banner (#1549), OpenRouter free-tier live fetch (#1548), credential pool 401 self-heal (#1553), inline provider chip + group model count in model picker (#1644).
|
||||
|
||||
**[@ai-ag2026](https://github.com/ai-ag2026)** — Session recovery + audit infrastructure (55 PRs, `v0.50.279` → `v0.51.47`)
|
||||
**[@ai-ag2026](https://github.com/ai-ag2026)** — Session recovery + audit infrastructure (67 PRs, `v0.50.279` → `v0.51.190`)
|
||||
Autonomous-AI contributor (Hermes Agent-driven) focused on durability: `state.db`-backed sidecar reconciliation (#2041), orphan `.json.bak` recovery on startup (#2035), read-only session recovery audit endpoints (#2036, #2040), active run lifecycle in `/health` (#2039), crash-safe turn-journal RFC at `docs/rfcs/turn-journal.md` (#2042), append-only turn-journal helper (#2059), lifecycle events layer (#2062), `Content-Security-Policy-Report-Only` header (#2084), per-cron toast toggle (#2100), fork-session compression lineage isolation (#2014).
|
||||
|
||||
**[@dso2ng](https://github.com/dso2ng)** — Session lineage + diagnostics (23 PRs, `v0.50.227` → `v0.51.51`)
|
||||
**[@dso2ng](https://github.com/dso2ng)** — Session lineage + diagnostics (25 PRs, `v0.50.227` → `v0.51.153`)
|
||||
`/api/session/lineage-report/<sid>` endpoint for bounded session graph diagnostics (#2012), stale Mermaid render error cleanup (#1337), `session_source="fork"` continuation-chain isolation (#2063), lazy lineage-report fetch on sidebar badge expand (#2130), and a long tail of frontend reliability fixes around session loading.
|
||||
|
||||
**[@jasonjcwu](https://github.com/jasonjcwu)** — Composer + transcript polish (16 PRs, `v0.50.227` → `v0.51.55`)
|
||||
**[@jasonjcwu](https://github.com/jasonjcwu)** — Composer + transcript polish (16 PRs, `v0.50.227` → `v0.51.132`)
|
||||
Sidebar collapse via active-rail click (#2054, fuses #1884 + #1924), composer chip lightbox (#1758), title fixes for tool-heavy first turns, silent compress-status during session switch (#2185), concurrent-send loss fix (#2186), in-transcript steer message badges (#2187), and a string of frontend polish fixes.
|
||||
|
||||
**[@Jordan-SkyLF](https://github.com/Jordan-SkyLF)** — Live streaming + UX polish (12 PRs, `v0.50.18` → `v0.51.58`)
|
||||
**[@Jordan-SkyLF](https://github.com/Jordan-SkyLF)** — Live streaming + UX polish (12 PRs, `v0.50.18` → `v0.51.66`)
|
||||
Original sprint of workspace fallback resolution, live reasoning cards (#366, #367, #394–#397), then a recent burst: manual "Refresh usage" button on the Provider quota card (#2150), cancelled-turn status classification (#2151), Firefox sidebar scroll stabilization (#2200), early provisional session titles (#2202), target-aware "What's new?" update-banner links (#2207), and MCP tools overflow fix in Settings (#2210).
|
||||
|
||||
**[@aronprins](https://github.com/aronprins)** — `v0.50.0` UI overhaul (PR #242, plus 9 follow-ups)
|
||||
@@ -720,10 +630,10 @@ Six consecutive, focused security PRs: session memory leak fix (expired token pr
|
||||
**[@lucasrc](https://github.com/lucasrc)** — Auth-hardening trilogy (PRs #2191, #2192, #2193)
|
||||
Three coordinated security PRs that all landed in v0.51.57: thread-safe login rate limiter with PBKDF2 key separation, password-hash cache invalidation on Settings save, and the full 64-char HMAC-SHA256 session signature with a backwards-compatible migration bridge. The kind of cleanly-decomposed security work that's reviewable as three independent pieces.
|
||||
|
||||
**[@LumenYoung](https://github.com/LumenYoung)** — Streaming hot-path correctness (4 PRs, `v0.51.47` → `v0.51.55`)
|
||||
**[@LumenYoung](https://github.com/LumenYoung)** — Streaming hot-path correctness (8 PRs, `v0.51.47` → `v0.51.99`)
|
||||
The original stale-stream writeback guard (#2136 — the bug class the next two releases extended), gateway-state alive-null classification (#2075), compression-banner anchor alignment (#2182), and context-progress ring auto-refresh on compression complete (#2188). Each PR opened a small surgical fix in one of the most fragile subsystems in the codebase.
|
||||
|
||||
**[@dobby-d-elf](https://github.com/dobby-d-elf)** — Frontend reliability + motion polish (6 PRs, `v0.51.38` → `v0.51.58`)
|
||||
**[@dobby-d-elf](https://github.com/dobby-d-elf)** — Frontend reliability + motion polish (15 PRs, `v0.51.38` → `v0.51.161`)
|
||||
Workspace fallback on deleted directories (#2138), iPhone PWA bottom-scroll fix (#2143), the new "Activity: X tools" composer footer shimmer animation (#2203), and follow-up animation tuning (#2212).
|
||||
|
||||
**[@JKJameson](https://github.com/JKJameson)** — Composer + session polish (10 PRs)
|
||||
@@ -786,6 +696,8 @@ A comprehensive CSRF / SSRF / XSS / env-race-condition audit that shipped in v0.
|
||||
**[@TaraTheStar](https://github.com/TaraTheStar)** — Bot name + thinking blocks + login refactor (PRs #132, #176, #181)
|
||||
Configurable assistant display name, thinking/reasoning block display, and a login page refactor.
|
||||
|
||||
---
|
||||
|
||||
## Repo
|
||||
|
||||
```
|
||||
|
||||
+9
-7
@@ -2,7 +2,7 @@
|
||||
|
||||
> Web companion to the Hermes Agent CLI. Same workflows, browser-native.
|
||||
>
|
||||
> Last updated: v0.51.31 (May 9, 2026) — 5028 tests collected — Release H 12-PR contributor batch (image-mode fix + race fixes + composer drafts + locale parity + custom-provider dedup + TTL config + heartbeat polish)
|
||||
> Last updated: v0.51.192 (May 31, 2026) — ~7,150 tests collected. Recent themes: forward-looking ruff lint gate (#3273), Windows upgrade state-stranding hotfix (#2905), per-model context_length default-only guard (#3256), gateway-configured banner + tooltip i18n, and an ongoing concentric PR-triage cadence.
|
||||
> Test source: `pytest tests/ --collect-only -q`
|
||||
> Per-version detail: see [CHANGELOG.md](./CHANGELOG.md)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
| **Sessions + projects + search** | ✅ CRUD, content search, projects, tags, archive, fork, import |
|
||||
| **Mobile + Docker + auth** | ✅ Hamburger nav, slide-overs, password auth, GHCR images |
|
||||
| **Auxiliary surfaces** | ✅ Workspace tree + edit, cron CRUD, skills CRUD, memory write, MCP server UI |
|
||||
| **Visual polish** | ✅ 8 themes (incl. light/system/OLED/Sienna), Mermaid, KaTeX, syntax highlighting |
|
||||
| **Visual polish** | ✅ Light/dark/system themes × 11 skins (default, ares, mono, slate, poseidon, sisyphus, charizard, sienna, catppuccin, nous, geist-contrast), Mermaid, KaTeX, syntax highlighting |
|
||||
| **Native distribution** | ✅ macOS desktop app (universal arm64+x86_64 DMG, signed) — separate repo |
|
||||
|
||||
Remaining gaps and forward work live in [Forward Work](#forward-work) below.
|
||||
@@ -29,15 +29,17 @@ Remaining gaps and forward work live in [Forward Work](#forward-work) below.
|
||||
|
||||
| Layer | Files | Status |
|
||||
|---|---|---|
|
||||
| Python server | `server.py` (~165 lines) + `api/` modules (~20k lines) | Thin shell + auth middleware + business logic |
|
||||
| HTML template | `static/index.html` (~600 lines) | Served from disk |
|
||||
| CSS | `static/style.css` (~3k lines) | Themes, mobile responsive, KaTeX, table styles |
|
||||
| JavaScript | `static/{ui,sessions,messages,workspace,panels,boot,commands,icons,i18n,login,onboarding}.js` (~26k lines) | 11 modules served as static files |
|
||||
| Python server | `server.py` + `api/` modules | Thin HTTP shell + auth middleware over the `api/` business logic (config, sessions, streaming, profiles, routes, onboarding, workspace, updates, upload) |
|
||||
| HTML template | `static/index.html` | Served from disk |
|
||||
| CSS | `static/style.css` | Themes + skins, mobile responsive, KaTeX, table styles |
|
||||
| JavaScript | `static/{ui,sessions,messages,workspace,panels,boot,commands,icons,i18n,login,onboarding}.js` | Vanilla-JS modules served as static files — no bundler |
|
||||
| Service worker | `static/sw.js` | Offline shell cache, version-pinned assets |
|
||||
| Docker | `Dockerfile`, `docker-compose.yml` | `python:3.12-slim`, multi-arch (amd64+arm64), HEALTHCHECK |
|
||||
| CI/CD | `.github/workflows/release.yml` | Auto-release + GHCR publish on tag push |
|
||||
| CI/CD | `.github/workflows/` | ruff lint + sharded pytest + browser smoke + Docker smoke on every PR; auto-release + GHCR publish on tag push |
|
||||
| Test isolation | `tests/_pytest_port.py` | Per-worktree port + state-dir derivation, no collisions |
|
||||
|
||||
> Per-file line counts drift every release; see `ARCHITECTURE.md` for the current module map and `git ls-files` for exact sizes.
|
||||
|
||||
---
|
||||
|
||||
## Feature parity checklist
|
||||
|
||||
+4
-4
@@ -1,14 +1,14 @@
|
||||
# Hermes Web UI: Browser Testing Plan
|
||||
|
||||
> This document is for manual browser testing by you or by a Claude browser agent.
|
||||
> It covers user-facing features of the UI through v0.50.21 and later releases.
|
||||
> It covers user-facing features of the UI across current releases.
|
||||
> Each section is written as a step-by-step test procedure with expected outcomes.
|
||||
> A browser agent (e.g. Claude with Chrome access) can execute this plan directly.
|
||||
>
|
||||
> Prerequisites: SSH tunnel is active on port 8787. Open http://localhost:8787 in browser.
|
||||
> Server health check: curl http://127.0.0.1:8787/health should return {"status":"ok"}.
|
||||
>
|
||||
> Automated coverage: 5303 tests collected via `pytest tests/ --collect-only -q`. Tests run on every PR via GitHub Actions on Python 3.11, 3.12, and 3.13. The suite covers the bootstrap/static wizard, real provider config persistence (`config.yaml` + `.env`), the `/api/onboarding/*` backend, the onboarding skip/existing-config guard, CSS regression coverage for thinking/tool card animation, streaming session persistence, mobile layout breakpoints, locale parity across 11 languages, and hundreds of issue/PR-pinned regression tests.
|
||||
> Automated coverage: ~7,150 tests collected via `pytest tests/ --collect-only -q`. Tests run on every PR via GitHub Actions on Python 3.11, 3.12, and 3.13 (3 parallel shards each), alongside a ruff lint gate, a headless browser smoke test, and a Docker smoke test. The suite covers the bootstrap/static wizard, real provider config persistence (`config.yaml` + `.env`), the `/api/onboarding/*` backend, the onboarding skip/existing-config guard, CSS regression coverage for thinking/tool card animation, streaming session persistence, mobile layout breakpoints, locale parity across 11 languages, and hundreds of issue/PR-pinned regression tests.
|
||||
> Run: `pytest tests/ -v --timeout=60`
|
||||
>
|
||||
> Local regression focus: verify that a previously closed workspace panel stays visually closed from first paint through boot completion on desktop refresh; there should be no brief open-then-close flash.
|
||||
@@ -1925,8 +1925,8 @@ Bridged CLI sessions:
|
||||
|
||||
---
|
||||
|
||||
*Last updated: v0.51.54, May 13, 2026*
|
||||
*Total automated tests collected: 5303*
|
||||
*Last updated: v0.51.192, May 31, 2026*
|
||||
*Total automated tests collected: ~7,150 (run `pytest tests/ --collect-only -q` for the exact current count)*
|
||||
*Regression gate: tests/test_regressions.py*
|
||||
*Run: pytest tests/ -v --timeout=60*
|
||||
*Source: <repo>/*
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
# Advanced chat setup
|
||||
|
||||
Two optional features for self-hosted Hermes WebUI deployments. **Most users need neither** — the defaults (in-process chat, no prefill) work out of the box.
|
||||
|
||||
## Session recall prefill
|
||||
|
||||
WebUI can attach ephemeral prefill messages to new browser-originated
|
||||
agent turns. This is useful when a deployment already has a local recall or
|
||||
router script for Joplin, Obsidian, Notion, llm-wiki, or another third-party
|
||||
notes source and wants browser chat to know where durable context lives.
|
||||
|
||||
Prefer a compact router-style prefill (for example, "Joplin has the durable
|
||||
project context; use the available notes/search tools before answering
|
||||
detail-dependent questions") instead of dumping the full note corpus into every
|
||||
new browser session. The prefill should point the agent toward retrieval; the
|
||||
notes/search tools should provide the specific facts on demand.
|
||||
|
||||
Static JSON remains supported through `prefill_messages_file` or
|
||||
`HERMES_PREFILL_MESSAGES_FILE`. For dynamic recall, opt in explicitly with a
|
||||
WebUI-specific script hook:
|
||||
|
||||
```yaml
|
||||
webui_prefill_messages_script:
|
||||
- python3
|
||||
- /path/to/notes_recall.py
|
||||
webui_prefill_messages_script_timeout: 5
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```bash
|
||||
HERMES_WEBUI_PREFILL_MESSAGES_SCRIPT="python3 /path/to/notes_recall.py" \
|
||||
HERMES_WEBUI_PREFILL_MESSAGES_SCRIPT_TIMEOUT=5 \
|
||||
./ctl.sh restart
|
||||
```
|
||||
|
||||
The script may print either an OpenAI-style JSON message list, a JSON object with
|
||||
a `messages` list, or plain text; plain text is wrapped as one `user` prefill
|
||||
message so dynamic recall text becomes ordinary context instead of an extra
|
||||
system instruction. If the hook must provide system-level guidance, emit JSON
|
||||
messages with an explicit `role: "system"` entry instead. Script output is capped
|
||||
at 256 KiB before parsing. Parsed prefill context is then bounded by
|
||||
`webui_prefill_context_max_chars` or `HERMES_WEBUI_PREFILL_CONTEXT_MAX_CHARS`
|
||||
(default: 12,000 characters; set to `0` to disable). When a dynamic script
|
||||
exceeds the budget and a compact static prefill file is configured, WebUI falls
|
||||
back to that file. If no compact fallback is available, WebUI injects a short
|
||||
retrieval instruction instead of sending the oversized note/body payload with
|
||||
every new browser turn. The browser only receives a compact status event
|
||||
(`source`, `label`, message count, compaction metadata, and redacted errors),
|
||||
never the prefill message bodies.
|
||||
|
||||
## Gateway-backed browser chat
|
||||
|
||||
By default, browser chat runs through WebUI's in-process legacy runtime. Advanced
|
||||
self-hosted deployments can opt into routing new browser turns through a running
|
||||
Hermes Gateway API server while preserving the existing WebUI `/api/chat/start`
|
||||
and `/api/chat/stream` browser contract:
|
||||
|
||||
```bash
|
||||
HERMES_WEBUI_CHAT_BACKEND=gateway \
|
||||
HERMES_WEBUI_GATEWAY_BASE_URL=http://127.0.0.1:8642 \
|
||||
HERMES_WEBUI_GATEWAY_API_KEY=... \
|
||||
./ctl.sh restart
|
||||
```
|
||||
|
||||
`HERMES_WEBUI_CHAT_BACKEND` is intentionally strict: only `gateway`,
|
||||
`api_server`, or `api-server` enable the bridge. Generic truthy values such as
|
||||
`1` or `true` are ignored so existing deployments do not change execution
|
||||
ownership accidentally. If `HERMES_WEBUI_GATEWAY_API_KEY` is omitted, WebUI falls
|
||||
back to `API_SERVER_KEY` when present. When Gateway returns HTTP 401, WebUI
|
||||
reports a `gateway_auth_error` that points at this WebUI↔Gateway key mismatch
|
||||
rather than showing the Gateway's generic provider-style "Invalid API key" body.
|
||||
`/api/health/agent` also includes a redacted `gateway_chat` block so operators can
|
||||
see whether gateway mode, base URL, and API-key presence are configured without
|
||||
exposing the key value. That `gateway_chat` field is an operator diagnostic
|
||||
payload only; it is not currently rendered as a user-facing health banner in the
|
||||
browser UI.
|
||||
|
||||
The bridge is best used by operators who already run Hermes Gateway/API Server
|
||||
locally and want browser-originated chat to use the same runtime/tool path as
|
||||
messaging surfaces. Attachments, cancellation, approvals, and clarify prompts
|
||||
still follow WebUI's current compatibility path and may not match every messaging
|
||||
surface until the runtime-adapter migration is complete.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Remote access
|
||||
|
||||
How to reach a self-hosted Hermes WebUI from another machine or your phone.
|
||||
|
||||
## Accessing from a remote machine
|
||||
|
||||
The server binds to `127.0.0.1` by default (loopback only). If you are running
|
||||
Hermes on a VPS or remote server, use an SSH tunnel from your local machine:
|
||||
|
||||
```bash
|
||||
ssh -N -L <local-port>:127.0.0.1:<remote-port> <user>@<server-host>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
ssh -N -L 8787:127.0.0.1:8787 user@your.server.com
|
||||
```
|
||||
|
||||
Then open `http://localhost:8787` in your local browser.
|
||||
|
||||
`start.sh` will print this command for you automatically when it detects you
|
||||
are running over SSH.
|
||||
|
||||
---
|
||||
|
||||
## Accessing on your phone with Tailscale
|
||||
|
||||
[Tailscale](https://tailscale.com) is a zero-config mesh VPN built on
|
||||
WireGuard. Install it on your server and your phone, and they join the same
|
||||
private network -- no port forwarding, no SSH tunnels, no public exposure.
|
||||
|
||||
The Hermes Web UI is fully responsive with a mobile-optimized layout
|
||||
(hamburger sidebar, sidebar top tabs in the drawer, touch-friendly controls),
|
||||
so it works well as a daily-driver agent interface from your phone.
|
||||
|
||||
**Setup:**
|
||||
|
||||
1. Install [Tailscale](https://tailscale.com/download) on your server and
|
||||
your iPhone/Android.
|
||||
2. Start the WebUI listening on all interfaces with password auth enabled:
|
||||
|
||||
```bash
|
||||
HERMES_WEBUI_HOST=0.0.0.0 HERMES_WEBUI_PASSWORD=your-secret ./start.sh
|
||||
```
|
||||
|
||||
3. Open `http://<server-tailscale-ip>:8787` in your phone's browser
|
||||
(find your server's Tailscale IP in the Tailscale app or with
|
||||
`tailscale ip -4` on the server).
|
||||
|
||||
That's it. Traffic is encrypted end-to-end by WireGuard, and password auth
|
||||
protects the UI at the application level. You can add it to your home screen
|
||||
for an app-like experience.
|
||||
|
||||
### Community field report: ARM64 Android via AVF
|
||||
|
||||
A community report in [#2364](https://github.com/nesquena/hermes-webui/issues/2364)
|
||||
documents Hermes Agent + WebUI running on a mid-range ARM64 Android phone inside
|
||||
a Debian 12 VM via Android Virtualization Framework (AVF). The reported setup
|
||||
used a Xiaomi Redmi Note 13 Pro 4G, 3.8 GiB RAM allocated to the VM, 8 visible
|
||||
CPU cores, Chrome on Android at `localhost:8787`, and cloud-hosted inference.
|
||||
|
||||
This is not an official support baseline or provider/model benchmark, but it is
|
||||
a useful compatibility signal for mobile ARM64 experiments: the WebUI rendered
|
||||
smoothly in Chrome, ARM64 Debian worked for the agent stack, and the total local
|
||||
footprint was about 1.7 GB. Practical caveats from the report: first install can
|
||||
take longer when dependencies compile from source, Android browser tabs may
|
||||
reload when switching apps, and disabling battery optimization for the terminal
|
||||
or VM host may be needed for longer-running sessions.
|
||||
|
||||
> **Tip:** If using Docker, set `HERMES_WEBUI_HOST=0.0.0.0` in your
|
||||
> `docker-compose.yml` environment (already the default) and set
|
||||
> `HERMES_WEBUI_PASSWORD`.
|
||||
|
||||
---
|
||||
@@ -201,9 +201,14 @@ def test_gateway_auth_label_i18n_key_exists_for_every_locale():
|
||||
|
||||
|
||||
def test_gateway_chat_health_payload_is_documented_as_operator_diagnostic_only():
|
||||
readme = Path("README.md").read_text(encoding="utf-8")
|
||||
# The Gateway-backed-chat operator docs moved out of the README into
|
||||
# docs/advanced-chat-setup.md during the v0.51.192 README IA pass (it's a
|
||||
# niche self-hosted feature). The contract — that gateway_chat is documented
|
||||
# as an operator-only diagnostic, not a user-facing banner — now lives there.
|
||||
# CHANGELOG keeps its release-note entry. (Contract test moved with content.)
|
||||
advanced = Path("docs/advanced-chat-setup.md").read_text(encoding="utf-8")
|
||||
changelog = Path("CHANGELOG.md").read_text(encoding="utf-8")
|
||||
for text in (readme, changelog):
|
||||
for text in (advanced, changelog):
|
||||
assert "gateway_chat" in text
|
||||
assert "operator diagnostic" in text
|
||||
assert "not currently rendered as a user-facing health banner" in text
|
||||
|
||||
Reference in New Issue
Block a user