Files
hermes-skills/open-webui-profile-setup/SKILL.md
Hermes Agent b0d790be34 Add all 104 active skills from all 16 Hermes profiles
12 unversioned skills now versioned at 1.0.0:
agent-communication, ascii-video, external-reasoning-augmentation,
jotty-notes-api, minecraft-modpack-server, obsidian, pokemon-player,
powerpoint, social-search, songwriting-and-ai-music,
workspace-context-organization, youtube-content

Total repo: 141 skills across all profile scopes
2026-07-04 11:44:04 -05:00

12 KiB

name, description, version, author, license, metadata
name description version author license metadata
open-webui-profile-setup Create new Hermes profiles for Open WebUI personas — clone, port assignment, API server config, and runbook generation. Covers the full pattern: Hermes-side profile creation + Open WebUI REST API configuration (groups, users, presets, connections, visibility grants, memory-isolation probes). 1.0.0 Hermes Agent MIT
hermes
tags related_skills
open-webui
hermes-profiles
api-server
runbooks
personas
hermes-config-internals
hermes-minimal-profile-setup

Open WebUI Profile Setup

Create new Hermes profiles that serve as API backends for Open WebUI personas. Each persona gets its own Hermes profile (separate port, separate memory, separate toolsets) and its own Open WebUI configuration (group, user, preset, connection). This skill covers both sides.

When to Use

  • Adding a new person to the Open WebUI instance (family member, guest, etc.)
  • Creating a new persona profile that needs memory isolation from existing users
  • Generating a runbook for a new profile so the setup is repeatable
  • Documenting the live state of the Open WebUI deployment

HARD RULE: Probe the Live System FIRST

Never trust documentation over live state. Before writing or updating any runbook, probe the live Open WebUI instance via its REST API. Documentation rots; the API is truth. Minimum probe surface:

  • GET /api/config — version, signup status, features
  • GET /api/v1/users/all — all users with IDs, emails, roles
  • GET /api/v1/groups/ — all groups with IDs, descriptions
  • POST /api/v1/groups/id/{id}/users — members of each group (POST, not GET!)
  • GET /api/v1/models — all models/presets with base_model_id, access_grants
  • GET /openai/config — all connections with URLs, key previews
  • GET /openai/models/{idx} — verify each connection resolves
  • GET /api/v1/audio/config — TTS/STT settings
  • ss -tlnp | grep 865 — which Hermes API servers are actually running
  • grep API_SERVER ~/.hermes/profiles/*/.env — which profiles have API server configs

Compile findings into an OpenWebUI.md document. See references/OpenWebUI-live-state-template.md for the section-by-section template. This document becomes the single source of truth — update it after every phase execution.

Architecture

Open WebUI (10.0.0.204:8080, Docker)
    |  POST /v1/chat/completions (OpenAI-compatible, SSE streaming)
    |  Multiple OpenAI connections, one per Hermes profile
    v
Hermes Agent API Servers (10.0.0.42)
    ├── open1  (8653) — Rob (operator)
    ├── openz  (8654) — Zoe (kid)
    ├── openg  (8655) — guests
    └── openj  (8656) — Jennifer (wife)

Memory isolation is enforced by which Hermes profile handles the chat. Each profile has its own memory scope. The Open WebUI preset's base_model_id determines routing.

Existing Profiles (port registry)

Profile Port Model name Owner Group
open1 8653 Hermes Agent (open1) Rob Operators
openz 8654 Hermes Agent (openz) Zoe Kids
openg 8655 Hermes Agent (openg) Guest Guests
openj 8656 Hermes Agent (openj) Jennifer Family

Reserved ports to avoid: 8644 (webhook), 8645 (wecom-callback), 8646 (msgraph-webhook). Use 8650+ for custom profiles. Next available: 8657.

Workflow

Phase 1: Hermes-side — create the profile

  1. Clone from open1 (the minimal template):

    hermes profile create <name> --clone-from open1
    
  2. Assign a port — pick the next available from the registry above. Add to ~/.hermes/profiles/<name>/.env:

    API_SERVER_ENABLED=true
    API_SERVER_PORT=<port>
    API_SERVER_HOST=0.0.0.0
    API_SERVER_KEY=<generate-a-strong-random-key>
    API_SERVER_MODEL_NAME=Hermes Agent (<name>)
    
  3. Install and start the gateway:

    hermes -p <name> gateway install --start-now --start-on-login
    
  4. Verify:

    ss -tlnp | grep <port>                  # expect 0.0.0.0:<port>
    curl -s http://127.0.0.1:<port>/health  # expect {"status":"ok"}
    
  5. Verify cross-host reachability from the Open WebUI host (use curl from Hermes host — SSH to 10.0.0.204 is NOT available, see gotcha #13):

    KEY=$(grep '^API_SERVER_KEY=' ~/.hermes/profiles/<name>/.env | cut -d= -f2-)
    curl -s -m 5 -H "Authorization: Bearer $KEY" http://10.0.0.42:<port>/v1/models
    # expect: {"object":"list","data":[{"id":"Hermes Agent (<name>)",...}]}
    

Phase 2: Generate the runbook

Read the most recent runbook as a template (currently openg_runbook.md or openj_runbook_for_jennifer.md in /home/n8n/workspace/open1/). Substitute:

  • Profile name, port, model name
  • Group name and description
  • User name, email, password, role
  • Preset id, name, TTS voice
  • Permission level (Operators-level for adults, Kids-level for children)
  • System prompt (empty for adults, guardrails for kids)

Write the runbook to /home/n8n/workspace/open1/<name>_runbook_for_<person>.md.

Phase 3: Open WebUI-side — execute the runbook

The runbook covers these steps in order:

  1. Verify prerequisites (gateway live, cross-host reachable)
  2. Admin signin to get JWT
  3. Add the new LLM connection (append to OPENAI_API_BASE_URLS)
  4. Create the group with appropriate permissions
  5. Create the user account (POST /api/v1/auths/add)
  6. Add user to group
  7. Create the preset (POST /api/v1/models/create)
  8. Set visibility grants for base model and preset
  9. Run memory-isolation probe
  10. Run full verification recipe

Permission Templates

Operators-level (adults: Rob, Jennifer, guests)

{
  "workspace": {
    "models": true, "knowledge": false, "prompts": false,
    "tools": false, "skills": false, "documents": true,
    "web_search": true, "image_generation": false,
    "code_interpreter": false, "web_browsing": false, "memory": true
  },
  "chat": {
    "stt": true, "tts": true, "file_upload": true,
    "delete": true, "edit": true, "share": true,
    "export": true, "call": true, "multiple_models": true, "temporary": true
  }
}

Kids-level (Zoe)

{
  "workspace": {
    "models": false, "knowledge": false, "prompts": false,
    "tools": false, "skills": false, "documents": false,
    "web_search": true, "image_generation": false,
    "code_interpreter": false, "web_browsing": false, "memory": true
  },
  "chat": {
    "stt": true, "tts": true, "file_upload": false,
    "delete": true, "edit": true, "share": false,
    "export": false, "call": false, "multiple_models": false, "temporary": false
  }
}

Critical Gotchas

  1. Open WebUI persists connection settings in its SQLite DB, NOT in /root/.env. Editing env vars after first launch has no effect. Use the REST API (/openai/config/update).

  2. OPENAI_API_BASE_URLS is plural and positional. Multiple connections live in one ordered list. open1=0, openz=1, openg=2, openj=3. When adding a new connection, pass ALL URLs (not just the new one) to preserve ordering.

  3. Model access_grants field shape is strict. Use principal_type (not type), principal_id (not group_id), permission (not access). Wrong field names are silently dropped — HTTP 200 with no effect. Always verify with a read-back.

  4. User creation endpoint is POST /api/v1/auths/add, NOT /api/v1/users/add (returns 405).

  5. List group users is POST, not GET. POST /api/v1/groups/id/{id}/users returns list[UserInfoResponse] (bare array of user objects), not {user_ids: [...]}. This bug existed in all three original runbooks and was fixed 2026-07-03.

  6. Add users to group returns GroupResponse, not {success: true}. Response includes member_count.

  7. The signin endpoint returns a JWT, not a cookie. All calls use Authorization: Bearer <token>. Token expires — re-signin on 401.

  8. New connections may take 5-10 seconds to discover models. If GET /openai/models/<idx> returns empty, wait and retry.

  9. Cross-grants break memory isolation. Never grant one profile's base model to another persona's group. The preset's base_model_id determines which Hermes profile (and thus which memory) handles the chat.

  10. The meta.system field layers ON TOP of Hermes' core system prompt. Set to empty for adults (Hermes default). Set to guardrail text for kids.

  11. API server must bind to 0.0.0.0, not 127.0.0.1. The Open WebUI container at 10.0.0.204 cannot reach 127.0.0.1 on the Hermes host.

  12. Passwords are bcrypt-hashed on creation. Plaintext in the POST body is fine. Never log it. Share credentials over a secure channel.

  13. SSH to 10.0.0.204 is NOT available from the Hermes host. All Open WebUI management must be done via REST API (curl from Hermes host) or browser (Admin Panel). Prerequisite checks use curl, not SSH.

  14. Preset display names can differ from the runbook plan. The live API showed "Max" instead of "Hermes-Operator" and "Suzie AI Assistant." instead of "Hermes-Kid". The runbook specifies the intended name; the UI may have been renamed after creation. Verify with live probe, not documentation.

  15. access_grants may return empty from API read-back. This is normal for admin; access control still works. Don't treat empty grants as a failure.

  16. base_model_id may not be visible in API read-back. Presets return (no base). The field was set at creation but the read endpoint may not expose it.

  17. New users see their base model AND their preset, not just the preset alone. Zoe sees ['Hermes Agent (openz)', 'Suzie AI Assistant.']; Jennifer sees ['Hermes Agent (openj)', 'Hermes-Family']. This is correct behavior — the visibility check should confirm the user sees their own base model + preset and does NOT see other groups' models/presets. The verification recipe's "expect: ONLY the new user's preset" is wrong — update it to "expect: the user's base model + preset, and no cross-group leakage."

  18. Stay scoped to the requested profile only. When executing Phase 3 for a specific profile, do NOT touch other profiles' gateways, configs, or connections. If a sibling profile's gateway isn't running, note it but do not start it — that's a separate task. The user's instruction to "ONLY setup openj" means exactly that.

Reference Runbooks

All in /home/n8n/workspace/open1/:

  • openz_runbook_for_zoe.md — Phase B (kid persona, restricted)
  • openg_runbook.md — Phase C (guest persona, Operators-level)
  • openj_runbook_for_jennifer.md — Phase D (wife persona, Operators-level)
  • open_web_ui_plan.md — Master plan (architecture, voice config, Part 13 admin creds, Part 15 user docs)
  • openwebui_build_checklist.md — Build/verification checklist

Verification Recipe (end of any phase)

TOKEN=<admin-jwt>

# 1. Connection working
curl -s -H "Authorization: Bearer $TOKEN" http://10.0.0.204:8080/openai/models/<idx>

# 2. All groups present
curl -s -H "Authorization: Bearer $TOKEN" http://10.0.0.204:8080/api/v1/groups/ | python3 -c 'import json,sys; print([g["name"] for g in json.load(sys.stdin)])'

# 3. All users present
curl -s -H "Authorization: Bearer $TOKEN" http://10.0.0.204:8080/api/v1/users/all | python3 -c '
import json, sys
d = json.load(sys.stdin)
print([(u["name"], u["role"]) for u in d.get("users", [])])
'

# 4. All presets with correct base models
curl -s -H "Authorization: Bearer $TOKEN" http://10.0.0.204:8080/api/v1/models | python3 -c '
import json, sys
for m in json.load(sys.stdin).get("data", []):
    print(m["name"], "→", m.get("base_model_id", "(no base)"))
'

# 5. Visibility from new user's perspective
NEW_TOKEN=$(curl -s -X POST -H "Content-Type: application/json" \
  -d '{"email":"<email>","password":"<password>"}' \
  http://10.0.0.204:8080/api/v1/auths/signin | python3 -c 'import json,sys; print(json.load(sys.stdin)["token"])')
curl -s -H "Authorization: Bearer $NEW_TOKEN" http://10.0.0.204:8080/api/v1/models | python3 -c 'import json,sys; print([m["name"] for m in json.load(sys.stdin)["data"]])'
# expect: the user's base model + preset (e.g. ['Hermes Agent (openj)', 'Hermes-Family']), and NO cross-group leakage (no Hermes-Operator, Hermes-Kid, etc.)

# 6. Memory-isolation probe
# Sign in as new user → send a retain → sign in as Rob → ask about it → expect "I don't have that"