Disabled tools still active or advertised in agent

NousResearch/hermes-agent · cluster 566 · agent

Data as of

Nothing open on GitHub. Fix merged in #18197. Check it covers your case.

The short answer

0open issues
0open PRs
6items grouped
Known?
Yes. 6 related items grouped here, 3 issues already closed.
Fix event
A fix was merged to main in PR #18197. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PR #18197. That is separate from the fix event.
Still open
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.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #18197 fix(tools): strictly subtract agent.disabled_toolsets in CLI + gateway

    merged to main

    files: cli.py, gateway/run.py, hermes_cli/config.py, model_tools.py, scripts/release.py

    Show the diff
    • cli.py
      @@ -15,7 +15,6 @@
       
       import logging
       import os
      -import re
       import shutil
       import sys
       import json
      @@ -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:
      @@ -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),
      @@ -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
      @@ -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"))
      @@ -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"),
      @@ -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
      @@ -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):
      @@ -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
      @@ -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
      @@ -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)
      @@ -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
      @@ -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)

2 PRs closed without merging

From the close comments: done elsewhere 1 · merged via another change 1

  • #17343 Enforce subtractive toolset filtration in tool resolution logic · merged via another change → e5d7021, #18197, #15291, #17309
  • #26691 fix(tools): refuse dispatch of tools outside the active toolset (#26568) · done elsewhere → e3624e0, #26568
Closed issues · 3
  • Closed#566fix: Disabled tools still advertised in execute_code and delegate_task descriptions
  • Closed#17309[Bug]: WEB tools not being disabled even after disabling.
  • Closed#26568[Bug]: Hermes Agent ignoring hot memory and using disabled tools after latest update
How this cluster was grouped
  • Link evidence (partly structural links): 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 item 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.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#18197
Closed issues
3
Attempts
2 PRs closed without merging
Fix link checked
1 May 2026

For your agent

The same facts, typed.