Files
command-center/omp-docs/core/wan-model-access.md
T
Frank f3360a28f1 feat(models): reach the fleet from off-network through the OpenWebUI door
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.
2026-10-21 16:48:29 -07:00

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.
omp
models-yml
openwebui
remote-access
fleet
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 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

# 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