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:
Frank committed 2026-10-21 16:48:29 -07:00
1 parent 8c3dc6bfc9
commit f3360a28f1
4 files changed
+290

No files matched your search

+132
View File
@@ -306,3 +306,135 @@ providers:
name: "frank-tts (dankclaw) — cloned voices"
api: openai-speech
input: [text]
# ─── WAN door ───────────────────────────────────────────────────────────
# The only block in this file that answers OFF the home network. Every
# provider above is a LAN IP, and `modelRoles` (config.yml) still points at
# those, so away from home you must NAME one of the rows below:
# omp --model 'openwebui (franksdank.cloud)/frank-dragoon:medium'
# The door is the OpenWebUI vhost `ai.franksdank.cloud` — Caddy → OWU on
# Dankclaw:3005, Anubis-exempt because OWU enforces its own auth, public A
# record present (LAN clients resolve 192.168.0.143 via Pi-hole, measured
# 2026-10-21). It exposes an OpenAI-compatible surface at /api/v1 and
# proxies to the SAME engines as the LAN rows — no second inference stack,
# so every engine-side law below still holds through it.
# CREDENTIAL LAW: the key is not in this file. `apiKey` accepts a
# command-backed value (`!cmd`, resolved by src/config/resolve-config-value.ts:
# stdout trimmed, cached for the process lifetime; anything else is tried as
# an env-var name, then taken literally), so the secret lives in
# ~/.config/secrets/openwebui_api_key, mode 600, like every other credential
# on these boxes. A literal `sk-…` here would trip the pre-commit shape gate
# and then live in every agent context on every box that pulls this repo.
# Rotate: rewrite the secret file, then RESTART omp — the `!cmd` result is
# cached per process, so a live session keeps serving the old key.
# WHAT THE DOOR SHOWS (probed 2026-10-21 with this key): /api/v1/models
# answers frank-dragoon and frank-zealot only. frank-archon is NOT published
# in OpenWebUI's picker and the API answers 400 "Model not found" for it, so
# the fleet's biggest endpoint has no WAN path until it is published
# server-side on Dankclaw — a fleet change, not a harness change.
# CONTRACT PROBED THROUGH THE DOOR (2026-10-21): streaming SSE carries
# `reasoning_content` deltas 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 (a 64 px red PNG comes
# back "Red" from each); and `chat_template_kwargs` IS forwarded — proven on
# frank-zealot, where enable_thinking false gives reasoning 0 chars and true
# gives a real thinking block on the same prompt. Every compat block below is
# therefore a copy of its LAN twin, never a simplification: dropping the
# kwargs here would quietly change thinking behaviour on the WAN path.
# Chat completions made with an API key create NO OpenWebUI chat row (the
# chat list is unchanged after probing), so an off-network session leaves no
# trail in the family's chat history.
# NOT BEHIND THIS DOOR: speech. OWU does route /api/v1/audio/speech and it
# does reach frank-tts:7772, but OWU substitutes its own voice enum first —
# a cloned pack name comes back 400 from the upstream. `modelRoles.speech`
# stays on `frank-tts (dankclaw)`; do not repoint it here.
"openwebui (franksdank.cloud)":
baseUrl: https://ai.franksdank.cloud/api/v1
api: openai-completions
# Quoted because an unquoted leading `!` is a YAML tag, not a command.
apiKey: "!cat ~/.config/secrets/openwebui_api_key"
models:
- id: frank-dragoon
# Same halogen flash-server as `llamacpp (dankclaw)` above, one hop
# further out; the picker row and the engine share this id, so there
# is no alias to keep in sync.
name: "frank-dragoon (WAN)"
# Off-network the compaction hub must be reachable too: the LAN
# frank-zealot row is a dead address from anywhere but the house.
# Same engine, same slot arithmetic, so the keepRecent/threshold
# math in the LAN rows transfers untouched.
compactionModel: openwebui (franksdank.cloud)/frank-zealot
# Inherited from the engine, not invented: 200,000 under the native
# 262,144 ceiling (the 400-zone law — never sit at the ceiling). The
# proxy neither widens the window nor truncates: an over-ceiling
# request is still a 400.
contextWindow: 200000
# Same clamp arithmetic as the LAN row (see frank_dragoon_unit.bats);
# HALOGEN_MAX_TOKENS_CAP on the engine is unchanged.
maxTokens: 45056
reasoning: true
input: [text, image]
compat:
# Verified through the door: reasoning arrives as
# `reasoning_content` in the deltas, not folded into content.
reasoningContentField: "reasoning_content"
# `:off` must be a real switch, and it is: this engine honours
# reasoning_effort "none"; the harness default would only send the
# lowest effort, which still thinks.
reasoningDisableMode: none-effort
# Identity map, same reason as the LAN row — flash-server lists
# minimal|low|medium|high|xhigh|max|none and 400s anything else.
reasoningEffortMap:
minimal: minimal
low: low
medium: medium
high: high
xhigh: xhigh
max: max
# Pinned so a server-side HALOGEN_TOP_K edit cannot change this
# session's recipe; the engine accepts all three keys.
extraBody:
temperature: 1.0
top_p: 0.95
top_k: 20
- id: frank-zealot
# The 3090 endpoint, same llama.cpp engine behind the proxy. Stays
# the widest slot in the file, so it is the WAN compaction target.
name: "frank-zealot (WAN)"
compactionModel: openwebui (franksdank.cloud)/frank-zealot
# PER-SLOT window: llama.cpp divides --ctx-size across slots and
# answers a longer prompt with a 400, never a truncation. Follows
# every reslice on the engine (3 × 200,704 today).
contextWindow: 200704
# Prompt + thinking budget inside the slot; compaction fires at 85 %
# of the window on this row set (see the LAN twin for the full
# worst-case arithmetic).
maxTokens: 16384
reasoning: true
input: [text, image]
compat:
reasoningContentField: "reasoning_content"
# LOAD-BEARING, exactly as on the LAN row: the Bonsai-2 template
# accepts ONLY xhigh|medium|low and raises on anything else, so an
# unmapped harness level is a hard failure on the role's first
# request. Probed 2026-10-21 that the mapped value, not the
# harness-level, is what reaches the template through this proxy.
reasoningEffortMap:
minimal: low
low: low
medium: medium
high: xhigh
max: xhigh
# The payload's own sampling recipe plus the thinking switches.
# chat_template_kwargs survives the proxy (verified), so
# preserve_thinking keeps prior assistants' thinking blocks in
# history for multi-turn tool traffic — same contract as at home.
extraBody:
temperature: 1.0
top_p: 0.95
top_k: 20
min_p: 0.0
presence_penalty: 0.0
chat_template_kwargs:
enable_thinking: true
preserve_thinking: true