fix(cli): re-resolve reasoning after every startup model move, not just the auth fallback

_resolve_cli_reasoning's invariant is that every path moving self.model re-resolves
reasoning_config before the agent build. The previous commit covered only the auth
fallback swap; the custom-entry runtime.model swap, the provider-default fill and the
provider normalization in _ensure_runtime_credentials, plus the first-run picker
re-sync in _offer_first_run_setup, still left the launch model's effort in place.

Hoist a single re-resolve to the end of _ensure_runtime_credentials (guarded on the
model actually changing since entry and on no explicit --reasoning), which also covers
the fallback path since that is its only caller; add the same guarded call after the
first-run picker re-sync, which runs before _ensure_runtime_credentials sees the move.
This commit is contained in:
teknium1
2026-09-18 03:43:19 -07:00
committed by Teknium
parent 8e1b4216b5
commit 4a15e049fd
3 changed files with 57 additions and 8 deletions

View File

@@ -182,6 +182,7 @@ class CLIAgentSetupMixin:
from hermes_cli.runtime_provider import resolve_runtime_provider, format_runtime_provider_error
_primary_exc = None
runtime = None
_model_at_entry = self.model
try:
# target_model: the ladder's model-keyed rungs (Zen/Go api_mode, Copilot/Nous
# api_mode) must see the model this CLI will actually send, not config's `default`,
@@ -271,6 +272,16 @@ class CLIAgentSetupMixin:
# Fixes #651.
model_changed = self._normalize_model_for_provider(resolved_provider)
# Startup resolved reasoning_config for the launch model; whichever path above moved
# self.model (auth fallback, custom-entry model, provider default, normalization) leaves a
# per-model contract the lazily built agent would otherwise miss (an always-thinking model
# 400s on the primary's effort). Same chokepoint as /model, /new and --resume; an explicit
# --reasoning is the user's intent for this run and outranks the new model's config.
if self.model != _model_at_entry and getattr(self, "_explicit_reasoning_config", None) is None:
from hermes_cli.cli_model_switch_mixin import _resolve_cli_reasoning
_resolve_cli_reasoning(self)
logger.info("Model moved to %s: reasoning_config resolved: %s", self.model, self.reasoning_config)
# AIAgent/OpenAI client holds auth at init, so rebuild on key/routing/model change.
if (credentials_changed or routing_changed or model_changed) and self.agent is not None:
self.agent = None
@@ -326,14 +337,7 @@ class CLIAgentSetupMixin:
platform="cli")
self.requested_provider = _fb_provider
self.model = _fb_model
# Startup resolved reasoning_config for the launch model; the fallback model has its
# own per-model contract (an always-thinking model 400s on the primary's effort).
# Same chokepoint as /model, /new and --resume; an explicit --reasoning is the
# user's intent for this run and outranks the fallback model's config.
if getattr(self, "_explicit_reasoning_config", None) is None:
from hermes_cli.cli_model_switch_mixin import _resolve_cli_reasoning
_resolve_cli_reasoning(self)
logger.info("Fallback %s: reasoning_config resolved: %s", self.model, self.reasoning_config)
# reasoning_config follows the swap in _ensure_runtime_credentials (the only caller).
return runtime
except Exception:
continue
@@ -400,6 +404,11 @@ class CLIAgentSetupMixin:
self.requested_provider = (_model_cfg.get("provider") or "").strip() or self.requested_provider
_new_model = (_model_cfg.get("default") or _model_cfg.get("model") or "").strip()
self.model = _new_model or self.model
# The picker's model has its own per-model reasoning contract (see
# _resolve_cli_reasoning); an explicit --reasoning stays the user's intent.
if _new_model and getattr(self, "_explicit_reasoning_config", None) is None:
from hermes_cli.cli_model_switch_mixin import _resolve_cli_reasoning
_resolve_cli_reasoning(self)
except Exception as exc:
logger.debug("first-run config re-sync failed: %s", exc)
# Force credential re-resolution + agent rebuild on next use.