Skip to content

fix: fail loud instead of silently swapping models on an unresolvable pin - #137

Merged
QuintinShaw merged 2 commits into
mainfrom
fix/model-pin-fail-loud
Jul 30, 2026
Merged

fix: fail loud instead of silently swapping models on an unresolvable pin#137
QuintinShaw merged 2 commits into
mainfrom
fix/model-pin-fail-loud

Conversation

@QuintinShaw

Copy link
Copy Markdown
Owner

Summary

  • An explicit agent({ model }) pin, agent-type model, explicit tier, or phase/metadata-routed model that can't be resolved now throws MODEL_NOT_FOUND (recoverable: false) before the subagent session is created, instead of silently running against the session default. Tier failures name their source (tier "big" from model-tiers.json resolves to "…", which is not available). Scripts can try/catch to degrade on their own. Fixes agent() silently returns null on a bare model pin despite the subagent generating successfully #131.
  • An untagged agent routed through the implicit default medium tier still degrades gracefully to the session default when that tier is unavailable — but the fallback is now visible as a once-per-run entry in the run log instead of a console warning. onModelFallback narrows to exactly this case (new object signature).
  • The resolver gains pi CLI's auth-aware preference: a bare vendor/model id whose vendor segment collides with an unauthenticated provider name resolves to the authenticated aggregator's literal id, matching resolveCliModel — cross-verified against pi's real resolver with property tests so future drift fails fast.
  • Skill references and the capability contract updated from the old "falls back to the session default" wording to the new asymmetric semantics.

Test plan

  • npm test — 1160 passing, release gate 0 warnings
  • New coverage: explicit-pin throw, tier-sourced error message, implicit-tier degradation with single warning, auth-preference cross-check vs pi's resolveCliModel

… pin

agent()'s model resolution had two stacked gaps: an unresolvable model spec
(e.g. a bare aggregator-style id like "moonshotai/kimi-k3" whose vendor
segment collides with a real, separately-registered provider name) silently
fell back to the session default with only a console.warn, and the resolver
itself lacked pi CLI's auth-aware preference for an authenticated raw-id
match over an unauthenticated same-named provider.

Now an unresolvable model/tier spec throws a clear MODEL_NOT_FOUND error
before the subagent session is even created, instead of quietly running
against a different (possibly unauthenticated) model. resolveModelSpecWithThinking
also gained the missing auth-preference check, cross-verified against
pi-coding-agent's own resolveCliModel via a fuzzed property test so future
drift between the two is caught immediately.

Closes #131.
An explicit model or tier pin that resolves to an unavailable model now
throws MODEL_NOT_FOUND naming its source (e.g. which tier, and what it
resolved to), matching the existing explicit-model behavior. An untagged
agent routed only through the implicit default "medium" tier never asked
for that specific model, so it still degrades to the session default when
unavailable — but the degrade now fires a run-visible log event instead of
being silent, and only once per run.

Also strengthens the model-spec parity property test so it actually
exercises the auth-preference branch against pi-coding-agent's real
resolveCliModel (the previous fuzz domain never triggered it), and updates
the workflow-authoring skill references and generated capability facts that
still described the old blanket fallback.
@QuintinShaw
QuintinShaw merged commit 42498cf into main Jul 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

agent() silently returns null on a bare model pin despite the subagent generating successfully

1 participant