Expand description
Stage 5b: suppress a resolved reasoning_effort when the model’s observed
template never reads it — and say so.
ADR 0007 decision 3: the resolved reasoning_effort is suppressed when —
and only when — the observed caps positively say the template does not read
the variable. This module is that sentence.
§Why it must be reported rather than performed quietly
A template that does not read the variable ignores it in perfect silence:
HTTP 200, prompt byte-identical, no warning, no status change (ADR 0007
finding 7c, confirmed live). Neither reasoning control is echoed anywhere —
not in /slots.params, not in /props (finding 7a) — so no readback will
ever notice that a level went nowhere. If gglib deletes the key without a
record, the fact is unrecoverable from every surface at once.
So the suppression writes itself down twice.
ParamSource::SuppressedByTemplate replaces the rung in the decision’s
provenance — no surface can print reasoning_effort=profile for a value
that was never sent — and SuppressedEffort carries the level and the
rung that supplied it, so a surface can say which value was dropped and
who asked for it.
Both records are read downstream: gglib model explain renders the
provenance, and the proxy hands SuppressedEffort to its sampling audit so
the dashboard can name the level and the rung.
This module’s own debug! stays, and is not redundant with the pipeline’s
"sampling resolved" line. That line is rendered by
sampling_log after this stage precisely so it
describes what was sent — which means that on a suppression it reads
reasoning_effort=None … reasoning_effort=suppressed-by-template, and the
level and rung this stage threw away appear nowhere in it.
§Unknown never gates
The predicate is deliberately conservative in the same shape
strip_unsupported_tools uses
(tools.rs:29-40): it acts only on a
catalog_resolved context, and
only on a positive Support::No. A passthrough model, a model nobody has
launched yet, a /props read that failed, and a caps object that did not
carry the field all mean nobody knows, and all keep their effort.
The precedent is a shape, not an equivalence, and the difference is worth stating: tool stripping reads gglib’s own catalog row — a fact this system recorded about the model — while this reads llama-server’s self-report, a fact another process stated about itself. ADR 0007 names that posture (a runtime self-report used as a policy input) precisely because it is not the same thing as a stored capability, and it carries the extra rule that a report which failed to arrive must never be read as one that arrived negative.
§Scope: the top-level key, and only the effort
This gate governs the top-level reasoning_effort key alone. A client’s
chat_template_kwargs remains a verbatim passthrough — gglib neither
reads nor edits it, and a caller who puts an effort level in there is
addressing the template directly, over gglib’s head, which is a different
(and unmodelled) act from setting the field the ladder resolves.
reasoning_budget_tokens
is never suppressed here. It is not a template variable at all: it is
enforced by llama.cpp’s own sampler-side budget
(common/reasoning-budget.{h,cpp}) and range-validated upstream, so a
template that ignores reasoning_effort still honours the budget. Gating it
on a caps bit that describes a template would be a category error, and
the_budget_survives_a_model_that_cannot_honour_effort pins it.
Structs§
- Suppressed
Effort - A resolved effort level this stage threw away, and where it came from.
Functions§
- describe_
rung 🔒 - Name the rung a suppressed level came from, for the debug line.
- suppress_
stored_ effort - Stage 5b’s rule, applied to a resolution with no request in hand.
- suppress_
unsupported_ effort - Remove a resolved
reasoning_effortthe observed template cannot read, and return what was removed.