From 76f40e6449ac8e4f0ceddb860ae0abdf8606a2ba Mon Sep 17 00:00:00 2001 From: yoniebans Date: Wed, 13 May 2026 16:54:56 +0200 Subject: [PATCH] fix(session_search): read default_mode from auxiliary.session_search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix wired _resolve_user_default_mode() to look up tools.session_search.default_mode, but the config schema has no top-level 'tools' section. The closest analogue is auxiliary., which already groups per-tool config by tool name (auxiliary.vision has download_timeout, auxiliary.session_search has max_concurrency — neither is strictly aux-LLM routing). This moves the lookup to auxiliary.session_search.default_mode so the knob lives next to max_concurrency and the existing session_search config block. Adds default_mode to the default config scaffold so it shows up in fresh installs. Updates docstring, tool description string, warning messages, and all 7 mock-config tests to the new path. 88/88 tests passing. --- hermes_cli/config.py | 1 + tests/tools/test_session_search.py | 26 +++++++++++++------------- tools/session_search_tool.py | 23 +++++++++++++---------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index feeb10892f..68c5e104dd 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -828,6 +828,7 @@ DEFAULT_CONFIG = { "timeout": 30, "extra_body": {}, "max_concurrency": 3, # Clamp parallel summaries to avoid request-burst 429s on small providers + "default_mode": "summary", # 'fast' | 'summary' — which mode session_search uses when caller passes none }, "skills_hub": { "provider": "auto", diff --git a/tests/tools/test_session_search.py b/tests/tools/test_session_search.py index 02af911693..6aa1e745af 100644 --- a/tests/tools/test_session_search.py +++ b/tests/tools/test_session_search.py @@ -746,7 +746,7 @@ class TestSessionSearch: If the handler substitutes "summary" when the LLM omits ``mode``, then ``_resolve_user_default_mode()`` is structurally unreachable from real - tool calls and the ``tools.session_search.default_mode`` config knob + tool calls and the ``auxiliary.session_search.default_mode`` config knob becomes dead code. This is the registry-handler counterpart to ``test_run_agent_special_session_search_paths_forward_mode``. """ @@ -761,13 +761,13 @@ class TestSessionSearch: ) assert 'mode=args.get("mode", "summary")' not in source, ( "registry handler must not hardcode \"summary\" as the mode default — " - "it shadows tools.session_search.default_mode in config.yaml" + "it shadows auxiliary.session_search.default_mode in config.yaml" ) def test_unset_mode_via_registry_honours_configured_default(self, monkeypatch): """End-to-end: unset mode through the registry handler resolves to config. - With ``tools.session_search.default_mode: fast`` configured, an LLM tool + With ``auxiliary.session_search.default_mode: fast`` configured, an LLM tool call that omits ``mode`` must run in fast mode (no aux LLM), not summary. This is the regression test for the bug where three layers of hardcoded ``"summary"`` defaults made the config knob unreachable. @@ -779,7 +779,7 @@ class TestSessionSearch: self._clear_default_mode_cache() monkeypatch.setattr( "hermes_cli.config.load_config", - lambda: {"tools": {"session_search": {"default_mode": "fast"}}}, + lambda: {"auxiliary": {"session_search": {"default_mode": "fast"}}}, ) # Sanity: the resolver itself sees fast. assert _resolve_user_default_mode() == "fast" @@ -798,7 +798,7 @@ class TestSessionSearch: ) # ----------------------------------------------------------------- - # User-configurable default mode (tools.session_search.default_mode + # User-configurable default mode (auxiliary.session_search.default_mode # in ~/.hermes/config.yaml). Lets a user opt into fast-as-default # without having to pass mode= on every call. # ----------------------------------------------------------------- @@ -818,12 +818,12 @@ class TestSessionSearch: assert _resolve_user_default_mode() == "summary" def test_user_can_configure_fast_as_default(self, monkeypatch): - """tools.session_search.default_mode: fast → unset mode resolves to 'fast'.""" + """auxiliary.session_search.default_mode: fast → unset mode resolves to 'fast'.""" from tools.session_search_tool import _resolve_user_default_mode self._clear_default_mode_cache() monkeypatch.setattr( "hermes_cli.config.load_config", - lambda: {"tools": {"session_search": {"default_mode": "fast"}}}, + lambda: {"auxiliary": {"session_search": {"default_mode": "fast"}}}, ) assert _resolve_user_default_mode() == "fast" @@ -833,7 +833,7 @@ class TestSessionSearch: self._clear_default_mode_cache() monkeypatch.setattr( "hermes_cli.config.load_config", - lambda: {"tools": {"session_search": {"default_mode": "summary"}}}, + lambda: {"auxiliary": {"session_search": {"default_mode": "summary"}}}, ) assert _resolve_user_default_mode() == "summary" @@ -844,7 +844,7 @@ class TestSessionSearch: self._clear_default_mode_cache() monkeypatch.setattr( "hermes_cli.config.load_config", - lambda: {"tools": {"session_search": {"default_mode": "smary"}}}, + lambda: {"auxiliary": {"session_search": {"default_mode": "smary"}}}, ) with caplog.at_level(logging.WARNING): assert _resolve_user_default_mode() == "summary" @@ -857,7 +857,7 @@ class TestSessionSearch: self._clear_default_mode_cache() monkeypatch.setattr( "hermes_cli.config.load_config", - lambda: {"tools": {"session_search": {"default_mode": "guided"}}}, + lambda: {"auxiliary": {"session_search": {"default_mode": "guided"}}}, ) assert _resolve_user_default_mode() == "summary" @@ -868,7 +868,7 @@ class TestSessionSearch: for bad in (42, ["fast"], {"mode": "fast"}, True): monkeypatch.setattr( "hermes_cli.config.load_config", - lambda b=bad: {"tools": {"session_search": {"default_mode": b}}}, + lambda b=bad: {"auxiliary": {"session_search": {"default_mode": b}}}, ) self._clear_default_mode_cache() assert _resolve_user_default_mode() == "summary", f"bad value {bad!r} should fall back" @@ -881,7 +881,7 @@ class TestSessionSearch: self._clear_default_mode_cache() monkeypatch.setattr( "hermes_cli.config.load_config", - lambda: {"tools": {"session_search": {"default_mode": "fast"}}}, + lambda: {"auxiliary": {"session_search": {"default_mode": "fast"}}}, ) mock_db = MagicMock() mock_db.search_messages.return_value = [] @@ -900,7 +900,7 @@ class TestSessionSearch: self._clear_default_mode_cache() monkeypatch.setattr( "hermes_cli.config.load_config", - lambda: {"tools": {"session_search": {"default_mode": "fast"}}}, + lambda: {"auxiliary": {"session_search": {"default_mode": "fast"}}}, ) async def fail_summarize(*_args, **_kwargs): diff --git a/tools/session_search_tool.py b/tools/session_search_tool.py index 2e0c23a93d..8c6f6296a5 100644 --- a/tools/session_search_tool.py +++ b/tools/session_search_tool.py @@ -26,16 +26,19 @@ MAX_SESSION_CHARS = 100_000 # Default mode is summary unless the user opts into a different one via -# ``tools.session_search.default_mode`` in ~/.hermes/config.yaml. Only ``fast`` -# and ``summary`` are valid defaults — guided requires anchors and can't be -# used standalone. Wrapped in lru_cache so the YAML read happens at most once -# per process; the CLI / TUI is the typical caller and config changes need a -# restart anyway. +# ``auxiliary.session_search.default_mode`` in ~/.hermes/config.yaml. Lives +# alongside the other session_search-scoped knobs (provider, model, +# max_concurrency) — "auxiliary" started as aux-LLM routing but in practice +# groups per-tool config by tool name. Only ``fast`` and ``summary`` are +# valid defaults — guided requires anchors and can't be used standalone. +# Wrapped in lru_cache so the YAML read happens at most once per process; +# the CLI / TUI is the typical caller and config changes need a restart +# anyway. _VALID_DEFAULT_MODES = ("fast", "summary") def _resolve_user_default_mode() -> str: - """Look up ``tools.session_search.default_mode`` from ~/.hermes/config.yaml. + """Look up ``auxiliary.session_search.default_mode`` from ~/.hermes/config.yaml. Returns "summary" if unset, invalid, or the config loader is unavailable (e.g. tests, tools loaded outside the CLI). Logs a one-time warning on @@ -52,7 +55,7 @@ def _resolve_user_default_mode() -> str: return "summary" raw = ( - config.get("tools", {}) + config.get("auxiliary", {}) .get("session_search", {}) .get("default_mode") ) @@ -60,14 +63,14 @@ def _resolve_user_default_mode() -> str: return "summary" if not isinstance(raw, str): logging.warning( - "tools.session_search.default_mode in config.yaml must be a string, got %r — falling back to 'summary'", + "auxiliary.session_search.default_mode in config.yaml must be a string, got %r — falling back to 'summary'", raw, ) return "summary" normalised = raw.strip().lower() if normalised not in _VALID_DEFAULT_MODES: logging.warning( - "tools.session_search.default_mode=%r is not one of %s — falling back to 'summary'. " + "auxiliary.session_search.default_mode=%r is not one of %s — falling back to 'summary'. " "(guided requires anchors and cannot be a default.)", raw, _VALID_DEFAULT_MODES, ) @@ -1124,7 +1127,7 @@ SESSION_SEARCH_SCHEMA = { "Returns titles, previews, timestamps. Zero LLM cost, instant. Start here when the user " "asks 'what were we working on' or 'what did we do recently'.\n\n" "The default mode is configurable per-user via " - "``tools.session_search.default_mode: fast`` in ~/.hermes/config.yaml. When no mode is " + "``auxiliary.session_search.default_mode: fast`` in ~/.hermes/config.yaml. When no mode is " "passed explicitly, that user-configured value applies (then 'summary' as the final " "fallback).\n\n" "USE THIS PROACTIVELY when:\n"