# OMP agent model registry — single source of truth (OMP is the primary # harness; there is no second copy to keep in sync). # Conversion notes from the pi-era import: # - apiKey "EMPTY" -> auth: none (keyless local endpoint) # - thinkingLevelMap {max: xhigh} -> compat.reasoningEffortMap providers: # Provider NAMES are labels, and every reference to them lives in config.yml # (modelRoles, agentModelOverrides) or in another model's compactionModel — # rename one and a missed reference fails at call time. So each label is # migrated with its model in the same edit. One provider per HOST:PORT, so # Dankclaw's second endpoint (:7771) is a separate block rather than a second # model inside the :7770 one. "llamacpp (dankserver)": baseUrl: http://192.168.0.235:7770/v1 api: openai-completions auth: none models: - id: frank-archon # Model id renamed from frank-archon on 2026-10-08 (official # unit-name scheme; the systemd service, scripts and runbooks keep # the frank-archon name — service names are unchanged). # Server-side compaction routes to compaction.remoteEndpoint in # config.yml (192.168.0.143:7771 — frank-zealot's 3090); the # compactionModel candidate keeps the wire model id valid there. compactionModel: llamacpp (dankclaw-3090)/frank-zealot name: "frank-archon" # Dankserver: BOTH Radeon AI PRO R9700s (gfx1201, TP=2, wht6 wire) on # the digest-pinned radiance image, plain `podman run` under a root # unit. Payload, shape, eras, laws, rollback: # serverAppConfigs-docs/LLM/frank-archon.md — and # scripts/frank-archon.env is the authority for every number there. # contextWindow MUST quote the engine's --max-model-len: radiance # refuses a longer prompt and never truncates. contextWindow: 200000 # maxTokens must survive the harness compaction gap AND the chars/4 # token-estimate under-run on code-heavy prompts. The clamp is # max_tokens = min(maxTokens, contextWindow − estimate − 4096); the # estimate under-runs by ~3 % on code. Compaction fires at 85 % of # the window = 170,000 est; worst-case prompt + budget ≈ # 170,000 × 1.03 + 16,384 ≈ 191,500 ≤ 200,000 — the engine # refuses a longer request, never truncates. maxTokens: 16384 reasoning: true # Text + image: the official FP8 checkpoint carries the vision # tower (vision_config depth 27; the first blocks stay bf16 in # modules_to_not_convert) and the plugin declares it # (arch/common/rad_block_vit.h). Video preprocessing configs ship # too; images are the contract that matters here. input: [text, image] compat: # radiance's --reasoning-format defaults to auto: thinking # comes back in `reasoning_content` (config.cpp). No parser # flags exist or are needed. reasoningContentField: "reasoning_content" # The froggeric template does not gate reasoning_effort the way # Qwen's shipped template did, and radiance special-cases only # none/minimal (core/server/oai.cpp:609). The map stays as the # zero-cost safety net, same as frank-zealot's. reasoningEffortMap: minimal: low low: low medium: medium high: xhigh max: xhigh # Qwen recommended sampling. extraBody is merged last and wins. # Every key is inside radiance's known-parameter list # (core/server/oai.cpp:194-205); unknown keys are refused 400. extraBody: temperature: 1.0 top_p: 0.95 top_k: 20 min_p: 0.0 presence_penalty: 0.0 repetition_penalty: 1.0 chat_template_kwargs: enable_thinking: true preserve_thinking: true "llamacpp (dankclaw)": baseUrl: http://192.168.0.143:7770/v1 api: openai-completions auth: none models: - id: frank-dragoon # Model id renamed from frank-dragoon on 2026-10-08 (official # unit-name scheme; the service/unit/scripts keep the frank-dragoon # name — service names are unchanged). name: "frank-dragoon (dankclaw-gpu)" # Server-side compaction runs on the compaction.remoteEndpoint in # config.yml (192.168.0.143:7771 — frank-zealot's 3090); this steers the # compaction model candidate so the wire model id stays valid # there even from a frank-dragoon session. compactionModel: llamacpp (dankclaw-3090)/frank-zealot # halogen flash-server on the Strix Halo iGPU. Payload, shape, eras, # laws, rollback: serverAppConfigs-docs/LLM/frank-dragoon.md — and # scripts/frank-dragoon.env is the authority for every number there. # contextWindow sits deliberately BELOW the engine ceiling # (HALOGEN_CTX): the clamp below leaves the gap that absorbs the # harness's chars/4 under-count, so a window equal to the ceiling # would make every long request a guaranteed 400. contextWindow: 155648 # The clamp is max_tokens = min(maxTokens, contextWindow − est − # 4096); the chars/4 est UNDER-counts the rendered prompt by ~3 % # on code/agent traffic (measured 2026-10-07: est 152,938 -> # server 157,388). Worst-case request = window − 4096 + 3 % est # error ≈ 156.2k, inside the engine ceiling with ~44k of margin # (robust to a ~25 % under-count). Compaction fires at 85 % of the # window and rides on frank-zealot: input ≈ threshold − # keepRecent(20k), comfortably inside its per-slot window. # HALOGEN_MAX_TOKENS_CAP in frank-dragoon.env must stay >= this. maxTokens: 45056 reasoning: true # VISION ON as of 2026-10-06: the v2 vision tower loads beside the # checkpoint (HALOGEN_VISION_TOWER; red-PNG probe verified on the # card). image_url content parts only — the legacy top-level # images field is the frank-zealot trap, do not repeat it here. input: [text, image] compat: # flash-server emits thinking as `reasoning_content` — same # field as every engine this endpoint has run, no harness # change needed. reasoningContentField: "reasoning_content" # Same Qwen3.8 template family (it ships inside the tokenizer # bundle): any reasoning_effort outside {xhigh, medium, low} is # rejected, not clamped — the fold stays mandatory. reasoningEffortMap: minimal: low low: low medium: medium high: xhigh max: xhigh # The card's thinking recipe, on the wire. The flash-server # engine HAS a server-side sampler (HALOGEN_TEMPERATURE/TOP_P/ # TOP_K = 1.0 / 0.95 / 20 in frank-dragoon.env) — these match it, # so per-request values are consistent, not load-bearing. extraBody: temperature: 1.0 top_p: 0.95 "llamacpp (dankclaw-3090)": baseUrl: http://192.168.0.143:7771/v1 api: openai-completions auth: none models: - id: frank-zealot # Model id renamed from frank-zealot on 2026-10-08 (official # unit-name scheme; the service/unit/scripts keep the # frank-zealot name — service names are unchanged). name: "frank-zealot (dankclaw-3090)" # Server-side compaction routes to compaction.remoteEndpoint in # config.yml (192.168.0.143:7771 — this endpoint); the # compactionModel candidate keeps the wire model id valid there. compactionModel: llamacpp (dankclaw-3090)/frank-zealot # Dankclaw's RTX 3090 llama.cpp endpoint (PrismML fork build — the only # loader for this payload's custom tensor type). Shape, payload, eras, # laws, rollback: serverAppConfigs-docs/LLM/frank-zealot.md, and # scripts/frank-zealot.env is the authority for every number there. # contextWindow is the PER-SLOT window: llama.cpp divides --ctx-size # across slots and answers a longer prompt with a 400, never a # truncation, so this value must follow every reslice (live now: # 3 x 200,704 over f16 K + q8_0 V with the MTP drafter armed, ba69ea5). contextWindow: 200704 # Prompt + budget must fit inside the slot window; thinking counts # against the budget. Compaction fires at 85 % of the window = # 170,598 est; worst-case prompt + budget ≈ 170,598 × 1.03 + # 16,384 ≈ 192,100 ≤ 200,704. This endpoint stays the fleet's # compaction hub: still the widest window in this file. maxTokens: 16384 reasoning: true input: [text, image] compat: # llama.cpp --reasoning-format deepseek emits thinking as # `reasoning_content` — the same field the MiMo/Ornith eras used; # harness reads unchanged. reasoningContentField: "reasoning_content" # Bonsai-2's template DOES read reasoning_effort (the Qwen3.5-9B # template did not — the map used to be inert here): it accepts ONLY # xhigh (default), medium, low and calls raise_exception on anything # else, so an unmapped harness level is a hard HTTP 500 on the role's # first request, naming Jinja and not the model. The fold below is # therefore load-bearing, identical to the frank-archon/dragoon law. # llama.cpp passes chat_template_kwargs through to Jinja. reasoningEffortMap: minimal: low low: low medium: medium high: xhigh max: xhigh # The payload's OWN sampling metadata (general.sampling.* in the # GGUF): temperature 1.0 / top_p 0.95 / top_k 20 — thinking-mode # recipe, the same trio the other Qwen3.8-family endpoints carry. # (The old 0.6 was the Qwen3.5-9B publisher's card, not this file.) # extraBody is merged last and wins over the harness default. 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 # The template DOES read preserve_thinking: when false it drops # prior assistants' blocks from history. Keep them — # multi-turn tool traffic on this endpoint is the majority. preserve_thinking: true # Speech endpoint — the fleet's TTS, NOT a chat provider. `api: # openai-speech` is a runner API (catalog kind `tts`), so this row never # enters the chat picker, the Ctrl+P cycle, or any chat role: it is # reachable only through the `speech` model role and the tts device. # frank-tts (host port 7772) is the OpenAI /v1/audio/speech façade that # resolves a voice BY NAME; its engine (frank-tts-engine, container-internal # 8880, never published) is arm A qwentts.cpp 5916bf7 — measured 0.55-0.67 # RTF warm and 86-451 ms to the first audio byte on Dankclaw's CPU, with # mainline llama-tts (TTS_BACKEND=llama-tts-cli) kept as the rollback. # Transport contract, read out of the harness itself: the request body is # {model, input, response_format, voice?, speed?}; `voice` is forwarded ONLY # when explicitly set, and sampleRate/bitRate are REJECTED outright (the # transport throws "openai-speech does not support sampleRate"), so the # codec comes from the output_path suffix and the only tunable that matters # is the voice name — and it comes from ONE place: the tool's `voice_id` # argument. No setting feeds it (`speech.voice` is the speak-aloud Kokoro enum, # `tts.localVoice` is the local backend's), so a call that omits voice_id arrives # at 7772 with no `voice` key and the endpoint answers 400 "unknown voice None; # available voices: narrator1, narrator2, narrator3". Say the voice in the call. # WIRED 2026-10-18: `config.yml` `modelRoles.speech` is this row, flipped only # after the service was deployed and answered real synthesis (two packs, # different bytes, 24 kHz PCM16, RTF 0.57). Why the gate existed: `auth: none` # makes this provider read as AVAILABLE even while the container is down, and an # explicit speech role has NO fallback candidate, so a dead 7772 fails the device # loudly instead of quietly narrating in Kokoro. Reverting is ONE key: # `modelRoles.speech: local/kokoro`. `speech.voice` stays a Kokoro name either # way — it was never this endpoint's voice. # SCHEMA LAW, learned the hard way (2026-10-18): provider-level `api` must be # a CHAT transport — the models.yml schema accepts only the chat/judge APIs # there, and naming a runner API (`openai-speech`) at provider level fails the # file's validation, which DISABLES EVERY custom provider: all three fleet # endpoints vanish from the registry, not just this one (`omp models` prints # "models.yml validation failed — custom providers disabled" and lists only # local/kokoro). The runner api belongs on the MODEL entry only, which is what # the block below does — `auth: none` covers the credential requirement, and # `contextWindow`/`maxTokens` stay unset because the check only fires when they # are provided (the bundled Kokoro row is null on both). `omp models --kind tts` # still prints 128K/16K for this row — the registry fills display defaults — and # the speech transport never reads them: its whole request body is the five # fields named above. Verified 2026-10-18 with `omp models --kind tts` (row # present) and `omp models` (frank-tts absent from the chat roster, all three # fleet providers still listed). "frank-tts (dankclaw)": baseUrl: http://192.168.0.143:7772/v1 auth: none models: - id: frank-tts name: "frank-tts (dankclaw) — cloned voices" api: openai-speech input: [text]