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.
6.7 KiB
type, title, description, tags, verified, timestamp
| type | title | description | tags | verified | timestamp | |||||
|---|---|---|---|---|---|---|---|---|---|---|
| reference | WAN Model Access (ai.franksdank.cloud) | 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. |
|
2026-10-21 | 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:
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 blockssk-…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
!cmdresult 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_contentandtool_callsdeltas,finish_reason: tool_calls, and a finalusageunderstream_options.include_usage. image_urlcontent parts reach both vision towers.chat_template_kwargsis forwarded — proven onfrank-zealot, whereenable_thinking: falseyields zero reasoning andtrueyields 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 foundforfrank-archon. Expected; see the table above.- Compaction wants a LAN address. Each WAN row sets
compactionModelto the WANfrank-zealot, butcompaction.remoteEndpointinagent/config.ymlis a LAN URL. It is last inmethodOrder, 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.fallbackChainsentries pointing the LAN defaults at the WAN rows; that is a deliberateconfig.ymlchange, not part of adding rows here.
Verify the door
# 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, System hosts.
- Model registry law and per-provider limits: Core config, OMP Subagent Limits.
- Server-side record:
serverAppConfigs-docs/Dankclaw/OpenWebUI.md(that repo's vault, not this bundle).