NousResearch/hermes-agent

Session reset ignoring config default model

Cluster 23131 · hermes cli. Data as of

2 open items. Fix merged in #67979 on 20 Jul 2026. Check it covers your case.

The short answer

0open issues
2open PRs
6items grouped
Known?
Yes. 6 related items grouped here, 2 issues already closed.
Fix event
A fix was merged to main in PR #67979. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #67979. That is separate from the fix event.
Still open
0 issues and 2 PRs.

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.

Open issues0

No open issues.

Open PRs2

  • PR#23182fix: reload default model from config.yaml on /reset and /new
  • PR#23262fix(cli): reload configured default model on new session

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #67979 fix: session-scoped /fast + full /new reset to config defaults

    merged to main

    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

    Show the diff
    • cli.py
      @@ -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
      @@ -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",
      @@ -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] = {}
      @@ -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
      @@ -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.
      @@ -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
      @@ -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
      @@ -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,
      @@ -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
      @@ -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).")
      @@ -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"
      @@ -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
      @@ -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
      @@ -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
      @@ -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
      @@ -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)
      @@ -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
      @@ -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)
      @@ -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
      @@ -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="",
           )
       
      @@ -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
      @@ -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)

1 PR closed without merging

From the close comments: duplicate 1

  • #23342 fix(cli): reload default model from config on /reset and /new · duplicate → #23182, #23131
Closed issues · 2
  • Closed#23131Bug: /reset and /new do not reload default model from config.yaml
  • Closed#48055/new does not reset model to config default after session-only /model switch
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.36: the share of item 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.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
hermes cli
Merged PRs
#67979
Closed issues
2
Attempts
1 PR closed without merging
Fix link checked
20 Jul 2026

For your agent

The same facts, typed.