# Open WebUI + Hermes Agent Integration

## Provider Setup

Hermes Agent is added as a second OpenAI provider in Open WebUI, not as a custom tool. Custom tools (function-calling tools) don't work with local GGUF models because llama.cpp doesn't implement OpenAI-style tool_calls.

### OpenAI Provider Config via API

POST to `/openai/config/update` with HTTP Basic auth (Bearer JWT token from `/api/v1/auths/signin`).

Key fields in the payload:
- `OPENAI_API_BASE_URLS`: array of base URLs — each must end with `/v1` (e.g. `http://host.docker.internal:18789/v1`)
- `OPENAI_API_KEYS`: array of API keys, one per URL, same length
- `OPENAI_API_CONFIGS`: dict keyed by index string (`"0"`, `"1"`) with per-provider metadata

The `/v1` suffix is critical because Open WebUI appends `/models` to the base URL. Without it, `http://host:18789/models` (404) instead of `http://host:18789/v1/models` (200).

### Model Aggregation Bug

When a secondary OpenAI provider returns model objects with extra fields (`permission`, `root`, `parent`), Open WebUI's `get_all_models()` can drop all models from the first provider. The merged `/api/v1/models` list shows only the secondary provider's models. Individual provider endpoints (`/openai/models/0`, `/openai/models/1`) work fine independently.

**Workaround:** Set `model_ids` in the provider config to a non-empty list — Open WebUI skips the live fetch and uses the static list, avoiding the merge entirely.

## UFW Configuration for Docker Networks

### The Problem

Docker compose creates a new bridge network per stack. Each `docker compose down + up` can assign a different bridge interface name (e.g. `br-8ab5c8a48abf`, then `br-256b9529f1e4`). UFW rules bound to a specific interface name (`allow in on br-XXXX`) become stale when the network is recreated.

### The Fix

Use **subnet-based rules** instead of interface-based rules:

```bash
sudo ufw allow proto tcp from 172.19.0.0/16 to any port 9292
sudo ufw allow proto tcp from 172.19.0.0/16 to any port 18789
```

This is stable across compose network recreations as long as the subnet doesn't change.

### Finding the Right Subnet

```bash
docker inspect <container> --format '{{.NetworkSettings.Networks.<network>.Gateway}}'
```

Or:
```bash
docker network inspect <network> --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
```

## Built-in "Open Terminal" vs Custom Tools

Open WebUI has a Workspace-level "Open Terminal" feature that provides an interactive browser terminal. This is distinct from the custom "tools" system (function calling):

| Feature | How it works | Works with local GGUF? |
|---------|-------------|----------------------|
| **Custom Tools** | OpenAI function_calls — model invokes them | **No** — llama.cpp doesn't implement tool_calls |
| **Open Terminal** | Workspace feature → WebSocket proxy → terminal server | **Yes** — runs separate from the LLM |
| **OpenAI Provider** | Standard chat completions | **Yes** — Hermes Agent as a model you select |

**Rule of thumb:** If you want the local model to *invoke* something during chat, it won't work with GGUF models. If you want a standalone service accessible alongside the chat, register it as an OpenAI provider or embed it in the Workspace.

## Key Error Messages & Resolutions

| Error | Cause | Fix |
|-------|-------|-----|
| `detail: "Unexpected error: HTTP Error: 404"` on `/openai/models/1` | Base URL missing `/v1` suffix | Ensure URL ends in `/v1` |
| Container cannot reach `host.docker.internal:18789` | UFW blocks routed Docker traffic | Add subnet-based UFW rule |
| Only `hermes-agent` appears in `/api/v1/models` | Model aggregation bug | Set `model_ids` in both provider configs |
| `curl: (28) Connection timeout` | UFW `deny (routed)` | Add `ufw allow in on br-*` or subnet rule |
| `OCI runtime exec failed: exec: "sqlite3": executable file not found` | sqlite3 not in container | Use REST API or mount the volume on host |
