{"repo":"NousResearch/hermes-agent","public_id":"566","display_label":"Disabled tools still active or advertised in agent","summary":"A cluster of bugs and fixes around tool/toolset disabling logic not being enforced: disabled toolsets were re-enabling themselves, being advertised in descriptions, or still dispatched at runtime across CLI, gateway, and web backends.","subsystem":"agent","counts":{"members":6,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":3,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":18197},"resolution":{"state":"partially_resolved","by":"pr-18197"},"freshness":{"state":"verified","last_verified_at":"2026-05-01T03:24:39+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.24799999999999997},"fix_edges":[{"issue_number":17309,"pr_number":18197,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":566,"title":"fix: Disabled tools still advertised in execute_code and delegate_task descriptions","url":null,"state":"closed"},{"kind":"issue","number":17309,"title":"[Bug]: WEB tools not being disabled even after disabling.","url":null,"state":"closed"},{"kind":"issue","number":26568,"title":"[Bug]: Hermes Agent ignoring hot memory and using disabled tools after latest update","url":null,"state":"closed"},{"kind":"pr","number":17343,"title":"Enforce subtractive toolset filtration in tool resolution logic","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"e5d702141","number":0},{"kind":"pr","value":"#18197","number":18197},{"kind":"pr","value":"#15291","number":15291},{"kind":"pr","value":"#17309","number":17309}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":18197,"title":"fix(tools): strictly subtract agent.disabled_toolsets in CLI + gateway","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["cli.py","gateway/run.py","hermes_cli/config.py","model_tools.py","scripts/release.py"],"fix_hunks":[{"file":"cli.py","hunks":["@@ -15,7 +15,6 @@\n \n import logging\n import os\n-import re\n import shutil\n import sys\n import json","@@ -600,6 +599,7 @@ def load_cli_config() -> Dict[str, Any]:\n # Load configuration at module startup\n CLI_CONFIG = load_cli_config()\n \n+\n # Initialize centralized logging early — agent.log + errors.log in ~/.hermes/logs/.\n # This ensures CLI sessions produce a log trail even before AIAgent is instantiated.\n try:","@@ -2118,6 +2118,8 @@ def __init__(\n         \n         # Parse and validate toolsets\n         self.enabled_toolsets = toolsets\n+        self.disabled_toolsets = CLI_CONFIG[\"agent\"].get(\"disabled_toolsets\") or []\n+\n         if toolsets and \"all\" not in toolsets and \"*\" not in toolsets:\n             # Validate each toolset — MCP server names are resolved via\n             # live registry aliases (registered during discover_mcp_tools),","@@ -3568,6 +3570,7 @@ def _init_agent(self, *, model_override: str = None, runtime_override: dict = No\n                 credential_pool=[redacted:secret-assignment]\"credential_pool\"),\n                 max_iterations=self.max_turns,\n                 enabled_toolsets=self.enabled_toolsets,\n+                disabled_toolsets=self.disabled_toolsets,\n                 verbose_logging=self.verbose,\n                 quiet_mode=not self.verbose,\n                 ephemeral_system_prompt=self.system_prompt if self.system_prompt else None,"]},{"file":"gateway/run.py","hunks":["@@ -7978,6 +7978,8 @@ async def _run_background_task(\n \n             from hermes_cli.tools_config import _get_platform_tools\n             enabled_toolsets = sorted(_get_platform_tools(user_config, platform_key))\n+            agent_cfg = user_config.get(\"agent\") or {}\n+            disabled_toolsets = agent_cfg.get(\"disabled_toolsets\") or None\n \n             pr = self._provider_routing\n             max_iterations = int(os.getenv(\"HERMES_MAX_ITERATIONS\", \"90\"))","@@ -7994,6 +7996,7 @@ def run_sync():\n                     quiet_mode=True,\n                     verbose_logging=False,\n                     enabled_toolsets=enabled_toolsets,\n+                    disabled_toolsets=disabled_toolsets,\n                     reasoning_config=reasoning_config,\n                     service_tier=self._service_tier,\n                     request_overrides=turn_route.get(\"request_overrides\"),","@@ -10352,6 +10355,7 @@ async def _run_process_watcher(self, watcher: dict) -> None:\n         (\"compression\", \"threshold\"),\n         (\"compression\", \"target_ratio\"),\n         (\"compression\", \"protect_last_n\"),\n+        (\"agent\", \"disabled_toolsets\"),\n     )\n \n     @classmethod","@@ -11135,6 +11139,8 @@ def _run_still_current() -> bool:\n \n         from hermes_cli.tools_config import _get_platform_tools\n         enabled_toolsets = sorted(_get_platform_tools(user_config, platform_key))\n+        agent_cfg_local = user_config.get(\"agent\") or {}\n+        disabled_toolsets = agent_cfg_local.get(\"disabled_toolsets\") or None\n \n         display_config = user_config.get(\"display\", {})\n         if not isinstance(display_config, dict):","@@ -11763,6 +11769,7 @@ def _interim_assistant_cb(text: str, *, already_streamed: bool = False) -> None:\n                     quiet_mode=True,\n                     verbose_logging=False,\n                     enabled_toolsets=enabled_toolsets,\n+                    disabled_toolsets=disabled_toolsets,\n                     ephemeral_system_prompt=combined_ephemeral or None,\n                     prefill_messages=self._prefill_messages or None,\n                     reasoning_config=reasoning_config,"]},{"file":"hermes_cli/config.py","hunks":["@@ -457,6 +457,7 @@ def _ensure_hermes_home_managed(home: Path):\n         # remains available as a tool regardless of this setting — the routing\n         # only controls how inbound user images are presented.\n         \"image_input_mode\": \"auto\",\n+        \"disabled_toolsets\": [],\n     },\n     \n     \"terminal\": {"]},{"file":"model_tools.py","hunks":["@@ -356,12 +356,17 @@ def _compute_tool_definitions(\n             else:\n                 if not quiet_mode:\n                     print(f\"⚠️  Unknown toolset: {toolset_name}\")\n-\n-    elif disabled_toolsets:\n+    else:\n+        # Default: start with everything\n         from toolsets import get_all_toolsets\n         for ts_name in get_all_toolsets():\n             tools_to_include.update(resolve_toolset(ts_name))\n \n+    # Always apply disabled toolsets as a subtraction step at the end.\n+    # This ensures that even if a composite toolset (like hermes-cli)\n+    # is enabled, any tools belonging to a disabled toolset are strictly\n+    # stripped out. See issue #17309.\n+    if disabled_toolsets:\n         for toolset_name in disabled_toolsets:\n             if validate_toolset(toolset_name):\n                 resolved = resolve_toolset(toolset_name)","@@ -376,10 +381,6 @@ def _compute_tool_definitions(\n             else:\n                 if not quiet_mode:\n                     print(f\"⚠️  Unknown toolset: {toolset_name}\")\n-    else:\n-        from toolsets import get_all_toolsets\n-        for ts_name in get_all_toolsets():\n-            tools_to_include.update(resolve_toolset(ts_name))\n \n     # Plugin-registered tools are now resolved through the normal toolset\n     # path — validate_toolset() / resolve_toolset() / get_all_toolsets()"]},{"file":"scripts/release.py","hunks":["@@ -48,6 +48,7 @@\n     \"[redacted:email]\": \"adamludwin\",\n     \"[redacted:email]\": \"exiao\",\n     \"[redacted:email]\": \"rylena\",\n+    \"[redacted:email]\": \"jatingodnani\",\n     \"[redacted:email]\": \"tmimmanuel\",\n     \"[redacted:email]\": \"revaraver\",\n     # Matrix parity salvage batch (April 2026)\n"]}]},{"kind":"pr","number":26691,"title":"fix(tools): refuse dispatch of tools outside the active toolset (#26568)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"e3624e00db6ddee7b1bf4009fc19453b5317f2f2","number":0},{"kind":"pr","value":"#26568","number":26568}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}