Hermes Web UI
Hermes Agent is a sophisticated autonomous agent that lives on your server, accessed via a terminal or
messaging apps, that remembers what it learns and gets more capable the longer it runs.
Hermes WebUI is a lightweight, dark-themed web app interface in your browser for Hermes Agent. Full parity
with the CLI experience - everything you can do from a terminal, you can do from this UI. No build step, no framework, no bundler. Just Python and
vanilla JS.
Layout: three-panel. Left sidebar for sessions and navigation, center for chat, right for workspace file browsing. Model, profile, and workspace
controls live in the composer footer — always visible while composing. A circular context ring shows token usage at a glance. All settings and
session tools are in the Hermes Control Center (launcher at the sidebar bottom).
| Light mode with full profile support |
Customize your settings, configure a password |
Workspace file browser with inline preview | Session projects, tags, and tool call cards |
This gives you nearly 1:1 parity with Hermes CLI from a convenient web UI which you can access securely through an SSH tunnel from your Hermes
setup. Single command to start this up, and a single command to SSH tunnel for access on your computer. Every single part of the web UI uses your
existing Hermes agent and existing models, without requiring any additional setup.
Why Hermes
Most AI tools reset every session. They don't know who you are, what you worked on, or what conventions your project follows. You re-explain yourself every time.
Hermes retains context across sessions, runs scheduled jobs while you're offline, and gets smarter about your environment the longer it runs. It uses
your existing Hermes agent setup, your existing models, and requires no additional configuration to start.
What makes it different from other agentic tools:
- Persistent memory — user profile, agent notes, and a skills system that saves reusable procedures; Hermes learns your environment and does not
have to relearn it - Self-hosted scheduling — cron jobs that fire while you're offline and deliver results to Telegram, Discord, Slack, Signal,
email, and more - 10+ messaging platforms — the same agent available in the terminal is reachable from your phone - Self-improving skills —
Hermes writes and saves its own skills automatically from experience; no marketplace to browse, no plugins to install - Provider-agnostic —
OpenAI, Anthropic, Google, DeepSeek, OpenRouter, and more - Orchestrates other agents — can spawn Claude Code or Codex for heavy coding tasks and
bring the results back into its own memory - Self-hosted — your conversations, your memory, your hardware
vs. the field (landscape is actively shifting — see docs/why-hermes.md for the full breakdown):
| | OpenClaw | Claude Code | Codex CLI | OpenCode | Hermes | |---|---|---|---|---|---| | Persistent memory (auto) | Yes | Partial† | Partial | Partial
| Yes | | Scheduled jobs (self-hosted) | Yes | No‡ | No | No | Yes | | Messaging app access | Yes (15+ platforms) | Partial (Telegram/Discord preview)
| No | No | Yes (10+) | | Web UI (self-hosted) | Dashboard only | No | No | Yes | Yes | | Self-improving skills | Partial | No | No | No | Yes | |
Python / ML ecosystem | No (Node.js) | No | No | No | Yes | | Provider-agnostic | Yes | No (Claude only) | Yes | Yes | Yes | | Open source | Yes (MIT)
| No | Yes | Yes | Yes |
† Claude Code has CLAUDE.md / MEMORY.md project context and rolling auto-memory, but not full automatic cross-session recall ‡ Claude Code has
cloud-managed scheduling (Anthropic infrastructure) and session-scoped /loop; no self-hosted cron
The closest competitor is OpenClaw — both are always-on, self-hosted, open-source agents with memory, cron, and messaging. The key differences:
Hermes writes and saves its own skills automatically as a core behavior (OpenClaw's skill system centers on a community marketplace); Hermes is more
stable across updates (OpenClaw has documented release regressions and ClawHub has had security incidents involving malicious skills); and Hermes runs
natively in the Python ecosystem. See docs/why-hermes.md for the full side-by-side.
Quick start
Run the repo bootstrap:
git clone https://github.com/nesquena/hermes-webui.git hermes-webui
cd hermes-webui
python3 bootstrap.py
Or keep using the shell launcher:
./start.sh
For self-hosted VM or homelab installs, ctl.sh wraps the common daemon lifecycle commands without requiring fuser or pkill:
```bash ./ctl.sh start # background daemon, PID at ~/.hermes/webui.pid ./ctl.sh status # PID, uptime, bound host/port, log path, /health ./ctl.sh logs
--lines 100 # tail ~/.hermes/webui.log ./ctl.sh restart ./ctl.sh stop ```
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
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:
webui_prefill_messages_script:
- python3
- /path/to/notes_recall.py
webui_prefill_messages_script_timeout: 5
or:
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:
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.
The bootstrap will:
- Detect Hermes Agent and, if missing, attempt the official installer (`curl -fsSL
https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash`). 2. Find or create a Python environment with the WebUI
dependencies. 3. Start the web server and wait for /health. 4. Open the browser unless you pass --no-browser. 5. Drop you into a first-run
onboarding wizard inside the WebUI.
Native Windows is not supported for this bootstrap yet. Use Linux, macOS, or WSL2. For Windows / WSL auto-start at login, see
docs/wsl-autostart.md.
A community-maintained native Windows setup is documented at
@markwang2658/hermes-windows-native-guide (companion setup repo:
@markwang2658/hermes-windows-native). Notes from the community report in
- Memory: community-measured ~330 MB native vs ~1080 MB with WSL2+Docker (varies by configuration). - What works: chat, workspace browser,
session management, all themes. - Known limitations: some POSIX-style file paths surface in the workspace browser; bash-assuming agent tools may
not work natively. - Native Windows setup: install Python 3.11+, then from the hermes-agent root in PowerShell: python -m venv venv → `pip
install -r requirements.txt→pwsh .\start.ps1(it auto-discoversvenv\Scripts\python.exe`). - WSL2 relationship: not a prerequisite — a
WSL2-built venv (venv/bin/python, ELF) isn't invokable by native Windows Python, so use the native setup above. WSL2 stays useful as a parallel
install if you want the full bootstrap.py + Linux runtime.
If provider setup is still incomplete after install, the onboarding wizard will point you to finish it with hermes model instead of trying to
replicate the full CLI setup in-browser. For a step-by-step walkthrough of the wizard, provider choices, local model server Base URLs, and safe
re-runs, see docs/onboarding.md. If an AI assistant is helping with install, reinstall, bootstrap, provider setup, or
first-run support, have it read docs/onboarding-agent-checklist.md before running commands or inspecting logs.
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. 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):
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):
# 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 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 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 | SetHERMES_SKIP_CHMOD=1in.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.
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:
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 | | HERMES_WEBUI_EXTENSION_STYLESHEET_URLS | (unset) | Optional comma-separated
same-origin stylesheet URLs to inject; see WebUI Extensions | | 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:
ssh -N -L <local-port>:127.0.0.1:<remote-port> <user>@<server-host>
Example:
ssh -N -L 8787:127.0.0.1:8787 [email protected]
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 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:
- Install Tailscale on your server and your iPhone/Android.
- Start the WebUI listening on all interfaces with password auth enabled:
HERMES_WEBUI_HOST=0.0.0.0 HERMES_WEBUI_PASSWORD=your-secret ./start.sh
- Open
http://<server-tailscale-ip>:8787in your phone's browser (find your server's Tailscale IP in the Tailscale app or withtailscale ip -4on 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 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.0in yourdocker-compose.ymlenvironment (already the default) and setHERMES_WEBUI_PASSWORD.
Manual launch (without start.sh)
If you prefer to launch the server directly:
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:
curl http://127.0.0.1:8787/health
Running tests
Tests discover the repo and the Hermes agent dynamically -- no hardcoded paths.
cd hermes-webui
pytest tests/ -v --timeout=60
Or using the agent venv explicitly:
/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 - Streaming responses via SSE (tokens appear as they are generated) - Multi-provider model support -- any Hermes API provider
(OpenAI, Anthropic, Google, DeepSeek, Nous Portal, OpenRouter, MiniMax, Xiaomi MiMo, Z.AI); dynamic model dropdown populated from configured keys -
Send a message while one is processing -- it queues automatically - Edit any past user message inline and regenerate from that point - Retry the last
assistant response with one click - Cancel a running task directly from the composer footer (Stop button next to Send) - Tool call cards inline --
each shows the tool name, args, and result snippet; expand/collapse all toggle for multi-tool turns - Subagent delegation cards -- child agent
activity shown with distinct icon and indented border - Mermaid diagram rendering inline (flowcharts, sequence diagrams, gantt charts) -
Thinking/reasoning display -- collapsible gold-themed cards for Claude extended thinking and o3 reasoning blocks - Approval card for dangerous shell
commands (allow once / session / always / deny) - SSE auto-reconnect on network blips (SSH tunnel resilience) - File attachments persist across page
reloads and are stored outside the active workspace by default (~/.hermes/webui/attachments/<session_id>/, or
HERMES_WEBUI_ATTACHMENT_DIR/<session_id>/ when configured) - Message timestamps (HH:MM next to each message, full date on hover) - Code block copy
button with "Copied!" feedback - Syntax highlighting via Prism.js (Python, JS, bash, JSON, SQL, and more) - Safe HTML rendering in AI responses (bold,
italic, code converted to markdown) - rAF-throttled token streaming for smoother rendering during long responses - Context usage indicator in composer
footer -- token count, cost, and fill bar (model-aware)
Sessions - Create, rename, duplicate, delete, search by title and message content - Session actions via ⋯ dropdown per session — pin, move to
project, archive, duplicate, delete - Pin/star sessions to the top of the sidebar (gold indicator) - Archive sessions (hide without deleting, toggle
to show) - Session projects -- named groups with colors for organizing sessions - Session tags -- add #tag to titles for colored chips and
click-to-filter - Grouped by Today / Yesterday / Earlier in the sidebar (collapsible date groups) - Download as Markdown transcript, full JSON export,
or import from JSON - Sessions persist across page reloads and SSH tunnel reconnects - Browser tab title reflects the active session name - CLI
session bridge -- CLI sessions from hermes-agent's SQLite store appear in the sidebar with a gold "cli" badge; click to import with full history and
reply normally - Token/cost display -- input tokens, output tokens, estimated cost shown per conversation (toggle in Settings or /usage command)
Workspace file browser - Directory tree with expand/collapse (single-click toggles, double-click navigates) - Breadcrumb navigation with clickable
path segments - Preview text, code, Markdown (rendered), and images inline - Chat links using workspace://path/to/file open files in the right-side
preview pane - Edit, create, delete, and rename files; create folders - Binary file download (auto-detected from server) - File preview auto-closes on
directory navigation (with unsaved-edit guard) - Git detection -- branch name and dirty file count badge in workspace header - Right panel is
drag-resizable - Syntax highlighted code preview (Prism.js)
Voice input - Microphone button in the composer (Web Speech API) - Tap to record, tap again or send to stop - Live interim transcription appears
in the textarea - Auto-stops after ~2s of silence - Appends to existing textarea content (doesn't replace) - Hidden when browser doesn't support Web
Speech API (Chrome, Edge, Safari)
Profiles - Profile chip in the composer footer -- dropdown showing all profiles with gateway status and model info - Gateway status dots
(green = running), model info, skill count per profile - Profiles management panel -- create, switch, and delete profiles from the sidebar - Clone
config from active profile on create - Optional custom endpoint fields on create -- Base URL and API key written into the profile's config.yaml at
creation time, so Ollama, LMStudio, and other local endpoints can be configured without editing files manually - Seamless switching -- no server
restart; reloads config, skills, memory, cron, models - Per-session profile tracking (records which profile was active at creation)
Authentication and security - Optional password auth -- off by default, zero friction for localhost - Enable via HERMES_WEBUI_PASSWORD env var
or Settings panel - Optional passkeys/WebAuthn -- register from Settings -> System after signing in with a password; the login page only shows passkey
sign-in after at least one passkey exists - After registering at least one passkey, Settings -> System can remove the password and keep passkey-only
sign-in enabled. Password auth remains the bootstrap/recovery path until you choose to go passwordless; passkeys are same-origin and stored locally in
the WebUI state directory - Signed HMAC HTTP-only cookie with 24h TTL - Minimal dark-themed login page at /login - Security headers on all responses
(X-Content-Type-Options, X-Frame-Options, Referrer-Policy) - 20MB POST body size limit - CDN resources pinned with SRI integrity hashes
Themes - Appearance is split into two axes: Theme (system, dark, light) and Skin (default, ares, mono, slate, poseidon,
sisyphus, charizard, sienna, catppuccin, nous, geist-contrast / Geist Contrast) - Switch via Settings -> Appearance (instant live preview)
or /theme <theme-or-skin> - Persists across reloads (server-side in set