Every provider in agent/models.yml is a LAN address, so a workstation that leaves the house has no model at all. Add `openwebui (franksdank.cloud)`: the already-public OpenWebUI vhost as an OpenAI-compatible provider that proxies the same engines, with frank-dragoon and frank-zealot rows and the WAN row as its own compaction target. LAN rows, modelRoles and the speech runner stay untouched — the door is purely additive. The key is command-backed (`apiKey: "!cat ~/.config/secrets/openwebui_api_key"`, mode 600) instead of inline: a literal sk-... in a tracked file is a key in every agent context on every box that pulls this repo, and the pre-commit shape gate would block it anyway. Rotate = rewrite the file + restart omp, because a `!cmd` value is cached for the process lifetime. Compat blocks copy their LAN twins because the proxy was probed, not assumed (2026-10-21): streaming SSE carries reasoning_content and tool_calls deltas plus final usage, image_url content parts reach both vision towers, and chat_template_kwargs is forwarded — proven on frank-zealot, where enable_thinking false yields zero reasoning and true a real block. frank-archon gets no WAN row: OpenWebUI answers 400 "Model not found" because its picker never published it, so that path is a fleet change. Verified end-to-end from a scratch HOME whose LAN baseUrls point at a dead port, so any answer can only have come through the door (text and a real tool round-trip); `omp models` still lists all four chat providers and the tts runner, so the file validates. bats 137/137, kb-lint clean. Docs: omp-docs/core/wan-model-access.md.
140 lines
6.7 KiB
Markdown
140 lines
6.7 KiB
Markdown
---
|
|
type: reference
|
|
title: WAN Model Access (ai.franksdank.cloud)
|
|
description: How to drive the fleet's models from outside the home network — the OpenWebUI door in agent/models.yml, the key file and its rotation, what the door does and does not publish, and the off-network failure modes.
|
|
tags:
|
|
- omp
|
|
- models-yml
|
|
- openwebui
|
|
- remote-access
|
|
- fleet
|
|
verified: 2026-10-21
|
|
timestamp: 2026-10-21
|
|
---
|
|
|
|
# WAN Model Access (ai.franksdank.cloud)
|
|
|
|
Every provider in `agent/models.yml` except one is a LAN address, so a laptop
|
|
that leaves the house loses the fleet. The exception is the
|
|
`openwebui (franksdank.cloud)` block: the fleet's already-public OpenWebUI vhost
|
|
speaks OpenAI-compatible HTTP at `/api/v1` and proxies to the same engines the
|
|
LAN rows point at. One hop further out, no second inference stack.
|
|
|
|
Read this page when you are off-network and a model call fails, when the key
|
|
needs rotating, or before adding another row behind the door.
|
|
|
|
## Run a session away from home
|
|
|
|
The door is added as a provider, never as a role default. `modelRoles` in
|
|
`agent/config.yml` still names LAN providers, so away from home you must name
|
|
the model explicitly:
|
|
|
|
```bash
|
|
omp --model 'openwebui (franksdank.cloud)/frank-dragoon:medium'
|
|
omp --model 'openwebui (franksdank.cloud)/frank-zealot:off'
|
|
```
|
|
|
|
The same two ids work in the `/model` picker inside a running session — the
|
|
provider label is what disambiguates them from the LAN rows, since the wire ids
|
|
are identical by design (OpenWebUI's picker row and the engine share the id, so
|
|
there is no alias to keep in sync).
|
|
|
|
## The key
|
|
|
|
`apiKey: "!cat ~/.config/secrets/openwebui_api_key"` — a **command-backed**
|
|
value, resolved by `src/config/resolve-config-value.ts` in the harness: stdout
|
|
is trimmed, an env-var name is tried next, and only then is the string taken
|
|
literally. The secret itself is a 600-mode file in `~/.config/secrets/`, the
|
|
same convention as `gitea` and `kagi_api_key`.
|
|
|
|
Laws:
|
|
|
|
- **Never inline the `sk-…`.** It is a tracked file; the pre-commit shape gate
|
|
blocks `sk-…` anywhere in the staged diff, and a key that slipped through
|
|
would live in every agent's context on every box that pulls this repo.
|
|
- **Rotation is: rewrite the secret file, then restart omp.** The `!cmd` result
|
|
is cached for the process lifetime, so a long-lived session keeps serving the
|
|
old key — a stale-session failure reads exactly like a revoked key.
|
|
- The key is an **OpenWebUI** key (User menu → Settings → Account → API keys),
|
|
not a fleet endpoint key. It carries that user's model ACL, so what the door
|
|
shows is whatever the picker shows that user.
|
|
- Every other workstation needs its own copy of the file; nothing in the repo
|
|
carries the value.
|
|
|
|
## What the door publishes
|
|
|
|
Probed 2026-10-21 with the fleet key.
|
|
|
|
|Behind the door|Notes|
|
|
|---|---|
|
|
|`frank-dragoon`|halogen flash-server, Dankclaw iGPU — same row as the LAN `llamacpp (dankclaw)`|
|
|
|`frank-zealot`|llama.cpp on Dankclaw's 3090 — same row as `llamacpp (dankclaw-3090)`; also the WAN compaction target|
|
|
|`frank-archon`|**not published.** `/api/v1/models` omits it and the API answers `400 "Model not found"`. Dankserver's endpoint has no public face at all. Giving it a WAN path is a fleet change on Dankclaw (publish it in the OpenWebUI picker), not a harness change|
|
|
|`frank-tts`|route exists, contract does not. OpenWebUI forwards `/api/v1/audio/speech` to `frank-tts:7772` but substitutes its own voice enum first, and a cloned-pack name comes back `400` from the upstream. `modelRoles.speech` stays on the LAN `frank-tts (dankclaw)` row|
|
|
|
|
## Contract verified through the proxy
|
|
|
|
Each of these was measured end-to-end on 2026-10-21 — they are why the compat
|
|
blocks in the WAN rows are copies of their LAN twins rather than simplifications:
|
|
|
|
- Streaming SSE carries `reasoning_content` **and** `tool_calls` deltas,
|
|
`finish_reason: tool_calls`, and a final `usage` under
|
|
`stream_options.include_usage`.
|
|
- `image_url` content parts reach both vision towers.
|
|
- `chat_template_kwargs` is forwarded — proven on `frank-zealot`, where
|
|
`enable_thinking: false` yields zero reasoning and `true` yields a real
|
|
thinking block on the same prompt. Drop the kwargs from a WAN row and you
|
|
silently change thinking behaviour on the WAN path.
|
|
- API-key completions create **no** OpenWebUI chat row: the chat list is
|
|
unchanged after a probe, so an off-network session leaves nothing in the
|
|
family's chat history.
|
|
|
|
Context windows, `maxTokens`, sampling and effort maps are inherited from the
|
|
engines and documented on the LAN rows and in the fleet vault
|
|
(`serverAppConfigs-docs/LLM/`). The proxy neither widens a window nor truncates:
|
|
an over-ceiling request is still a `400`.
|
|
|
|
## Off-network failure modes
|
|
|
|
- **`Connection refused` / long timeout on a LAN id.** Not a service outage —
|
|
you are not home. Switch to the WAN id.
|
|
- **`Model not found` for `frank-archon`.** Expected; see the table above.
|
|
- **Compaction wants a LAN address.** Each WAN row sets `compactionModel` to
|
|
the WAN `frank-zealot`, but `compaction.remoteEndpoint` in `agent/config.yml`
|
|
is a LAN URL. It is last in `methodOrder`, so the other methods run first and
|
|
the remote attempt just fails — leave it unless off-network compaction turns
|
|
out to need it.
|
|
- **Roles still resolve to LAN.** A full off-network session that must not
|
|
depend on hand-picked models needs `retry.fallbackChains` entries pointing
|
|
the LAN defaults at the WAN rows; that is a deliberate `config.yml` change,
|
|
not part of adding rows here.
|
|
|
|
## Verify the door
|
|
|
|
```bash
|
|
# Key + model list (LAN clients resolve 192.168.0.143 via Pi-hole; the WAN
|
|
# A record is what makes this reachable from elsewhere).
|
|
curl -s https://ai.franksdank.cloud/api/v1/models \
|
|
-H "Authorization: Bearer $(cat ~/.config/secrets/openwebui_api_key)" | jq -r '.data[].id'
|
|
|
|
# Registry loaded, WAN rows present, no validation failure (a failed load
|
|
# silently disables EVERY custom provider — see
|
|
# agent/models.yml's schema-law comment):
|
|
omp models --no-extensions | grep -E 'franksdank|validation failed'
|
|
```
|
|
|
|
A scratch-HOME run is the only real proof that a harness change reaches the
|
|
door: copy `models.yml` + `config.yml` to `$TMP/.omp/agent/`, symlink
|
|
`$TMP/.config/secrets` at the real one (a `~` in a `!cmd` value expands to
|
|
`$HOME`), rewrite the LAN baseUrls to a dead port so a success cannot be
|
|
faked, then `HOME=$TMP omp --model 'openwebui (franksdank.cloud)/…' -p …`.
|
|
Delete the scratch tree afterwards — it holds endpoint URLs.
|
|
|
|
## Pointers
|
|
|
|
- Door topology: [Caddy](/caddy/index.md), [System hosts](/system/index.md).
|
|
- Model registry law and per-provider limits: [Core config](index.md),
|
|
[OMP Subagent Limits](subagent-limits.md).
|
|
- Server-side record: `serverAppConfigs-docs/Dankclaw/OpenWebUI.md`
|
|
(that repo's vault, not this bundle).
|