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.
This commit is contained in:
1 parent
8c3dc6bfc9
commit
f3360a28f1
4 files changed
+290
No files matched your search
@@ -15,5 +15,6 @@ These files define the structural conventions that govern the agent system itsel
|
||||
* [dankvoice](dankvoice.md) — Voice output is the `dank/dankvoice` package (`pi install git:`); this page keeps fleet wiring and the cutover procedure.
|
||||
* [OMP Status Line Customization](statusline.md) — How presets, composer shapes, and segment arrays actually interact; the fleet's tuned bar. (moved from /pi/, 2026-10-19)
|
||||
* [OMP Subagent Limits & Fan-out Policy](subagent-limits.md) — Live caps: concurrency 2, no nested spawns (the `maxRecursionDepth: 0` trap), per-endpoint in-flight ceilings, isolation policy. (moved from /pi/, 2026-10-19)
|
||||
* [WAN Model Access](wan-model-access.md) — Driving the fleet's models from outside the home network through the OpenWebUI door: the command, the key file and its rotation law, what the door publishes, and the off-network failure modes.
|
||||
|
||||
**Change log:** the bundle keeps exactly one, one level up — [Bundle Change Log](../log.md). A directory here never has its own.
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
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).
|
||||
@@ -24,6 +24,24 @@ long form.
|
||||
|
||||
## 2026-10-21
|
||||
|
||||
### The WAN door into the model registry
|
||||
|
||||
`openwebui (franksdank.cloud)` joins `agent/models.yml` — the already-public
|
||||
OpenWebUI vhost as an OpenAI-compatible provider, so a workstation outside the
|
||||
house still reaches `frank-dragoon` and `frank-zealot` while every LAN row stays
|
||||
untouched. The credential is command-backed
|
||||
(`apiKey: "!cat ~/.config/secrets/openwebui_api_key"`, 600-mode file): a literal
|
||||
`sk-…` in a tracked file is a key in every agent context on every box that pulls.
|
||||
|
||||
Laws: **OpenWebUI forwards `chat_template_kwargs`**, so a proxied row copies its
|
||||
LAN twin's compat block instead of simplifying it — proved on `frank-zealot`,
|
||||
`enable_thinking` false giving zero reasoning and true a real block on one
|
||||
prompt; **the picker IS the API's model list**, `frank-archon` answers `400
|
||||
"Model not found"` because it was never published, so a WAN path for it is a
|
||||
fleet change on Dankclaw and not a harness one; and **a `!cmd` value is cached
|
||||
for the process lifetime**, so rotating a key without restarting omp leaves the
|
||||
old key working. Page: [core/wan-model-access.md](core/wan-model-access.md).
|
||||
|
||||
### the audit grows an advisory table, and corrects three of its own numbers
|
||||
|
||||
`omp-docs/reports/fleet-security-audit-2026-10-21.html` folds in the two per-host vulnerability
|
||||
|
||||
Reference in new issue
Block a user