fix(#62): frank-dragoon's reasoning_effort contract, measured instead of assumed #63

Merged
dank merged 1 commits from fix/62-dragoon-effort-contract into main 2026-10-21 01:44:23 -07:00
2 changed files with 83 additions and 17 deletions
+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)"
+15
View File
@@ -20,6 +20,21 @@ This page is the bundle's **only** work log: one file, at the root. Never start
second one in a subdirectory or under another name — `scripts/kb-lint.sh` reports any
`log.md` that is not this one.
## 2026-10-21 — an effort map folding against rejections that never existed
`agent/models.yml` rewrote `minimal→low`, `high→xhigh` and `max→xhigh` for
frank-dragoon to dodge 400s the engine does not send: flash-server's own error names
its allow-list (`minimal|low|medium|high|xhigh|max|none`) and the harness passes an
unmapped level through verbatim, so the fold only asked for a different thinking
budget than the caller named. The map is identity now, and
`reasoningDisableMode: none-effort` was added because the default disable mode does
not disable — `:off` left as `reasoning_effort: "minimal"` and paid for a canned
thinking block, where `none` returns `reasoning_tokens: 0`. Both settled by capturing
the wire through a logging proxy rather than reading the config: a claim about what
the harness sends is worth exactly its last capture. The same pass restored the three
pre-callsign model ids (`frank-medium` / `frank-large` / `frank-small`) that the
2026-10-08 rename had overwritten inside its own explanatory comments. Refs #62
## 2026-10-21 — a documented mode the panel never had
The home ultrawide had been running at 60 Hz for weeks while `desk-1-home.kdl`