fix(toolsets): stop disabled browser from stripping web_search

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
This commit is contained in:
falkoro
2026-07-15 18:05:07 +02:00
committed by kshitij
parent b51c055a12
commit decacbac35
3 changed files with 31 additions and 5 deletions

View File

@@ -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):

View File

@@ -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",

View File

@@ -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 "