fix(omp): frank-dragoon's effort map folded against rejections that do not exist

The entry rewrote every effort before the wire: `minimal -> low`, `high -> xhigh`,
`max -> xhigh`, under a comment claiming flash-server rejects anything outside
{xhigh, medium, low}. The engine says otherwise, in its own 400:

    reasoning_effort 'maximum' unsupported; use minimal|low|medium|high|xhigh|max|none

and the harness passes an unmapped level through verbatim
(`compat.reasoningEffortMap?.[effort] ?? effort`, read in the pinned `dist/cli.js`). So the
fold protected nothing and silently changed what the caller asked for. The levels are not
cosmetic either — thinking length on one prompt at `max_tokens 2048`: `low` 766 reasoning
tokens, `xhigh` 614, `max` 1049 with the block closed at 1024.

The map is identity. Two things came out of capturing the wire (scratch HOME + a logging
proxy forwarding :7799 to :7770) that reading the config could not have told me:

- `:off` does not switch thinking off. The default disable mode sends the lowest effort,
  so `off` left as `reasoning_effort: "minimal"` — and `minimal` still thinks, a canned
  26-token block on every request. `reasoningDisableMode: none-effort` makes the harness
  send `"none"`, which the engine honours as a real switch: `reasoning_tokens: 0`, answer
  only, no thinking block rendered. Verified after the change, not before it.
- `:max` still arrives as `xhigh`: the harness clamps the requested effort to its own
  ladder for this model before the map applies. Recorded in the comment as the engine-side
  half of the contract rather than papered over — nothing in models.yml forces it.

Also pinned `top_k: 20` (accepted; the comment claimed the wire matched the server sampler
while `top_k` was never sent, leaving the recipe one env edit away from drifting), and
restored the pre-callsign model ids `frank-medium` / `frank-large` / `frank-small` — the
2026-10-08 rename had replaced the old id inside its own explanation, leaving "renamed
from frank-dragoon" three times and destroying the record needed to resolve an old id.

Verified: full suite green (0 failures), `kb-lint` 0 violations, YAML parses, and the four
efforts `off/high/medium/max` captured on the wire as `none/high/medium/xhigh` with
`top_k: 20` on every request. Refs #62
This commit is contained in:
Frank committed 2026-10-21 01:28:37 -07:00
1 parent 97b65067b1
commit d4d6dd0c44
2 files changed
+83 -17

No files matched your search

+68 -17
View File
@@ -16,7 +16,7 @@ providers:
auth: none
models:
- id: frank-archon
# Model id renamed from frank-archon on 2026-10-08 (official
# Model id renamed from frank-medium 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
@@ -82,9 +82,11 @@ providers:
auth: none
models:
- id: frank-dragoon
# Model id renamed from frank-dragoon on 2026-10-08 (official
# Model id renamed from frank-large on 2026-10-08 (official
# unit-name scheme; the service/unit/scripts keep the frank-dragoon
# name — service names are unchanged).
# name — service names are unchanged). The pre-rename id is
# `frank-large`, recorded here because the callsign rename overwrote
# its own explanation and left "renamed from frank-dragoon".
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
@@ -111,31 +113,80 @@ providers:
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.
# checkpoint (HALOGEN_VISION_TOWER; priced at 0.8 GiB in the boot
# ledger, and re-probed 2026-10-21 — a 512 px solid-red PNG comes back
# "Red" at 282 prompt tokens). 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.
# flash-server validates reasoning_effort against its own list and
# 400s anything else, naming it: "reasoning_effort 'maximum'
# unsupported; use minimal|low|medium|high|xhigh|max|none" (measured
# 2026-10-21). Every omp level is already spelled by the engine, so
# the map is identity — it exists to record that, not to fold. An
# earlier version rewrote high->xhigh and minimal->low to stop
# rejections that do not exist, and the rewrite asked the engine for
# a different thinking budget than the caller named. Levels really do
# differ here: thinking length on one prompt at max_tokens 2048
# measured low 766 tokens, xhigh 614, max 1049 with the block closed
# at 1024 (reasoning_closed_at). The harness passes an unmapped level
# through verbatim (`compat.reasoningEffortMap?.[effort] ?? effort`),
# so removing an entry is a pass-through, never a 400.
#
# `max` still does not reach the engine, and the map is not why: the
# harness clamps the requested effort to the model's own effort ladder
# before the map applies, and its ladder for this model tops out at
# xhigh — captured on the wire 2026-10-21, `:max` left as
# `reasoning_effort: "xhigh"` with this identity map in place. The
# `max` row below is the engine-side half of the contract; it takes
# effect the moment the harness ladder gains `max`, and nothing here
# can force it sooner.
#
# Thinking OFF needs an explicit mode. The default disable mode sends
# the LOWEST effort rather than a switch — `:off` left the wire as
# `reasoning_effort: "minimal"` (captured), and minimal still thinks:
# a canned 26-token block on every "off" request. `none-effort` makes
# the harness send `reasoning_effort: "none"`, which this engine
# honours as a real switch: `reasoning_tokens: 0`, answer only, no
# thinking block in the rendered output (both verified 2026-10-21).
#
# Re-measure before changing any of it. The allow-list, in the
# engine's own words:
# curl -s :7770/v1/chat/completions -H 'content-type: application/json' \
# -d '{"model":"frank-dragoon","messages":[{"role":"user","content":"x"}],
# "reasoning_effort":"maximum"}'
# -> 400 "reasoning_effort 'maximum' unsupported; use
# minimal|low|medium|high|xhigh|max|none"
# and read usage.completion_tokens_details.reasoning_tokens at
# max_tokens 2048 to see the levels separate. The wire itself is
# settled by pointing a scratch HOME at a logging proxy on :7799 that
# forwards to :7770 and running `omp --model …:off` — config claims
# are worth exactly what their last capture says.
reasoningDisableMode: none-effort
reasoningEffortMap:
minimal: low
minimal: minimal
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.
high: high
xhigh: xhigh
max: max
# 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), but an omitted parameter is
# the engine's choice, not a pinned one: top_k is sent explicitly so
# the recipe survives someone editing HALOGEN_TOP_K. The engine
# accepts top_k, min_p, presence_penalty and repetition_penalty
# (measured 2026-10-21, all 200) — only the values this endpoint is
# meant to run are pinned here.
extraBody:
temperature: 1.0
top_p: 0.95
top_k: 20
"llamacpp (dankclaw-3090)":
baseUrl: http://192.168.0.143:7771/v1
@@ -143,7 +194,7 @@ providers:
auth: none
models:
- id: frank-zealot
# Model id renamed from frank-zealot on 2026-10-08 (official
# Model id renamed from frank-small 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)"