# NousResearch/hermes-agent · Conflicting tool name collisions in agent routing

## Open now

- Nothing open on GitHub.

These issues and fixes address a bug where external or memory tools with names that conflict with core tool names interfere with tool call routing and initialization. The work involves detecting and rejecting or removing such conflicting tools from the routing table.

## 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 #40902. Verify it addresses your case.
- **Recommended:** read PR #40902 (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: `resolved` · deciding PR(s): `pr-40902`. 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

- #40466: \[Bug\]: The external tool with conflicting name still affects the tool call procedures.

## Prior & competing PRs

- **#40902: fix(memory): reject memory tools that shadow core tool names**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `agent/memory_manager.py`, `tests/run_agent/test_memory_provider_init.py`
  - fix hunks:
  - `agent/memory_manager.py`

    ```diff
    @@ -281,9 +281,28 @@ def add_provider(self, provider: MemoryProvider) -> None:
     
             self._providers.append(provider)
     
    +        # Core tool names are reserved — a memory provider must never register
    +        # a tool that shadows a built-in (e.g. ``clarify``, ``delegate_task``).
    +        # Built-ins always win, so such a tool is dropped at agent init and
    +        # would otherwise linger in ``_tool_to_provider`` and hijack dispatch
    +        # (#40466). Reject it here, at the door, so it never enters the routing
    +        # table at all — matching the built-ins-always-win invariant used by
    +        # the TTS/browser/search provider registries.
    +        from toolsets import _HERMES_CORE_TOOLS
    +
    +        _core_tool_names = set(_HERMES_CORE_TOOLS)
    +
             # Index tool names → provider for routing
             for schema in provider.get_tool_schemas():
                 tool_name = schema.get("name", "")
    +            if tool_name in _core_tool_names:
    +                logger.warning(
    +                    "Memory provider '%s' tool '%s' shadows a reserved core "
    +                    "tool name; registration ignored. Core tools always win — "
    +                    "rename the provider's tool to something unique.",
    +                    provider.name, tool_name,
    +                )
    +                continue
                 if tool_name and tool_name not in self._tool_to_provider:
                     self._tool_to_provider[tool_name] = provider
                 elif tool_name in self._tool_to_provider:
    ```

    ```diff
    @@ -413,13 +432,24 @@ def sync_all(
         # -- Tools ---------------------------------------------------------------
     
         def get_all_tool_schemas(self) -> List[Dict[str, Any]]:
    -        """Collect tool schemas from all providers."""
    +        """Collect tool schemas from all providers.
    +
    +        Reserved core tool names (``clarify``, ``delegate_task``, etc.) are
    +        skipped — they are rejected from the routing table in
    +        :meth:`add_provider`, so the manager must not advertise a schema it
    +        will never route. Built-ins always win (#40466).
    +        """
    +        from toolsets import _HERMES_CORE_TOOLS
    +
    +        _core_tool_names = set(_HERMES_CORE_TOOLS)
             schemas = []
             seen = set()
             for provider in self._providers:
                 try:
                     for schema in provider.get_tool_schemas():
                         name = schema.get("name", "")
    +                    if name in _core_tool_names:
    +                        continue
                         if name and name not in seen:
                             schemas.append(schema)
                             seen.add(name)
    ```
  - `tests/run_agent/test_memory_provider_init.py`

    ```diff
    @@ -90,3 +90,45 @@ def test_aiagent_forwards_user_id_alt_to_memory_provider():
         assert provider.init_kwargs["user_id"] == "open-id"
         assert provider.init_kwargs["user_id_alt"] == "union-id"
         assert provider.init_kwargs["platform"] == "feishu"
    +
    +
    +class CoreShadowProvider:
    +    """Provider that tries to register tools shadowing built-in core tools."""
    +
    +    name = "core-shadow"
    +
    +    def get_tool_schemas(self):
    +        return [
    +            {"name": "clarify", "description": "shadows built-in clarify"},
    +            {"name": "delegate_task", "description": "shadows built-in delegate"},
    +            {"name": "honcho_search", "description": "legit memory tool"},
    +        ]
    +
    +
    +def test_core_tool_names_rejected_from_memory_routing_table():
    +    """Memory tools shadowing core tool names are rejected at registration (#40466).
    +
    +    Built-ins always win: a conflicting tool must never enter the routing
    +    table nor be advertised via get_all_tool_schemas, so it can never hijack
    +    dispatch. The non-conflicting tool is preserved.
    +    """
    +    from agent.memory_manager import MemoryManager
    +
    +    mm = MemoryManager()
    +    mm.add_provider(CoreShadowProvider())
    +
    +    # Reserved names never enter the routing table
    +    assert not mm.has_tool("clarify")
    +    assert not mm.has_tool("delegate_task")
    +    assert "clarify" not in mm._tool_to_provider
    +    assert "delegate_task" not in mm._tool_to_provider
    +
    +    # Non-conflicting tool survives
    +    assert mm.has_tool("honcho_search")
    +    assert "honcho_search" in mm._tool_to_provider
    +
    +    # Manager never advertises a schema it would refuse to route
    +    schema_names = {s.get("name") for s in mm.get_all_tool_schemas()}
    +    assert "clarify" not in schema_names
    +    assert "delegate_task" not in schema_names
    +    assert "honcho_search" in schema_names
    ```
- **#40475: fix(agent): remove conflicting tool from routing table during init**. closed without merge
  - maintainer direction: category `implemented_elsewhere`; implication `superseded`; refs `fe8920db18f2b65579eb170b654086e789669a04`, `#40902`, `#40466`
- **#40538: fix(agent): remove conflicting tool from routing table during init (#…**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `#40902`

## Fix status

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