{"repo":"NousResearch/hermes-agent","public_id":"16696","display_label":"Expose limit parameter in web_search tool","summary":"These items concern adding and exposing a limit parameter for the web_search tool, along with documenting query operators. The request appears across a feature issue and two related pull requests.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":16934,"merged_at":"2026-04-28T09:09:30+00:00"},"resolution":{"state":"resolved","by":"pr-16934"},"freshness":{"state":"stale","last_verified_at":"2026-04-28T09:09:30+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":16696,"pr_number":16934,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-28T09:09:30+00:00"}],"members":[{"kind":"issue","number":16696,"title":"[Feature]: Expose limit parameter and document query operators in web_search tool","url":null,"state":"closed"},{"kind":"pr","number":16808,"title":"feat(web): expose limit for web_search","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#16934","number":16934},{"kind":"pr","value":"#16696","number":16696}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":16934,"title":"feat(web): expose limit for web_search","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tests/tools/test_web_tools_config.py","tools/web_tools.py","website/docs/reference/tools-reference.md"],"fix_hunks":[{"file":"tests/tools/test_web_tools_config.py","hunks":["@@ -448,6 +448,54 @@ def test_singleton_returns_same_instance(self):\n             assert client1 is client2\n \n \n+class TestWebSearchSchema:\n+    \"\"\"Test suite for web_search tool schema and handler wiring.\"\"\"\n+\n+    def test_schema_exposes_optional_limit(self):\n+        import tools.web_tools\n+\n+        limit_schema = tools.web_tools.WEB_SEARCH_SCHEMA[\"parameters\"][\"properties\"][\"limit\"]\n+\n+        assert limit_schema[\"type\"] == \"integer\"\n+        assert limit_schema[\"minimum\"] == 1\n+        assert limit_schema[\"maximum\"] == 100\n+        assert limit_schema[\"default\"] == 5\n+        assert \"limit\" not in tools.web_tools.WEB_SEARCH_SCHEMA[\"parameters\"][\"required\"]\n+\n+    def test_registered_handler_passes_limit(self):\n+        import tools.web_tools\n+\n+        entry = tools.web_tools.registry.get_entry(\"web_search\")\n+        with patch(\"tools.web_tools.web_search_tool\", return_value='{\"success\": true}') as mock_search:\n+            result = entry.handler({\"query\": \"site:example.com docs\", \"limit\": 12})\n+\n+        assert result == '{\"success\": true}'\n+        mock_search.assert_called_once_with(\"site:example.com docs\", limit=12)\n+\n+    def test_registered_handler_defaults_limit_to_five(self):\n+        import tools.web_tools\n+\n+        entry = tools.web_tools.registry.get_entry(\"web_search\")\n+        with patch(\"tools.web_tools.web_search_tool\", return_value='{\"success\": true}') as mock_search:\n+            result = entry.handler({\"query\": \"docs\"})\n+\n+        assert result == '{\"success\": true}'\n+        mock_search.assert_called_once_with(\"docs\", limit=5)\n+\n+    def test_web_search_clamps_limit_before_backend_call(self):\n+        import tools.web_tools\n+\n+        with patch(\"tools.web_tools._get_backend\", return_value=\"parallel\"), \\\n+             patch(\"tools.web_tools._parallel_search\", return_value={\"success\": True, \"data\": {\"web\": []}}) as mock_search, \\\n+             patch(\"tools.interrupt.is_interrupted\", return_value=False), \\\n+             patch.object(tools.web_tools._debug, \"log_call\"), \\\n+             patch.object(tools.web_tools._debug, \"save\"):\n+            result = json.loads(tools.web_tools.web_search_tool(\"docs\", limit=500))\n+\n+        assert result == {\"success\": True, \"data\": {\"web\": []}}\n+        mock_search.assert_called_once_with(\"docs\", 100)\n+\n+\n class TestWebSearchErrorHandling:\n     \"\"\"Test suite for web_search_tool() error responses.\"\"\"\n "]},{"file":"tools/web_tools.py","hunks":["@@ -1066,6 +1066,12 @@ def web_search_tool(query: str, limit: int = 5) -> str:\n     Raises:\n         Exception: If search fails or API key is not set\n     \"\"\"\n+    try:\n+        limit = int(limit)\n+    except (TypeError, ValueError):\n+        limit = 5\n+    limit = min(max(limit, 1), 100)\n+\n     debug_call_data = {\n         \"parameters\": {\n             \"query\": query,","@@ -2047,13 +2053,20 @@ def check_auxiliary_model() -> bool:\n \n WEB_SEARCH_SCHEMA = {\n     \"name\": \"web_search\",\n-    \"description\": \"Search the web for information on any topic. Returns up to 5 relevant results with titles, URLs, and descriptions.\",\n+    \"description\": \"Search the web for information. Returns up to 5 results by default with titles, URLs, and descriptions. The query is passed through to the configured backend, so operators such as site:domain, filetype:pdf, intitle:word, -term, and \\\"exact phrase\\\" may work when the backend supports them.\",\n     \"parameters\": {\n         \"type\": \"object\",\n         \"properties\": {\n             \"query\": {\n                 \"type\": \"string\",\n-                \"description\": \"The search query to look up on the web\"\n+                \"description\": \"The search query to look up on the web. You may include backend-supported operators such as site:example.com, filetype:pdf, intitle:word, -term, or \\\"exact phrase\\\".\"\n+            },\n+            \"limit\": {\n+                \"type\": \"integer\",\n+                \"description\": \"Maximum number of results to return. Defaults to 5.\",\n+                \"minimum\": 1,\n+                \"maximum\": 100,\n+                \"default\": 5\n             }\n         },\n         \"required\": [\"query\"]","@@ -2081,7 +2094,7 @@ def check_auxiliary_model() -> bool:\n     name=\"web_search\",\n     toolset=\"web\",\n     schema=WEB_SEARCH_SCHEMA,\n-    handler=lambda args, **kw: web_search_tool(args.get(\"query\", \"\"), limit=5),\n+    handler=lambda args, **kw: web_search_tool(args.get(\"query\", \"\"), limit=args.get(\"limit\", 5)),\n     check_fn=check_web_api_key,\n     requires_env=_web_requires_env(),\n     emoji=\"🔍\","]},{"file":"website/docs/reference/tools-reference.md","hunks":["@@ -179,7 +179,7 @@ Scoped to the Feishu document-comment handler. Drives comment read/write operati\n \n | Tool | Description | Requires environment |\n |------|-------------|----------------------|\n-| `web_search` | Search the web for information on any topic. Returns up to 5 relevant results with titles, URLs, and descriptions. | EXA_API_KEY or PARALLEL_API_KEY or FIRECRAWL_API_KEY or TAVILY_API_KEY |\n+| `web_search` | Search the web for information. Returns up to 5 results by default with titles, URLs, and descriptions. Accepts an optional `limit` (1-100, default 5). The query is passed through to the configured backend, so operators such as `site:domain`, `filetype:pdf`, `intitle:word`, `-term`, and `\"exact phrase\"` may work when the backend supports them. | EXA_API_KEY or PARALLEL_API_KEY or FIRECRAWL_API_KEY or TAVILY_API_KEY |\n | `web_extract` | Extract content from web page URLs. Returns page content in markdown format. Also works with PDF URLs — pass the PDF link directly and it converts to markdown text. Pages under 5000 chars return full markdown; larger pages are LLM-summarized. | EXA_API_KEY or PARALLEL_API_KEY or FIRECRAWL_API_KEY or TAVILY_API_KEY |\n \n ## `tts` toolset\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}