From decacbac35eb1e083e668019cfb3da32236c1f1e Mon Sep 17 00:00:00 2001 From: falkoro <39274208+falkoro@users.noreply.github.com> Date: Wed, 15 Jul 2026 18:05:07 +0200 Subject: [PATCH] fix(toolsets): stop disabled browser from stripping web_search MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit web_search belongs to the web/search toolsets, but the browser toolset also listed it as a member. Because disabled_toolsets is a strict end-of-pipeline subtraction (#17309), disabling browser — a natural choice for headless/Docker deployments with no Chromium — stripped web_search from every session even when web was enabled. Remove web_search from the browser composite so its membership no longer couples web search to browser availability. The browser_tasks RL distribution kept web_search via browser, so add the search toolset there to preserve its effective tool availability. Fixes #64503 --- tests/test_model_tools.py | 22 ++++++++++++++++++++++ toolset_distributions.py | 6 +++--- toolsets.py | 8 ++++++-- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/tests/test_model_tools.py b/tests/test_model_tools.py index acbf794c08..f5d84e56ff 100644 --- a/tests/test_model_tools.py +++ b/tests/test_model_tools.py @@ -450,6 +450,28 @@ class TestDisabledToolsetsPlatformBundle: assert "discord" not in names + def test_disabling_browser_keeps_web_search_when_web_enabled(self): + """Regression for #64503: disabling the `browser` toolset must NOT + strip `web_search`, which belongs to `web`/`search`. Disabling browser + (natural in headless/Docker deployments with no Chromium) previously + removed web_search from every session because `browser` statically + listed it as a member and disabled_toolsets is a strict subtraction. + + Asserted at the toolset-resolution layer, not get_tool_definitions: + web_search's check_fn (Tavily key) filters it from the final schema in + CI, so membership is the correct layer to pin the fix.""" + from toolsets import resolve_toolset + + browser_tools = set(resolve_toolset("browser")) + assert "web_search" not in browser_tools, ( + "web_search must not be a member of the `browser` toolset — that " + "is what lets `disabled_toolsets: [browser]` strip it (#64503)" + ) + # browser still owns its own tools, and web/search still own web_search. + assert "browser_navigate" in browser_tools + assert "web_search" in set(resolve_toolset("web")) + assert "web_search" in set(resolve_toolset("search")) + def test_bundle_non_core_tools_unknown_falls_back(self): diff --git a/toolset_distributions.py b/toolset_distributions.py index 409d7ce818..2d9e310349 100644 --- a/toolset_distributions.py +++ b/toolset_distributions.py @@ -30,10 +30,10 @@ DISTRIBUTIONS = { "reasoning": _dist("Heavy research/reasoning distribution with minimal other tools", web=90, file=60, terminal=20), "browser_use": _dist("Full browser-based web interaction with search, vision, and page control", browser=100, web=80, vision=70), "browser_only": _dist("Only browser automation tools for pure web interaction tasks", browser=100), - # browser-use-tasks.jsonl: the browser toolset includes web_search since Google blocks direct browser searches + # browser-use-tasks.jsonl: web_search for finding URLs since Google blocks direct browser searches "browser_tasks": _dist( - "Browser-focused distribution (browser toolset includes web_search for finding URLs since Google blocks direct browser searches)", - browser=97, vision=12, terminal=15, + "Browser-focused distribution with web_search for finding URLs (Google blocks direct browser searches)", + browser=97, search=97, vision=12, terminal=15, ), # nous-terminal-tasks.jsonl "terminal_tasks": _dist("Terminal-focused distribution with high terminal/file availability, occasional other tools", diff --git a/toolsets.py b/toolsets.py index 7e87f49dd8..cf7aef5c5a 100644 --- a/toolsets.py +++ b/toolsets.py @@ -100,10 +100,14 @@ TOOLSETS = { "instructions and knowledge", ["skills_list", "skill_view", "skill_manage"], ), + # web_search belongs to `web`/`search` only. Listing it here too let + # `disabled_toolsets: [browser]` (headless/Docker deployments) strip + # web_search from every session, because disabled toolsets are a strict + # end-of-pipeline subtraction (#17309, #64503). "browser": _ts( "Browser automation for web interaction (navigate, click, type, scroll, " - "iframes, hold-click) with web search for finding URLs", - [t for t in _HERMES_CORE_TOOLS if t.startswith("browser_")] + ["web_search"], + "iframes, hold-click)", + [t for t in _HERMES_CORE_TOOLS if t.startswith("browser_")], ), "cronjob": _ts( "Cronjob management tool - create, list, update, pause, resume, remove, and "