A sixteen-row matrix over 127 real calls: disable and enable on
MiniMax-M3 in both streaming and non-streaming mode, extra_body winning
over the profile slot, qwen and deepseek still disabling correctly, a
drift sentinel that re-derives every registered capability from live
behaviour, and the assembly guard refusing the models that cannot
comply.
Two judgement criteria had to be corrected by the data they were meant
to judge. Output length cannot separate the two regimes at all -- the
disabled runs reach 46 tokens when the model narrates its working in
the visible answer, and the enabled runs drop to 13 when medium effort
barely thinks. reasoning_tokens separates them cleanly in both
directions, which is precisely what issue #6 was collected for. A
second anchor compares prompt_tokens between the two regimes: the
vendor injects a reasoning instruction when thinking is on, so the
input side grows, and comparing the two runs relatively avoids
hardcoding any vendor number.
Provider names are mapped explicitly rather than guessed from the model
string; guessing had silently skipped the qwen row behind a "source
unavailable" reason that was not true.
Ten tasks in a fixed order: land reasoning_tokens first so it can serve
as the acceptance instrument for the thinking-switch fix, then reshape
the provider profile, add the model-level capability table, wire the
assembly guard, fold enable_thinking into the cache fingerprint, and
verify the whole thing against the live API.
Incorporates a read-only Codex review: resolve_thinking now takes the
model name so its errors can name it, and the warning assertion uses a
loguru sink because caplog cannot see loguru output.
Findings: live-API measurements across MiniMax M3/M2.7/M2.5, qwen and
deepseek, plus a survey of how nine unified gateways model per-model
parameter divergence. Key facts: reasoning_effort is MiniMax's real
switch, M2.x reasoning is mandatory and cannot be disabled, and the
relay's local token-count fallback silently drops reasoning_tokens.
Design: keep the parameter shape at provider level, push capability
down to model level, split "unknown" / "unsupported" / "no opinion"
into three distinct values, and fail at assembly time when a model
cannot honour enable_thinking=False.
The test passed overlay and sampling as separate objects while production
aliases them, so an in-place mutation slipped through it. Also syncs the
telemetry schema page and adds the missing postgres round-trip assertion.
The ordering is the load-bearing part. All three changes interlock and
every wrong interleaving fails silently: flipping the usage fallback to
(0, 0) before the settlement points read the derived value refunds the
whole pre-deduction on success, and flipping the embedding transport
before _merge goes three-state mislabels unavailable batches as
measured. So the plan adds the capability first, moves all five call
sites onto it while it is still equivalent, only then lets the third
state take effect, and unbinds the constraint last.
Registers both wiki entries and links the plan to its design.
Records the approved option, the four rejected alternatives with their
reasons, the intentionally dropped CHS migration item, and the two
defects the independent review caught. Links the entry to m1-core-design
as a refinement, since that milestone is where est_tokens froze with
both jobs attached.
The independent verifier found 15 more checks still living only in from_env:
six enum domains, seven conditional-required pairs and two scalar ranges.
More severe than round one because client.py has five asserts that claim
config already validated the redis_url and telemetry paths, which is false on
the from_settings path.
Also records a normalisation gap the verifier missed: _load_pg_dsn strips the
SQLAlchemy +asyncpg suffix, so a hand-built DSN reaches asyncpg unstripped.
Guards for the three cross-field invariants (source timeout vs lease TTL,
stall window vs max TTFT, probe TTL vs slowest timeout) only ran inside
from_env, so the from_settings path could build a GatewaySettings that
violates the class's own documented invariants. Design moves all of them
plus a non-empty sources check into __post_init__ as _validate_* methods,
matching the existing frozen dataclasses in types.py.
Covers two defects left by PR#1: probe_ttl_s was never moved, and an empty
sources tuple leaked a bare 'max() arg is an empty sequence'.
Records the human decision to test against the real gateway with real
data instead of building a mock gateway, six execution scenarios with
fault-source mixing, hard invariants scored from our own telemetry,
and the corpus inventory pulled from the lab server.