NousResearch/hermes-agent

Conflicting tool name collisions in agent routing

Cluster 40466 · agent. Data as of

Nothing open on GitHub. Fix merged in #40902 on 7 Jun 2026. Check it covers your case.

The short answer

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

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.

  • #40902 fix(memory): reject memory tools that shadow core tool names

    merged to main

    files: agent/memory_manager.py, tests/run_agent/test_memory_provider_init.py

    Show the diff
    • agent/memory_manager.py
      @@ -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:
      @@ -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
      @@ -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

2 PRs closed without merging

From the close comments: done elsewhere 1 · superseded by another PR 1

  • #40475 fix(agent): remove conflicting tool from routing table during init · done elsewhere → fe8920d, #40902, #40466
  • #40538 fix(agent): remove conflicting tool from routing table during init (#… · superseded by another PR → #40902
Closed issues · 1
  • Closed#40466[Bug]: The external tool with conflicting name still affects the tool call procedures.
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.50: 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
#40902
Closed issues
1
Attempts
2 PRs closed without merging
Fix link checked
7 Jun 2026

For your agent

The same facts, typed.