---
name: hermes-web-dashboard
description: Deploy and configure a web-accessible chat interface for Hermes Agent — browser-based dashboard, reverse proxy, auth, and QQ bot integration.
metadata:
  version: "1.1.0"
  tags: [hermes, dashboard, web, nginx, chat, deployment]
---

# Hermes Web Dashboard

Deploy a web-accessible chat interface for Hermes Agent that works in browsers and messaging platforms.


## Notes on Model Selection
- See the [hermes-model-management](devops/hermes-model-management) for guidelines on model choice and management.

- User wants a browser-based chat UI for Hermes
- User wants to access Hermes from multiple devices / platforms
- User wants to share Hermes access with others via a URL
- User wants QQ bot or similar messaging platform integration
- User says "I want the web chat to be the same as the terminal" → use Option C (API Server)

## Architecture Options

### Option A: Hermes Built-in Dashboard

Hermes has a built-in web dashboard at port 9119.

```bash
# Start dashboard (local only)
hermes dashboard

# Start with TUI chat enabled, bind to all interfaces
hermes dashboard --host 0.0.0.0 --port 9119 --no-open --tui --insecure
```

**Limitations:**
- No built-in password protection (relies on session token injected into HTML)
- Token is generated server-side per page load — cannot be accessed programmatically
- No user management
- Chrome blocks with ERR_BLOCKED_BY_CLIENT on non-localhost IPs

### Option B: Standalone Web Chat Server (Basic)

Build a custom web chat server with cookie-based auth, proxied through Nginx.
Uses `hermes chat -Q -q` subprocess calls. Simple Q&A only, no tool calls.

See `templates/web-chat-server.py` for the full server implementation.

**Limitations:** No tool calls, no memory, separate agent instance per request.

### Option C: API Server Full Agent Mode (Recommended for "same as terminal")

The best way to make web chat "the same as terminal" is using Hermes's built-in **API Server** platform.
Provides OpenAI-compatible `/v1/chat/completions` routing through full AIAgent with all tools.

See `templates/web-chat-server-api.py` for the full implementation with memory injection.

## Deployment Steps (Option C)

### 1. Create the Web Chat Server

Save to `~/.hermes/webchat/server.py`. Copy from `templates/web-chat-server-api.py`.

Key configuration:
```python
PASSWORD = "YourPassword123!"  # Change this!
PORT = 8888
HOST = "0.0.0.0"
USE_SSL = False  # SSL handled by Nginx
API_SERVER_URL = "http://127.0.0.1:8080"
API_KEY = "your-api-server-key"
```

### 2. Start the Server

```bash
python3 ~/.hermes/webchat/server.py &
```

### 3. Configure Nginx Reverse Proxy

```nginx
server {
    listen 80;
    server_name _;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    location / {
        proxy_pass http://127.0.0.1:8888;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_read_timeout 120;
    }
}
```

```bash
ln -sf /etc/nginx/sites-available/hermes-dashboard /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t && nginx -s reload
```

### 4. Firewall

```bash
ufw allow 80/tcp comment 'Hermes WebChat'
ufw deny 9119/tcp comment 'Block direct dashboard access'
ufw allow 443/tcp comment 'HTTPS (if using SSL)'
```

### 5. Start Gateway with API Server

```bash
export API_SERVER_KEY=$(openssl rand -hex 32)
# Save the key! You'll need it for the web chat server config.

API_SERVER_ENABLED=true \
API_SERVER_KEY=$API_SERVER_KEY \
API_SERVER_PORT=8080 \
API_SERVER_HOST=127.0.0.1 \
API_SERVER_CORS_ORIGINS="*" \
hermes gateway run --accept-hooks
```

## Hermes CLI Invocation (for programmatic use)

When calling Hermes from a web server backend (Option B only):

```bash
# Correct quiet mode (no banner, no spinner)
hermes chat -Q -q "your message here" --yolo --accept-hooks

# Filter session_id metadata lines from output
```

**Common mistakes:**
- `hermes -z "message"` — doesn't work when message has spaces/special chars via subprocess
- `hermes chat -q "message"` without `-Q` — includes banner output
- Forgetting `--yolo` — will prompt for tool approvals
- Forgetting `--accept-hooks` — will hang on TTY prompts in non-interactive context

## SSL / HTTPS

### Self-Signed Certificate (development / trusted network only)

```bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/hermes.key -out /etc/nginx/hermes.crt \
  -subj "/CN=YOUR_IP" -addext "subjectAltName=IP:YOUR_IP"
```

**Warning:** Chrome shows ERR_CERT_AUTHORITY_INVALID. Users must manually bypass. Not suitable for production.

### Let's Encrypt (production, requires domain)

```bash
apt install certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com
```

## Achieving "Same Agent" Parity

When user says "the web chat should be the same as the terminal", they mean:
1. Same tools (browser, file, code, search) → Use API Server (Option C)
2. Same memory → Inject `USER.md`/`MEMORY.md` as system prompt
3. Same conversation context → Use `X-Hermes-Session-Id` header

All three are implemented in `templates/web-chat-server-api.py`.

**Important caveat:** Web chat and terminal are still separate agent instances.
They share context via session ID + memory injection, but are not literally the same process.
This is as close as possible without running the terminal agent as a network service.

## Known Pitfalls

1. **Chrome blocks HTTP to non-localhost IPs.** `ERR_BLOCKED_BY_CLIENT`. Chrome simply refuses to load insecure HTTP pages on the public internet. Use HTTPS (even self-signed) or access via localhost/trusted network.

2. **Nginx Basic Auth + Chrome = `ERR_BLOCKED_BY_CLIENT`.** Chrome blocks the auth dialog entirely. Do not use Basic Auth.

3. **Self-signed cert = Chrome blocks.** `ERR_CERT_AUTHORITY_INVALID`. Users must manually bypass. For trusted networks, HTTP is simpler.

4. **Port conflicts from stale processes.** Always `ss -tlnp | grep -E '(8080|8888|9119)'` and `pkill -f "webchat/server.py"` before restarting. Multiple accumulate from repeated restarts.

5. **USE_SSL mismatch.** Nginx handles SSL → backend MUST have `USE_SSL = False`. Both doing SSL = 502 Bad Gateway.

6. **CLI output filtering.** `hermes chat -Q -q` output includes `session_id:` and `⚠` warning lines. Filter before displaying.

7. **`--accept-hooks` required in server context.** Prevents TTY prompts that hang the subprocess.

8. **API Server 429 rate limits.** OpenRouter credential pool exhaustion. API Server retries 3 times then returns error. Wait 30-60s before retrying. Check: `tail -f ~/.hermes/logs/agent.log | grep 429`.

9. **API Server auth is Bearer token, NOT Dashboard session token.** `window.__HERMES_SESSION_TOKEN__` is only for Dashboard. API Server uses `Authorization: Bearer <API_SERVER_KEY>`.

10. **Stale gateway processes.** Use `hermes gateway stop` then verify port is free with `ss -tlnp | grep 8080`.

11. **Web chat ≠ terminal agent (by default).** Even with API Server, web chat creates separate instances. Use `X-Hermes-Session-Id` + memory injection for parity.

12. **Memory injection is one-way.** Web chat can read `USER.md`/`MEMORY.md` but won't automatically write back. Terminal agent handles memory persistence.

13. **BrokenPipeError on client disconnect.** When a user closes the browser mid-request, the server gets `BrokenPipeError: [Errno 32]`. This is harmless — the client already left. No action needed.

14. **Cookie Max-Age = 24h by default.** If user wants persistent login, increase `Max-Age` in the `Set-Cookie` header (e.g., `Max-Age=2592000` for 30 days).

15. **Memory injection adds latency.** Reading `USER.md`/`MEMORY.md` on every API call adds ~1-2s. For faster responses, cache the memory content and only reload when files change (check `os.path.getmtime()`).

16. **Shared session ID across users.** Using a fixed `X-Hermes-Session-Id` (e.g., `"hermes-main-session"`) means ALL web chat users share the same conversation context. For multi-user setups, use per-session IDs like `f"webchat-{sid[:8]}"`.

17. **`hermes gateway install --system` refuses root by default.** On VPS/root environments, must pass `--run-as-user root`: `sudo hermes gateway install --system --run-as-user root`. Without it: `ValueError: Refusing to install the gateway system service as root`.

18. **API Server env vars must be in the systemd unit file.** The Gateway process started by systemd does NOT inherit shell environment variables. `API_SERVER_ENABLED`, `API_SERVER_KEY`, `API_SERVER_PORT`, `API_SERVER_HOST`, `API_SERVER_CORS_ORIGINS` must all be `Environment=` lines in `[Service]` section. Verify with `systemctl show hermes-gateway --property=Environment`.

19. **`RestartMaxDelaySec` and `RestartSteps` not supported on older systemd.** Ubuntu 22.04's systemd (v249) doesn't recognize these keys — logs `Unknown key name 'RestartMaxDelaySec' in section 'Service'`. Use only `Restart=always` + `RestartSec=60` for compatibility.

20. **Gateway starts but port 8080 takes ~5s to bind.** After `systemctl start hermes-gateway`, the process is "active (running)" before the API port is listening. Always verify with `ss -tlnp | grep 8080` before declaring success.

21. **Adding custom routes to webchat server.py.** To add new pages (e.g., a video viewer) to the webchat server:
    - Add a `serve_file(self, filepath, content_type)` method to the `Handler` class for static file serving
    - Add route checks in `do_GET()` BEFORE the default chat page fallback, AFTER the auth check
    - Define HTML page constants (e.g., `VIDEO_PAGE`) alongside `LOGIN_PAGE` and `CHAT_PAGE`
    - Place static assets in a known path (e.g., `/root/.hermes/webchat/static/`)
    - Restart with `systemctl restart hermes-webchat` (triggers approval gate — ask user to run it)
    - Pattern: check auth → check custom routes → serve static files → fallback to CHAT_PAGE

22. **Nginx static file serving for media.** To serve video/media files through Nginx:
    - Add a `location /media/` block pointing to your media directory
    - Place it BEFORE the catch-all `location /` proxy block
    - Example:
      ```nginx
      location /media/ {
          alias /var/www/html/media/;
          add_header Access-Control-Allow-Origin *;
      }
      ```
    - Place media files in the alias directory (e.g., `/var/www/html/media/`)
    - Access via `http://YOUR_IP/media/filename.mp4`
    - IMPORTANT: The `location /media/` block MUST come before `location /` or the proxy will catch all requests first** Not all models/endpoints support image input. When `vision_analyze` returns "No endpoints found that support image input", fall back to:
    - `ffprobe` for video metadata (duration, resolution, codec)
    - `ffmpeg -ss <time> -frames:v 1 out.jpg` to extract frames as JPEG
    - Serve frames via web server for user to view directly in browser
    - Do NOT retry `vision_analyze` more than once — it will keep failing on the same model

## QQ Bot Integration

### Prerequisites
1. Register a bot at [q.qq.com](https://q.qq.com) (requires real-name verification)
2. Note your **App ID** and **App Secret** from the developer portal
3. Enable required intents: C2C messages, Group @-messages, Guild messages
4. Configure in sandbox mode for testing, or publish for production

### Configuration

Add to `~/.hermes/.env`:
```bash
QQ_APP_ID=your-app-id
QQ_CLIENT_SECRET=your-app-secret
QQ_ALLOW_ALL_USERS=true
```

Or configure in `~/.hermes/config.yaml`:
```yaml
platforms:
  qq:
    enabled: true
    extra:
      app_id: "your-app-id"
      client_secret: "your-secret"
      markdown_support: true
      dm_policy: "open"
      group_policy: "open"
```

### Verification

After adding credentials, restart the gateway:
```bash
systemctl restart hermes-gateway
```

Check connection in logs:
```bash
tail -50 ~/.hermes/logs/gateway.log | grep -i qq
```

Expected output:
```
✓ qqbot connected
[QQBot:APPID] WebSocket connected to wss://api.sgroup.qq.com/websocket
[QQBot:APPID] Ready, session_id=...
```

### Voice Messages (STT)
- QQ provides built-in ASR (`asr_refer_text`) for voice messages — free, always tried first
- Fallback STT provider configurable via `QQ_STT_API_KEY`, `QQ_STT_BASE_URL`, `QQ_STT_MODEL`
- Default: Zhipu/GLM (`glm-asr` model)

### QQ Bot ≠ Web Chat
- QQ Bot connects via WebSocket to QQ's gateway — users message the bot in QQ
- Web Chat is a browser-based interface — users access via URL
- Both can connect to the same API Server backend
- QQ Bot session IDs should be prefixed: `X-Hermes-Session-Id: qq-{user_id}`

### Known QQ Bot Pitfalls
1. **Sandbox mode**: Bot can only receive messages from QQ's sandbox test channel until published
2. **Real-name verification**: Required for QQ developer account — cannot be skipped
3. **Token refresh**: Access tokens expire every 7200s — the adapter handles this automatically
4. **Group messages**: Bot must be @mentioned in groups unless group_policy is "open"
5. **SOUL.md / persona not propagating**: If SOUL.md is empty or generic, the model will reply with its own identity (e.g., "I am OWL") instead of the configured persona (e.g., "星璇"). Always ensure SOUL.md has the agent's name, personality, and key identity rules BEFORE connecting messaging platforms. After updating SOUL.md, restart the gateway: `systemctl restart hermes-gateway`.
6. **QQ model uses openrouter/owl-alpha by default**: The model name "OWL" may cause the agent to identify as OWL instead of the configured persona. SOUL.md must explicitly override this with "Your name is [NAME], not OWL or any other model name."

7. **CloudCone / US datacenter IPs → QQ WebSocket instability**: See the `hermes-gateway-troubleshooting` skill's `references/qq-websocket-cloudcone.md` for diagnosis and fix steps.

## Support Files

| File | Purpose |
|---|---|
| `templates/web-chat-server.py` | Basic web chat (CLI subprocess, simple Q&A) |
| `templates/web-chat-server-api.py` | Full agent web chat (API Server, tools + memory) |
| `references/session-notes.md` | Technical notes: session continuity, memory injection, pitfalls |
| `references/systemd-deployment.md` | systemd service templates, verification checklist, common issues |
| `references/custom-routes.md` | Adding custom pages and static file serving to webchat server.py |
| `references/qqbot-websocket-instability.md` | (deprecated — see `hermes-gateway-troubleshooting` skill's `references/qq-websocket-cloudcone.md`) |
