# NousResearch/hermes-agent · Deduplicating plugin tools against built-in toolsets

## Open now

- Nothing open on GitHub.

These issues and pull requests address duplicate entries appearing when plugin toolsets overlap with built-in toolset keys in the hermes tools output. Multiple fix attempts target the same deduplication problem from different angles.

## TL;DR

- 4 member(s): 0 open issue(s), 1 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 #15634. Verify it addresses your case.
- **Recommended:** read PR #15634 (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: `unresolved`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.50`: 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

- #13640: hermes tools duplicates built-in toolset rows for plugins attached to existing toolsets

## Prior & competing PRs

- **#15634: fix(tools): dedupe bundled plugin toolsets with built-in entries**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `hermes_cli/tools_config.py`, `tests/hermes_cli/test_tools_config.py`
  - fix hunks:
  - `hermes_cli/tools_config.py`

    ```diff
    @@ -103,13 +103,23 @@ def _get_effective_configurable_toolsets():
         """Return CONFIGURABLE_TOOLSETS + any plugin-provided toolsets.
     
         Plugin toolsets are appended at the end so they appear after the
    -    built-in toolsets in the TUI checklist.
    +    built-in toolsets in the TUI checklist. A plugin whose toolset key
    +    already appears in ``CONFIGURABLE_TOOLSETS`` is skipped — bundled
    +    plugins (e.g. ``plugins/spotify``) share their toolset key with the
    +    built-in entry, and we want the built-in label/description to win.
    +    Without the dedupe, ``hermes tools`` → "reconfigure existing" would
    +    list the same toolset twice.
         """
         result = list(CONFIGURABLE_TOOLSETS)
    +    seen = {ts_key for ts_key, _, _ in result}
         try:
             from hermes_cli.plugins import discover_plugins, get_plugin_toolsets
             discover_plugins()  # idempotent — ensures plugins are loaded
    -        result.extend(get_plugin_toolsets())
    +        for entry in get_plugin_toolsets():
    +            if entry[0] in seen:
    +                continue
    +            seen.add(entry[0])
    +            result.append(entry)
         except Exception:
             pass
         return result
    ```
  - `tests/hermes_cli/test_tools_config.py`

    ```diff
    @@ -766,3 +766,24 @@ def test_get_platform_tools_feishu_tools_not_on_other_platforms():
             enabled = _get_platform_tools({}, plat)
             assert "feishu_doc" not in enabled, f"feishu_doc leaked onto {plat}"
             assert "feishu_drive" not in enabled, f"feishu_drive leaked onto {plat}"
    +
    +
    +def test_get_effective_configurable_toolsets_dedupes_bundled_plugins():
    +    """Bundled plugins (plugins/spotify) share their toolset key with the
    +    built-in CONFIGURABLE_TOOLSETS entry. The effective list must not list
    +    them twice — otherwise `hermes tools` → "reconfigure existing" shows
    +    the same toolset two rows in a row.
    +    """
    +    from hermes_cli.tools_config import _get_effective_configurable_toolsets
    +
    +    all_ts = _get_effective_configurable_toolsets()
    +    keys = [ts_key for ts_key, _, _ in all_ts]
    +    assert len(keys) == len(set(keys)), (
    +        f"duplicate toolset keys in effective list: "
    +        f"{[k for k in keys if keys.count(k) > 1]}"
    +    )
    +    # Spotify specifically — the bug that motivated the dedupe.
    +    spotify_rows = [t for t in all_ts if t[0] == "spotify"]
    +    assert len(spotify_rows) == 1, spotify_rows
    +    # Built-in label wins over the plugin label.
    +    assert spotify_rows[0][1] == "🎵 Spotify"
    ```
- **#13804: fix(cli): deduplicate plugin toolsets against built-in keys in hermes tools (Closes #13640)**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `ac05daa1890279ad5e6b50ea9944f6f337fc88d3`, `#15634`
- **#14127: fix(tools): dedupe plugin-configurable toolsets**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `ac05daa18`, `#15634`, `#13804`

## Fix status

- Merged to main in PR #15634 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-04-25`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
- Subsystem: `skills`
