# NousResearch/hermes-agent · Disabled tools still active or advertised in agent

## Open now

- Nothing open on GitHub.

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.

## TL;DR

- 6 member(s): 0 open issue(s), 3 closed issue(s), 0 open PR(s), 1 merged PR(s), 2 PR(s) closed without merging.
- A fix was merged to main in PR #18197. Verify it addresses your case.
- **Recommended:** read PR #18197 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-18197`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
- Members outside that structural core may belong to a different underlying problem.
- Pairwise link density `0.25`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #566: fix: Disabled tools still advertised in execute_code and delegate_task descriptions
- #17309: \[Bug\]: WEB tools not being disabled even after disabling.
- #26568: \[Bug\]: Hermes Agent ignoring hot memory and using disabled tools after latest update

## Prior & competing PRs

- **#18197: fix(tools): strictly subtract agent.disabled_toolsets in CLI + gateway**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `cli.py`, `gateway/run.py`, `hermes_cli/config.py`, `model_tools.py`, `scripts/release.py`
  - fix hunks:
  - `cli.py`

    ```diff
    @@ -15,7 +15,6 @@
     
     import logging
     import os
    -import re
     import shutil
     import sys
     import json
    ```

    ```diff
    @@ -600,6 +599,7 @@ def load_cli_config() -> Dict[str, Any]:
     # Load configuration at module startup
     CLI_CONFIG = load_cli_config()
     
    +
     # Initialize centralized logging early — agent.log + errors.log in ~/.hermes/logs/.
     # This ensures CLI sessions produce a log trail even before AIAgent is instantiated.
     try:
    ```

    ```diff
    @@ -2118,6 +2118,8 @@ def __init__(
             
             # Parse and validate toolsets
             self.enabled_toolsets = toolsets
    +        self.disabled_toolsets = CLI_CONFIG["agent"].get("disabled_toolsets") or []
    +
             if toolsets and "all" not in toolsets and "*" not in toolsets:
                 # Validate each toolset — MCP server names are resolved via
                 # live registry aliases (registered during discover_mcp_tools),
    ```

    ```diff
    @@ -3568,6 +3570,7 @@ def _init_agent(self, *, model_override: str = None, runtime_override: dict = No
                     credential_pool=[redacted:secret-assignment]"credential_pool"),
                     max_iterations=self.max_turns,
                     enabled_toolsets=self.enabled_toolsets,
    +                disabled_toolsets=self.disabled_toolsets,
                     verbose_logging=self.verbose,
                     quiet_mode=not self.verbose,
                     ephemeral_system_prompt=self.system_prompt if self.system_prompt else None,
    ```
  - `gateway/run.py`

    ```diff
    @@ -7978,6 +7978,8 @@ async def _run_background_task(
     
                 from hermes_cli.tools_config import _get_platform_tools
                 enabled_toolsets = sorted(_get_platform_tools(user_config, platform_key))
    +            agent_cfg = user_config.get("agent") or {}
    +            disabled_toolsets = agent_cfg.get("disabled_toolsets") or None
     
                 pr = self._provider_routing
                 max_iterations = int(os.getenv("HERMES_MAX_ITERATIONS", "90"))
    ```

    ```diff
    @@ -7994,6 +7996,7 @@ def run_sync():
                         quiet_mode=True,
                         verbose_logging=False,
                         enabled_toolsets=enabled_toolsets,
    +                    disabled_toolsets=disabled_toolsets,
                         reasoning_config=reasoning_config,
                         service_tier=self._service_tier,
                         request_overrides=turn_route.get("request_overrides"),
    ```

    ```diff
    @@ -10352,6 +10355,7 @@ async def _run_process_watcher(self, watcher: dict) -> None:
             ("compression", "threshold"),
             ("compression", "target_ratio"),
             ("compression", "protect_last_n"),
    +        ("agent", "disabled_toolsets"),
         )
     
         @classmethod
    ```

    ```diff
    @@ -11135,6 +11139,8 @@ def _run_still_current() -> bool:
     
             from hermes_cli.tools_config import _get_platform_tools
             enabled_toolsets = sorted(_get_platform_tools(user_config, platform_key))
    +        agent_cfg_local = user_config.get("agent") or {}
    +        disabled_toolsets = agent_cfg_local.get("disabled_toolsets") or None
     
             display_config = user_config.get("display", {})
             if not isinstance(display_config, dict):
    ```

    ```diff
    @@ -11763,6 +11769,7 @@ def _interim_assistant_cb(text: str, *, already_streamed: bool = False) -> None:
                         quiet_mode=True,
                         verbose_logging=False,
                         enabled_toolsets=enabled_toolsets,
    +                    disabled_toolsets=disabled_toolsets,
                         ephemeral_system_prompt=combined_ephemeral or None,
                         prefill_messages=self._prefill_messages or None,
                         reasoning_config=reasoning_config,
    ```
  - `hermes_cli/config.py`

    ```diff
    @@ -457,6 +457,7 @@ def _ensure_hermes_home_managed(home: Path):
             # remains available as a tool regardless of this setting — the routing
             # only controls how inbound user images are presented.
             "image_input_mode": "auto",
    +        "disabled_toolsets": [],
         },
         
         "terminal": {
    ```
  - `model_tools.py`

    ```diff
    @@ -356,12 +356,17 @@ def _compute_tool_definitions(
                 else:
                     if not quiet_mode:
                         print(f"⚠️  Unknown toolset: {toolset_name}")
    -
    -    elif disabled_toolsets:
    +    else:
    +        # Default: start with everything
             from toolsets import get_all_toolsets
             for ts_name in get_all_toolsets():
                 tools_to_include.update(resolve_toolset(ts_name))
     
    +    # Always apply disabled toolsets as a subtraction step at the end.
    +    # This ensures that even if a composite toolset (like hermes-cli)
    +    # is enabled, any tools belonging to a disabled toolset are strictly
    +    # stripped out. See issue #17309.
    +    if disabled_toolsets:
             for toolset_name in disabled_toolsets:
                 if validate_toolset(toolset_name):
                     resolved = resolve_toolset(toolset_name)
    ```

    ```diff
    @@ -376,10 +381,6 @@ def _compute_tool_definitions(
                 else:
                     if not quiet_mode:
                         print(f"⚠️  Unknown toolset: {toolset_name}")
    -    else:
    -        from toolsets import get_all_toolsets
    -        for ts_name in get_all_toolsets():
    -            tools_to_include.update(resolve_toolset(ts_name))
     
         # Plugin-registered tools are now resolved through the normal toolset
         # path — validate_toolset() / resolve_toolset() / get_all_toolsets()
    ```
  - `scripts/release.py`

    ```diff
    @@ -48,6 +48,7 @@
         "[redacted:email]": "adamludwin",
         "[redacted:email]": "exiao",
         "[redacted:email]": "rylena",
    +    "[redacted:email]": "jatingodnani",
         "[redacted:email]": "tmimmanuel",
         "[redacted:email]": "revaraver",
         # Matrix parity salvage batch (April 2026)
    ```
- **#17343: Enforce subtractive toolset filtration in tool resolution logic**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `e5d702141`, `#18197`, `#15291`, `#17309`
- **#26691: fix(tools): refuse dispatch of tools outside the active toolset (#26568)**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `e3624e00db6ddee7b1bf4009fc19453b5317f2f2`, `#26568`

## Fix status

- Merged to main in PR #18197 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-05-01`.
- Subsystem: `agent`
