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).

Hermes Web UI — three-panel layout

Light mode with full profile support
Light mode with full profile support
Customize your settings, configure a password
Customize your settings, configure a password
Workspace file browser with inline preview
Workspace file browser with inline preview
Session projects, tags, and tool call cards
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:

  1. 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

#1952:

  • 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:

  1. Install Tailscale on your server and your iPhone/Android.
  2. 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
  1. 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 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:

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