{"repo":"NousResearch/hermes-agent","public_id":"22601","display_label":"Toolset resolution bugs: composite, alias, platform defaults","summary":"These issues and PRs address multiple related bugs where composite toolsets (e.g. hermes-cli, hermes-discord), legacy aliases, and platform-native defaults are silently dropped or ignored during toolset resolution. Several entries also cover Discord and Feishu tool wiring and splitting.","subsystem":"gateway","counts":{"members":12,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":3,"merged_prs":3,"attempt_prs":6},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":22673},"resolution":{"state":"resolved","by":"pr-22673,pr-60553,pr-131906"},"freshness":{"state":"verified","last_verified_at":"2026-10-03T01:27:25+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.14727272727272725},"fix_edges":[{"issue_number":22601,"pr_number":22673,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":35527,"pr_number":60553,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":41579,"pr_number":131906,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":22601,"title":"_get_platform_tools silently drops composite toolset (e.g. hermes-cli) when saved list mixes composite + configurable — entire native toolset disappears","url":null,"state":"closed"},{"kind":"issue","number":35527,"title":"[Bug]: discord/discord_admin tools silently missing when platform_toolsets uses hermes-discord composite","url":null,"state":"closed"},{"kind":"issue","number":41579,"title":"fix: _get_platform_tools() should resolve legacy toolset aliases (hermes → hermes-cli)","url":null,"state":"closed"},{"kind":"pr","number":22606,"title":"fix(cli): expand composite toolset when mixed with configurables in platform_toolsets","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#22673","number":22673}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":22608,"title":"fix(tools): expand composite platform toolsets alongside explicit configurable keys","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#22673","number":22673},{"kind":"pr","value":"#22606","number":22606},{"kind":"pr","value":"#22606","number":22606}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":22641,"title":"fix(tools): expand composite toolsets in has_explicit_config branch","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#22673","number":22673},{"kind":"pr","value":"#22606","number":22606},{"kind":"pr","value":"#22606","number":22606}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":22673,"title":"fix(cli): expand composite toolset when mixed with configurables in platform_toolsets (salvage #22606)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/tools_config.py","tests/hermes_cli/test_tools_config.py"],"fix_hunks":[{"file":"hermes_cli/tools_config.py","hunks":["@@ -972,6 +972,38 @@ def _get_platform_tools(\n             ts for ts in toolset_names\n             if ts in configurable_keys and _toolset_allowed_for_platform(ts, platform)\n         }\n+        # Mixed config: composite toolset alongside configurables (e.g.\n+        # ``[hermes-cli, spotify]`` after enabling Spotify via ``hermes\n+        # tools``). Without expansion the composite name is silently dropped,\n+        # leaving sessions with only the configurable opt-ins and no native\n+        # tools. Mirror the else-branch's subset inference, but apply\n+        # _DEFAULT_OFF_TOOLSETS only to the implicit expansion — anything the\n+        # user explicitly listed (e.g. ``spotify``) must survive.\n+        composite_tools = set()\n+        for ts_name in toolset_names:\n+            if ts_name in configurable_keys or ts_name in plugin_ts_keys:\n+                continue\n+            if ts_name not in TOOLSETS:\n+                continue\n+            composite_tools.update(resolve_toolset(ts_name))\n+\n+        if composite_tools:\n+            expanded = set()\n+            for ts_key, _, _ in CONFIGURABLE_TOOLSETS:\n+                if not _toolset_allowed_for_platform(ts_key, platform):\n+                    continue\n+                ts_tools = set(resolve_toolset(ts_key))\n+                if ts_tools and ts_tools.issubset(composite_tools):\n+                    expanded.add(ts_key)\n+\n+            default_off = set(_DEFAULT_OFF_TOOLSETS)\n+            if platform in default_off and platform not in _TOOLSET_PLATFORM_RESTRICTIONS:\n+                default_off.remove(platform)\n+            if \"homeassistant\" in default_off and os.getenv(\"HASS_TOKEN\"):\n+                default_off.remove(\"homeassistant\")\n+            expanded -= default_off\n+\n+            enabled_toolsets |= expanded\n     else:\n         # No explicit config — fall back to resolving composite toolset names\n         # (e.g. \"hermes-cli\") to individual tool names and reverse-mapping."]},{"file":"tests/hermes_cli/test_tools_config.py","hunks":["@@ -119,6 +119,64 @@ def test_get_platform_tools_homeassistant_toolset_off_for_cron_when_hass_token_m\n     assert \"homeassistant\" not in cron_enabled\n \n \n+def test_get_platform_tools_expands_composite_when_mixed_with_configurable():\n+    \"\"\"``[hermes-cli, spotify]`` (composite + configurable) must keep the full\n+    ``hermes-cli`` toolset alongside the explicit Spotify opt-in. The\n+    has_explicit_config branch used to drop ``hermes-cli`` on the floor,\n+    leaving sessions with only ``{spotify, kanban}``.\"\"\"\n+    config = {\"platform_toolsets\": {\"cli\": [\"hermes-cli\", \"spotify\"]}}\n+\n+    enabled = _get_platform_tools(config, \"cli\", include_default_mcp_servers=False)\n+\n+    # Native tools must reappear.\n+    for ts in (\"terminal\", \"file\", \"web\", \"browser\", \"memory\", \"delegation\",\n+               \"code_execution\", \"todo\", \"session_search\", \"skills\"):\n+        assert ts in enabled, f\"{ts} should be enabled when hermes-cli is listed\"\n+    # User explicitly opted into Spotify — must survive _DEFAULT_OFF_TOOLSETS subtraction.\n+    assert \"spotify\" in enabled\n+\n+\n+def test_get_platform_tools_composite_only_unchanged():\n+    \"\"\"Composite-only config (no configurable in list) must still take the\n+    else-branch path and produce the full toolset — guards against the new\n+    code accidentally hijacking the composite-only case.\"\"\"\n+    composite_only = _get_platform_tools(\n+        {\"platform_toolsets\": {\"cli\": [\"hermes-cli\"]}},\n+        \"cli\",\n+        include_default_mcp_servers=False,\n+    )\n+    default = _get_platform_tools({}, \"cli\", include_default_mcp_servers=False)\n+\n+    assert composite_only == default\n+\n+\n+def test_get_platform_tools_configurable_only_no_expansion():\n+    \"\"\"Configurable-only list (no composite) must not pull in unrelated\n+    toolsets — guards against the expansion firing when ``composite_tools``\n+    is empty.\"\"\"\n+    config = {\"platform_toolsets\": {\"cli\": [\"terminal\", \"file\"]}}\n+\n+    enabled = _get_platform_tools(config, \"cli\", include_default_mcp_servers=False)\n+\n+    assert \"terminal\" in enabled\n+    assert \"file\" in enabled\n+    # Web shouldn't sneak in via the new expansion path.\n+    assert \"web\" not in enabled\n+\n+\n+def test_get_platform_tools_mixed_does_not_resurrect_default_off():\n+    \"\"\"Expansion must subtract _DEFAULT_OFF_TOOLSETS from the implicit\n+    pull-in. Without this, ``hermes-cli`` expansion would re-enable\n+    ``moa`` / ``rl`` / ``homeassistant`` for users who never opted in.\"\"\"\n+    config = {\"platform_toolsets\": {\"cli\": [\"hermes-cli\", \"terminal\"]}}\n+\n+    enabled = _get_platform_tools(config, \"cli\", include_default_mcp_servers=False)\n+\n+    assert \"terminal\" in enabled\n+    assert \"moa\" not in enabled\n+    assert \"rl\" not in enabled\n+\n+\n def test_get_platform_tools_preserves_explicit_empty_selection():\n     config = {\"platform_toolsets\": {\"cli\": []}}\n \n"]}]},{"kind":"pr","number":35536,"title":"fix(tools): honor explicit composite toolsets for platform-restricted defaults","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"8a726e91ba2b56974498cc731cc8b51c95af3b80","number":0},{"kind":"pr","value":"#35527","number":35527}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":38532,"title":"fix(tools): honor explicit composite toolsets for platform-restricted defaults","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":41587,"title":"fix(tools): expand legacy \"hermes\" toolset alias to prevent silent tool loss","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#131906","number":131906}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":60553,"title":"fix(tools): honor explicit platform_toolsets config for platform-native default-off toolsets","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/tools_config.py","tests/hermes_cli/test_tools_config.py"]},{"kind":"pr","number":131906,"title":"fix(tools): expand legacy \"hermes\" toolset alias to prevent silent tool loss [risk 0.30]","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/tools_config.py","tests/hermes_cli/test_tools_config.py"]}],"trust":{"default_tier":"verified","includes_suggested":false}}