# NousResearch/hermes-agent · Session reset ignoring config default model

## Open now

- 0 issues, 2 PRs open on GitHub.
- PR #23182: fix: reload default model from config.yaml on /reset and /new
- PR #23262: fix(cli): reload configured default model on new session

The /new and /reset commands did not reload the default model from config.yaml, instead retaining in-session model switches. Multiple patches address session scoping and config reloading behavior.

## TL;DR

- 6 member(s): 0 open issue(s), 2 closed issue(s), 2 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #67979. Verify it addresses your case.
- **Recommended:** read PR #67979 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-67979`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.36`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #23131: Bug: /reset and /new do not reload default model from config.yaml
- #48055: /new does not reset model to config default after session-only /model switch

## Prior & competing PRs

- **#67979: fix: session-scoped /fast + full /new reset to config defaults**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `cli.py`, `gateway/run.py`, `gateway/slash_commands.py`, `hermes_cli/cli_commands_mixin.py`, `hermes_cli/commands.py`, `tests/cli/test_fast_command.py`, `tests/cli/test_reasoning_command.py`, `tests/gateway/test_choice_picker.py`, `tests/gateway/test_fast_command.py`, `tests/test_tui_gateway_server.py`, `tui_gateway/server.py`
  - fix hunks:
  - `cli.py`

    ```diff
    @@ -7154,6 +7154,68 @@ def new_session(self, silent=False, title=None):
             self.reasoning_config = _parse_reasoning_config(
                 CLI_CONFIG["agent"].get("reasoning_effort", "")
             )
    +        # /new is a full conversation boundary: session-scoped runtime
    +        # overrides (/model --session, /fast, one-turn restores) do not carry
    +        # forward.  Re-derive model/provider and service tier from config.yaml
    +        # so a session-only switch never leaks into the next session (#48055,
    +        # #23131).
    +        self._pending_one_turn_model_restore = None
    +        self.service_tier = _parse_service_tier_config(
    +            CLI_CONFIG["agent"].get("service_tier", "")
    +        )
    +        _model_config = CLI_CONFIG.get("model", {})
    +        _config_model = (
    +            (_model_config.get("default") or _model_config.get("model") or "")
    +            if isinstance(_model_config, dict)
    +            else (_model_config or "")
    +        )
    +        if _config_model and _config_model != getattr(self, "model", None):
    +            _config_provider = (
    +                _model_config.get("provider", "")
    +                if isinstance(_model_config, dict)
    +                else ""
    +            )
    +            try:
    +                from hermes_cli.model_switch import switch_model as _switch_model
    +
    +                _reset_result = _switch_model(
    +                    raw_input=_config_model,
    +                    current_provider=self.provider or "",
    +                    current_model=self.model or "",
    +                    current_base_url=self.base_url or "",
    +                    current_api_key=self.api_key or "",
    +                    is_global=False,
    +                    explicit_provider=_config_provider or "",
    +                )
    +                if _reset_result.success:
    +                    if self.agent:
    +                        self.agent.switch_model(
    +                            new_model=_reset_result.new_model,
    +                            new_provider=_reset_result.target_provider,
    +                            api_key=[redacted:secret-assignment]
    +                            base_url=_reset_result.base_url,
    +                            api_mode=_reset_result.api_mode,
    +                        )
    +                    self.model = _reset_result.new_model
    +                    self.provider = _reset_result.target_provider
    +                    self.requested_provider = _reset_result.target_provider
    +                    self._explicit_api_key = [redacted:secret-assignment]
    +                    self._explicit_base_url = _reset_result.base_url
    +                    if _reset_result.api_key:
    +                        self.api_key = [redacted:secret-assignment]
    +                    if _reset_result.base_url:
    +                        self.base_url = _reset_result.base_url
    +                    if _reset_result.api_mode:
    +                        self.api_mode = _reset_result.api_mode
    +                    if not silent:
    +                        _cprint(
    +                            f"  (model reset to config default: "
    +                            f"{_reset_result.new_model})"
    +                        )
    +            except Exception:
    +                # Best-effort: an unreachable config default must never block
    +                # /new. The session keeps the current working model.
    +                logger.debug("/new model reset to config default failed", exc_info=True)
             _sync_process_session_id(self.session_id)
     
             if self.agent:
    ```
  - `gateway/run.py`

    ```diff
    @@ -2031,6 +2031,7 @@ def _own_policy_open_startup_violation(config) -> Optional[str]:
         "_session_model_overrides",
         "_pending_one_turn_model_restores",
         "_session_reasoning_overrides",
    +    "_session_service_tier_overrides",
         "_pending_model_notes",
         "_last_resolved_model",
         "_queued_events",
    ```

    ```diff
    @@ -3055,6 +3056,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
         _session_model_overrides: Dict[str, Dict[str, str]] = {}
         _pending_one_turn_model_restores: Dict[str, Dict[str, Any]] = {}
         _session_reasoning_overrides: Dict[str, Dict[str, Any]] = {}
    +    _session_service_tier_overrides: Dict[str, Optional[str]] = {}
         _pending_turn_sidecar_notes: Dict[str, List[str]] = {}
         _session_ephemeral_pin: Dict[str, tuple] = {}
         _session_vc_last: Dict[str, str] = {}
    ```

    ```diff
    @@ -3264,6 +3266,9 @@ def __init__(self, config: Optional[GatewayConfig] = None):
             # Per-session reasoning effort overrides from /reasoning.
             # Key: session_key, Value: parsed reasoning config dict.
             self._session_reasoning_overrides: Dict[str, Dict[str, Any]] = {}
    +        # Per-session fast-mode overrides from /fast.
    +        # Key: session_key, Value: "priority" or None (explicit normal).
    +        self._session_service_tier_overrides: Dict[str, Optional[str]] = {}
             # Per-turn must-deliver notes relocated out of the ephemeral system
             # prompt (auto-reset note, first-contact intro, voice-channel change).
             # Staged by _handle_message_with_agent, consumed once by run_sync and
    ```

    ```diff
    @@ -5238,6 +5243,52 @@ def _set_session_reasoning_override(
             else:
                 self._session_reasoning_overrides[session_key] = dict(reasoning_config)
     
    +    def _resolve_session_service_tier(
    +        self,
    +        source=None,
    +        session_key: Optional[str] = None,
    +    ) -> Optional[str]:
    +        """Resolve the effective service tier for a session.
    +
    +        A session-scoped /fast override wins over the config default. The
    +        override dict stores "priority" or None (explicit normal), so key
    +        presence — not value truthiness — decides whether it applies.
    +        """
    +        resolved_session_key = session_key
    +        if not resolved_session_key and source is not None:
    +            try:
    +                resolved_session_key = self._session_key_for_source(source)
    +            except Exception:
    +                resolved_session_key = None
    +
    +        overrides = getattr(self, "_session_service_tier_overrides", {}) or {}
    +        if resolved_session_key and resolved_session_key in overrides:
    +            return overrides[resolved_session_key]
    +        return self._load_service_tier()
    +
    +    def _set_session_service_tier_override(
    +        self,
    +        session_key: str,
    +        service_tier,
    +        clear: bool = False,
    +    ) -> None:
    +        """Set or clear the session-scoped /fast override.
    +
    +        ``service_tier`` is "priority" or None (explicit normal). Pass
    +        ``clear=True`` to remove the override entirely (fall back to config).
    +        """
    +        if not session_key:
    +            return
    +        if "_session_service_tier_overrides" not in self.__dict__:
    +            # Force an instance-level dict: the class attribute is a shared
    +            # default for partially-constructed test runners, and mutating it
    +            # would leak overrides across runner instances.
    +            self._session_service_tier_overrides = {}
    +        if clear:
    +            self._session_service_tier_overrides.pop(session_key, None)
    +        else:
    +            self._session_service_tier_overrides[session_key] = service_tier
    +
         @staticmethod
         def _load_service_tier() -> str | None:
             """Load Priority Processing setting from config.yaml.
    ```

    ```diff
    @@ -14702,7 +14753,7 @@ async def _run_background_task_inner(
                     source=source, model=model
                 )
                 self._reasoning_config = reasoning_config
    -            self._service_tier = self._load_service_tier()
    +            self._service_tier = self._resolve_session_service_tier(source=source)
                 turn_route = self._resolve_turn_agent_config(prompt, model, runtime_kwargs)
     
                 # Enrich the prompt with image descriptions so the background
    ```

    ```diff
    @@ -19917,7 +19968,9 @@ def run_sync():
                     model=model,
                 )
                 self._reasoning_config = reasoning_config
    -            self._service_tier = self._load_service_tier()
    +            self._service_tier = self._resolve_session_service_tier(
    +                source=source, session_key=session_key
    +            )
                 # Set up stream consumer for token streaming or interim commentary.
                 _stream_consumer = None
                 _stream_delta_cb = None
    ```
  - `gateway/slash_commands.py`

    ```diff
    @@ -3108,43 +3108,64 @@ def _set_approval(enabled: bool):
             return out
     
         async def _handle_fast_command(self, event: MessageEvent) -> Optional[str]:
    -        """Handle /fast — mirror the CLI Priority Processing toggle in gateway chats."""
    +        """Handle /fast — mirror the CLI Priority Processing toggle in gateway chats.
    +
    +        Session-scoped by default; ``--global`` persists agent.service_tier
    +        to config.yaml (parity with /model and /reasoning).
    +        """
             from gateway.run import _load_gateway_config, _resolve_gateway_model
             from hermes_cli.models import model_supports_fast_mode
     
    -        args = event.get_command_args().strip().lower()
    -        self._service_tier = self._load_service_tier()
    +        raw_args = event.get_command_args().strip().lower()
    +        # Reuse the /reasoning arg parser: strips --global (any position),
    +        # normalizes unicode dashes.
    +        args, persist_global = self._parse_reasoning_command_args(raw_args)
    +        session_key = self._session_key_for_source(event.source)
    +        self._service_tier = self._resolve_session_service_tier(
    +            session_key=session_key
    +        )
     
             user_config = _load_gateway_config()
             model = _resolve_gateway_model(user_config)
             if not model_supports_fast_mode(model):
                 return t("gateway.fast.not_supported")
     
    -        def _apply_fast_selection(value: str) -> str:
    +        def _apply_fast_selection(value: str, persist: bool = False) -> str:
                 """Apply a /fast argument (typed or picked) and return the reply."""
                 if value in {"fast", "on"}:
    -                self._service_tier = "priority"
    +                tier = "priority"
                     saved_value = "fast"
                     label = t("gateway.fast.label_fast")
                 elif value in {"normal", "off"}:
    -                self._service_tier = None
    +                tier = None
                     saved_value = "normal"
                     label = t("gateway.fast.label_normal")
                 else:
                     return t("gateway.fast.unknown_arg", arg=value)
    -            if self._save_gateway_config_key("agent.service_tier", saved_value):
    -                return t("gateway.fast.saved", label=label)
    +            self._service_tier = tier
    +            if persist:
    +                if self._save_gateway_config_key("agent.service_tier", saved_value):
    +                    # Global write supersedes any session override.
    +                    self._set_session_service_tier_override(
    +                        session_key, None, clear=True
    +                    )
    +                    self._evict_cached_agent(session_key)
    +                    return t("gateway.fast.saved", label=label)
    +                # Config write failed — fall back to a session override so the
    +                # user's choice still applies (mirrors /reasoning --global).
    +                self._set_session_service_tier_override(session_key, tier)
    +                self._evict_cached_agent(session_key)
    +                return t("gateway.fast.session_only", label=label)
    +            self._set_session_service_tier_override(session_key, tier)
    +            self._evict_cached_agent(session_key)
                 return t("gateway.fast.session_only", label=label)
     
             if not args or args == "status":
                 is_fast = self._service_tier == "priority"
                 status = t("gateway.fast.status_fast") if is_fast else t("gateway.fast.status_normal")
     
    -            # Interactive picker on platforms that support it.
    -            session_key = self._session_key_for_source(event.source)
    -
                 async def _on_fast_choice(_chat_id: str, value: str) -> str:
    -                return _apply_fast_selection(value)
    +                return _apply_fast_selection(value, persist=persist_global)
     
                 picker_sent = await self._try_send_choice_picker(
                     event,
    ```

    ```diff
    @@ -3169,7 +3190,7 @@ async def _on_fast_choice(_chat_id: str, value: str) -> str:
     
                 return t("gateway.fast.status", mode=status)
     
    -        return _apply_fast_selection(args)
    +        return _apply_fast_selection(args, persist=persist_global)
     
         async def _handle_yolo_command(self, event: MessageEvent) -> Union[str, EphemeralReply]:
             """Handle /yolo — toggle dangerous command approval bypass for this session only."""
    ```
  - `hermes_cli/cli_commands_mixin.py`

    ```diff
    @@ -2653,7 +2653,11 @@ def _handle_busy_command(self, cmd: str):
                 _cprint(f"  {_ACCENT}✓ Busy input mode set to '{arg}' (session only){_RST}")
     
         def _handle_fast_command(self, cmd: str):
    -        """Handle /fast — toggle fast mode (OpenAI Priority Processing / Anthropic Fast Mode)."""
    +        """Handle /fast — toggle fast mode (OpenAI Priority Processing / Anthropic Fast Mode).
    +
    +        Session-scoped by default; ``--global`` persists agent.service_tier
    +        to config.yaml (parity with /model and /reasoning).
    +        """
             from cli import _ACCENT, _DIM, _RST, _cprint, save_config_value
             if not self._fast_command_available():
                 _cprint("  (._.) /fast is only available for models that support fast mode (OpenAI Priority Processing or Anthropic Fast Mode).")
    ```

    ```diff
    @@ -2672,10 +2676,15 @@ def _handle_fast_command(self, cmd: str):
             if len(parts) < 2 or parts[1].strip().lower() == "status":
                 status = "fast" if self.service_tier == "priority" else "normal"
                 _cprint(f"  {_ACCENT}{feature_name}: {status}{_RST}")
    -            _cprint(f"  {_DIM}Usage: /fast [normal|fast|status]{_RST}")
    +            _cprint(f"  {_DIM}Usage: /fast [normal|fast|status] [--global]{_RST}")
                 return
     
    -        arg = parts[1].strip().lower()
    +        arg_tokens = [redacted:secret-assignment]
    +        explicit_global = "--global" in arg_tokens
    +        arg = " ".join(
    +            token for token in arg_tokens
    +            if token not in ("--global", "--session")
    +        )
     
             if arg in {"fast", "on"}:
                 self.service_tier = "priority"
    ```

    ```diff
    @@ -2687,14 +2696,16 @@ def _handle_fast_command(self, cmd: str):
                 label = "NORMAL"
             else:
                 _cprint(f"  {_DIM}(._.) Unknown argument: {arg}{_RST}")
    -            _cprint(f"  {_DIM}Usage: /fast [normal|fast|status]{_RST}")
    +            _cprint(f"  {_DIM}Usage: /fast [normal|fast|status] [--global]{_RST}")
                 return
     
             self.agent = None  # Force agent re-init with new service-tier config
    -        if save_config_value("agent.service_tier", saved_value):
    +        if explicit_global and save_config_value("agent.service_tier", saved_value):
                 _cprint(f"  {_ACCENT}✓ {feature_name} set to {label} (saved to config){_RST}")
    +        elif explicit_global:
    +            _cprint(f"  {_ACCENT}✓ {feature_name} set to {label} (session only; config save failed){_RST}")
             else:
    -            _cprint(f"  {_ACCENT}✓ {feature_name} set to {label} (session only){_RST}")
    +            _cprint(f"  {_ACCENT}✓ {feature_name} set to {label} (this session — use --global to persist){_RST}")
     
         def _handle_debug_command(self, cmd_original: str = ""):
             """Handle /debug — upload debug report + logs and print share URLs.
    ```
  - `hermes_cli/commands.py`

    ```diff
    @@ -156,8 +156,8 @@ class CommandDef:
                    args_hint="[level|show|hide|full|clamp] [--global]",
                    subcommands=("none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra", "show", "hide", "on", "off", "full", "clamp", "--global")),
         CommandDef("fast", "Toggle fast mode — OpenAI Priority Processing / Anthropic Fast Mode (Normal/Fast)", "Configuration",
    -               args_hint="[normal|fast|status]",
    -               subcommands=("normal", "fast", "status", "on", "off")),
    +               args_hint="[normal|fast|status] [--global]",
    +               subcommands=("normal", "fast", "status", "on", "off", "--global")),
         CommandDef("skin", "Show or change the display skin/theme", "Configuration",
                    cli_only=True, args_hint="[name]"),
         CommandDef("indicator", "Pick the TUI busy-indicator style", "Configuration",
    ```
  - `tests/cli/test_fast_command.py`

    ```diff
    @@ -83,6 +83,20 @@ def test_normal_argument_clears_service_tier(self):
             ):
                 cli_mod.HermesCLI._handle_fast_command(stub, "/fast normal")
     
    +        # Session-scoped by default: no config write.
    +        mock_save.assert_not_called()
    +        self.assertIsNone(stub.service_tier)
    +        self.assertIsNone(stub.agent)
    +
    +    def test_global_flag_persists_service_tier(self):
    +        cli_mod = _import_cli()
    +        stub = self._make_cli(service_tier="priority")
    +        with (
    +            patch.object(cli_mod, "_cprint"),
    +            patch.object(cli_mod, "save_config_value", return_value=True) as mock_save,
    +        ):
    +            cli_mod.HermesCLI._handle_fast_command(stub, "/fast normal --global")
    +
             mock_save.assert_called_once_with("agent.service_tier", "normal")
             self.assertIsNone(stub.service_tier)
             self.assertIsNone(stub.agent)
    ```
  - `tests/cli/test_reasoning_command.py`

    ```diff
    @@ -220,6 +220,63 @@ def test_new_session_clears_session_reasoning_override(self):
             self.assertEqual(agent.reasoning_config, {"enabled": True, "effort": "medium"})
             agent.reset_session_state.assert_called_once()
     
    +    def test_new_session_resets_service_tier_and_model_from_config(self):
    +        """/new re-derives service tier and model from config.yaml — session
    +        /fast and /model switches do not carry forward (#48055, #23131)."""
    +        from cli import CLI_CONFIG, HermesCLI
    +
    +        agent = SimpleNamespace(
    +            reasoning_config=None,
    +            reset_session_state=MagicMock(),
    +            switch_model=MagicMock(),
    +        )
    +        stub = SimpleNamespace(
    +            agent=agent,
    +            conversation_history=[],
    +            session_id="old-session",
    +            _session_db=None,
    +            _pending_title=None,
    +            _resumed=False,
    +            reasoning_config=None,
    +            _notify_session_boundary=MagicMock(),
    +            # Session had switched to fast + a session-only model.
    +            service_tier="priority",
    +            _pending_one_turn_model_restore={"model": "stale"},
    +            model="session-switched-model",
    +            provider="openrouter",
    +            requested_provider="openrouter",
    +            api_key="k",
    +            base_url="",
    +            api_mode="",
    +        )
    +
    +        fake_result = SimpleNamespace(
    +            success=True,
    +            new_model="config-default-model",
    +            target_provider="openrouter",
    +            api_key="k2",
    +            base_url="https://openrouter.ai/api/v1",
    +            api_mode="chat_completions",
    +        )
    +        with patch.dict(
    +            CLI_CONFIG.setdefault("agent", {}),
    +            {"reasoning_effort": "medium", "service_tier": "normal"},
    +        ), patch.dict(
    +            CLI_CONFIG,
    +            {"model": {"default": "config-default-model", "provider": "openrouter"}},
    +        ), patch(
    +            "hermes_cli.model_switch.switch_model", return_value=fake_result
    +        ):
    +            HermesCLI.new_session(stub, silent=True)
    +
    +        # Fast override cleared back to config default (normal → None).
    +        self.assertIsNone(stub.service_tier)
    +        # One-turn restore snapshot cleared.
    +        self.assertIsNone(stub._pending_one_turn_model_restore)
    +        # Model reset to the config default via the live agent swap.
    +        self.assertEqual(stub.model, "config-default-model")
    +        agent.switch_model.assert_called_once()
    +
     
     # ---------------------------------------------------------------------------
     # Reasoning extraction and result dict
    ```
  - `tests/gateway/test_choice_picker.py`

    ```diff
    @@ -189,7 +189,8 @@ async def test_bare_fast_sends_picker_when_adapter_supports_it(self, tmp_path, m
             assert values == ["fast", "normal"]
     
         @pytest.mark.asyncio
    -    async def test_fast_picker_selection_persists_service_tier(self, tmp_path, monkeypatch):
    +    async def test_fast_picker_selection_is_session_scoped(self, tmp_path, monkeypatch):
    +        """A bare /fast picker tap applies a session override, not a config write."""
             self._patch_fast_support(monkeypatch, tmp_path)
             adapter = _PickerAdapter()
             runner = _make_runner(adapter)
    ```

    ```diff
    @@ -199,6 +200,22 @@ async def test_fast_picker_selection_persists_service_tier(self, tmp_path, monke
             on_choice = adapter.calls[0]["on_choice_selected"]
             await on_choice(event.source.chat_id, "fast")
     
    +        assert runner._service_tier == "priority"
    +        assert runner._session_service_tier_overrides
    +        assert not (tmp_path / "config.yaml").exists()
    +
    +    @pytest.mark.asyncio
    +    async def test_fast_picker_global_flag_persists_service_tier(self, tmp_path, monkeypatch):
    +        """A /fast --global picker tap persists agent.service_tier to config."""
    +        self._patch_fast_support(monkeypatch, tmp_path)
    +        adapter = _PickerAdapter()
    +        runner = _make_runner(adapter)
    +        event = _make_event("/fast --global")
    +
    +        await runner._handle_fast_command(event)
    +        on_choice = adapter.calls[0]["on_choice_selected"]
    +        await on_choice(event.source.chat_id, "fast")
    +
             assert runner._service_tier == "priority"
             saved = yaml.safe_load((tmp_path / "config.yaml").read_text(encoding="utf-8"))
             assert saved["agent"]["service_tier"] == "fast"
    ```
  - `tests/gateway/test_fast_command.py`

    ```diff
    @@ -143,7 +143,8 @@ def test_turn_route_skips_priority_processing_for_unsupported_models():
     
     
     @pytest.mark.asyncio
    -async def test_handle_fast_command_persists_config(monkeypatch, tmp_path):
    +async def test_handle_fast_command_session_scoped_by_default(monkeypatch, tmp_path):
    +    """Bare /fast fast applies a session override — config.yaml untouched."""
         runner = _make_runner()
     
         monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    ```

    ```diff
    @@ -152,11 +153,57 @@ async def test_handle_fast_command_persists_config(monkeypatch, tmp_path):
     
         response = await runner._handle_fast_command(_make_event("/fast fast"))
     
    +    assert "FAST" in response
    +    assert runner._service_tier == "priority"
    +    # Session override recorded; config.yaml NOT written.
    +    assert runner._session_service_tier_overrides
    +    assert not (tmp_path / "config.yaml").exists()
    +
    +
    [redacted:email]
    +async def test_handle_fast_command_global_flag_persists_config(monkeypatch, tmp_path):
    +    runner = _make_runner()
    +
    +    monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    +    monkeypatch.setattr(gateway_run, "_load_gateway_config", lambda: {})
    +    monkeypatch.setattr(gateway_run, "_resolve_gateway_model", lambda config=None: "gpt-5.4")
    +
    +    response = await runner._handle_fast_command(_make_event("/fast fast --global"))
    +
         assert "FAST" in response
         assert runner._service_tier == "priority"
     
         saved = yaml.safe_load((tmp_path / "config.yaml").read_text(encoding="utf-8"))
         assert saved["agent"]["service_tier"] == "fast"
    +    # Global write supersedes the session override.
    +    assert not runner._session_service_tier_overrides
    +
    +
    [redacted:email]
    +async def test_session_fast_override_beats_config_default(monkeypatch, tmp_path):
    +    """A session /fast normal wins over agent.service_tier: fast in config."""
    +    runner = _make_runner()
    +
    +    monkeypatch.setattr(gateway_run, "_hermes_home", tmp_path)
    +    monkeypatch.setattr(gateway_run, "_load_gateway_config", lambda: {})
    +    monkeypatch.setattr(
    +        gateway_run,
    +        "_load_gateway_runtime_config",
    +        lambda: {"agent": {"service_tier": "fast"}},
    +    )
    +    monkeypatch.setattr(gateway_run, "_resolve_gateway_model", lambda config=None: "gpt-5.4")
    +
    +    event = _make_event("/fast normal")
    +    session_key = runner._session_key_for_source(event.source)
    +
    +    response = await runner._handle_fast_command(event)
    +
    +    assert "NORMAL" in response
    +    # Override stores explicit None (normal) and wins over config "fast".
    +    assert session_key in runner._session_service_tier_overrides
    +    assert runner._resolve_session_service_tier(session_key=session_key) is None
    +    # A different session still gets the config default.
    +    assert runner._resolve_session_service_tier(session_key="other-session") == "priority"
     
     
     @pytest.mark.asyncio
    ```
  - `tests/test_tui_gateway_server.py`

    ```diff
    @@ -10459,16 +10459,20 @@ def fake_make_agent(sid, key, session_id=None, session_db=None, **kwargs):
     # ── billing/subscription state + error serialization ─────────────────
     
     
    -def test_reset_session_agent_preserves_explicit_normal_fast(monkeypatch):
    +def test_reset_session_agent_clears_session_overrides(monkeypatch):
    +    """/new is a full conversation boundary: session-scoped /model, /reasoning,
    +    and /fast overrides do NOT carry into the fresh agent — it re-derives
    +    everything from config.yaml (#48055, #23131)."""
         captured = {}
         new_agent = types.SimpleNamespace(model="openai/gpt-5.4", service_tier="")
         session = _session(
             agent=types.SimpleNamespace(
                 model="openai/gpt-5.4",
    -            reasoning_config=None,
    +            reasoning_config={"enabled": True, "effort": "high"},
                 service_tier="",
             ),
             model_override={"model": "openai/gpt-5.4"},
    +        create_reasoning_override={"enabled": True, "effort": "high"},
             create_service_tier_override="",
         )
     
    ```

    ```diff
    @@ -10488,7 +10492,14 @@ def make_agent(*_args, **kwargs):
     
         server._reset_session_agent("sid", session)
     
    -    assert captured["service_tier_override"] == ""
    +    # No session overrides forwarded — fresh agent builds from config.
    +    assert "model_override" not in captured
    +    assert "reasoning_config_override" not in captured
    +    assert "service_tier_override" not in captured
    +    # And the session pins are gone so a later rebuild can't resurrect them.
    +    assert "model_override" not in session
    +    assert "create_reasoning_override" not in session
    +    assert "create_service_tier_override" not in session
         assert session["agent"] is new_agent
     
     
    ```
  - `tui_gateway/server.py`

    ```diff
    @@ -4765,25 +4765,22 @@ def tool_progress(event_type: str, name: str | None = None, preview: str | None
     def _reset_session_agent(sid: str, session: dict) -> dict:
         tokens = [redacted:secret-assignment]"session_key"])
         try:
    -        # Preserve this session's chosen model AND reasoning across /new so a
    -        # reset doesn't silently revert to global config (or to a model
    -        # another session set). See the cross-session-contamination note in
    -        # _apply_model_switch.
    -        reset_kw = {"model_override": session.get("model_override")}
    -        old_reasoning = getattr(session.get("agent"), "reasoning_config", None)
    -        if old_reasoning is None:
    -            old_reasoning = session.get("create_reasoning_override")
    -        if isinstance(old_reasoning, dict):
    -            reset_kw["reasoning_config_override"] = old_reasoning
    -        create_service_tier_override = session.get("create_service_tier_override")
    -        if create_service_tier_override is not None:
    -            reset_kw["service_tier_override"] = create_service_tier_override
    +        # /new is a full conversation boundary: session-scoped runtime
    +        # overrides (/model, /reasoning, /fast) do NOT carry forward — the
    +        # fresh agent re-derives model/provider, reasoning, and service tier
    +        # from config.yaml (#48055, #23131). Session pins are cleared below so
    +        # a rebuild can't resurrect them. (Global process state is still never
    +        # touched — see the cross-session-contamination note in
    +        # _apply_model_switch.)
    +        session.pop("model_override", None)
    +        session.pop("create_reasoning_override", None)
    +        session.pop("create_service_tier_override", None)
    +        session.pop("one_turn_model_restore", None)
             new_agent = _make_agent(
                 sid,
                 session["session_key"],
                 session_id=session["session_key"],
                 platform_override=_session_source(session),
    -            **reset_kw,
             )
         finally:
             _clear_session_context(tokens)
    ```
- **#23182: fix: reload default model from config.yaml on /reset and /new**. open
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#23131`
  - files: `cli.py`, `tests/cli/test_cli_new_session.py`
- **#23262: fix(cli): reload configured default model on new session**. open
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#23182`, `#23131`
  - files: `cli.py`, `tests/cli/test_cli_new_session.py`
- **#23342: fix(cli): reload default model from config on /reset and /new**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#23182`, `#23131`

## Fix status

- Merged to main in PR #67979 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-07-20`.
- Subsystem: `hermes_cli`
