{"repo":"NousResearch/hermes-agent","public_id":"40466","display_label":"Conflicting tool name collisions in agent routing","summary":"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.","subsystem":"agent","counts":{"members":4,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":40902,"merged_at":"2026-06-07T01:44:09+00:00"},"resolution":{"state":"resolved","by":"pr-40902"},"freshness":{"state":"verified","last_verified_at":"2026-06-07T01:44:09+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":40466,"pr_number":40902,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-06-07T01:44:09+00:00"}],"members":[{"kind":"issue","number":40466,"title":"[Bug]: The external tool with conflicting name still affects the tool call procedures.","url":null,"state":"closed"},{"kind":"pr","number":40475,"title":"fix(agent): remove conflicting tool from routing table during init","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"fe8920db18f2b65579eb170b654086e789669a04","number":0},{"kind":"pr","value":"#40902","number":40902},{"kind":"pr","value":"#40466","number":40466}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":40538,"title":"fix(agent): remove conflicting tool from routing table during init (#…","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#40902","number":40902}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":40902,"title":"fix(memory): reject memory tools that shadow core tool names","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/memory_manager.py","tests/run_agent/test_memory_provider_init.py"],"fix_hunks":[{"file":"agent/memory_manager.py","hunks":["@@ -281,9 +281,28 @@ def add_provider(self, provider: MemoryProvider) -> None:\n \n         self._providers.append(provider)\n \n+        # Core tool names are reserved — a memory provider must never register\n+        # a tool that shadows a built-in (e.g. ``clarify``, ``delegate_task``).\n+        # Built-ins always win, so such a tool is dropped at agent init and\n+        # would otherwise linger in ``_tool_to_provider`` and hijack dispatch\n+        # (#40466). Reject it here, at the door, so it never enters the routing\n+        # table at all — matching the built-ins-always-win invariant used by\n+        # the TTS/browser/search provider registries.\n+        from toolsets import _HERMES_CORE_TOOLS\n+\n+        _core_tool_names = set(_HERMES_CORE_TOOLS)\n+\n         # Index tool names → provider for routing\n         for schema in provider.get_tool_schemas():\n             tool_name = schema.get(\"name\", \"\")\n+            if tool_name in _core_tool_names:\n+                logger.warning(\n+                    \"Memory provider '%s' tool '%s' shadows a reserved core \"\n+                    \"tool name; registration ignored. Core tools always win — \"\n+                    \"rename the provider's tool to something unique.\",\n+                    provider.name, tool_name,\n+                )\n+                continue\n             if tool_name and tool_name not in self._tool_to_provider:\n                 self._tool_to_provider[tool_name] = provider\n             elif tool_name in self._tool_to_provider:","@@ -413,13 +432,24 @@ def sync_all(\n     # -- Tools ---------------------------------------------------------------\n \n     def get_all_tool_schemas(self) -> List[Dict[str, Any]]:\n-        \"\"\"Collect tool schemas from all providers.\"\"\"\n+        \"\"\"Collect tool schemas from all providers.\n+\n+        Reserved core tool names (``clarify``, ``delegate_task``, etc.) are\n+        skipped — they are rejected from the routing table in\n+        :meth:`add_provider`, so the manager must not advertise a schema it\n+        will never route. Built-ins always win (#40466).\n+        \"\"\"\n+        from toolsets import _HERMES_CORE_TOOLS\n+\n+        _core_tool_names = set(_HERMES_CORE_TOOLS)\n         schemas = []\n         seen = set()\n         for provider in self._providers:\n             try:\n                 for schema in provider.get_tool_schemas():\n                     name = schema.get(\"name\", \"\")\n+                    if name in _core_tool_names:\n+                        continue\n                     if name and name not in seen:\n                         schemas.append(schema)\n                         seen.add(name)"]},{"file":"tests/run_agent/test_memory_provider_init.py","hunks":["@@ -90,3 +90,45 @@ def test_aiagent_forwards_user_id_alt_to_memory_provider():\n     assert provider.init_kwargs[\"user_id\"] == \"open-id\"\n     assert provider.init_kwargs[\"user_id_alt\"] == \"union-id\"\n     assert provider.init_kwargs[\"platform\"] == \"feishu\"\n+\n+\n+class CoreShadowProvider:\n+    \"\"\"Provider that tries to register tools shadowing built-in core tools.\"\"\"\n+\n+    name = \"core-shadow\"\n+\n+    def get_tool_schemas(self):\n+        return [\n+            {\"name\": \"clarify\", \"description\": \"shadows built-in clarify\"},\n+            {\"name\": \"delegate_task\", \"description\": \"shadows built-in delegate\"},\n+            {\"name\": \"honcho_search\", \"description\": \"legit memory tool\"},\n+        ]\n+\n+\n+def test_core_tool_names_rejected_from_memory_routing_table():\n+    \"\"\"Memory tools shadowing core tool names are rejected at registration (#40466).\n+\n+    Built-ins always win: a conflicting tool must never enter the routing\n+    table nor be advertised via get_all_tool_schemas, so it can never hijack\n+    dispatch. The non-conflicting tool is preserved.\n+    \"\"\"\n+    from agent.memory_manager import MemoryManager\n+\n+    mm = MemoryManager()\n+    mm.add_provider(CoreShadowProvider())\n+\n+    # Reserved names never enter the routing table\n+    assert not mm.has_tool(\"clarify\")\n+    assert not mm.has_tool(\"delegate_task\")\n+    assert \"clarify\" not in mm._tool_to_provider\n+    assert \"delegate_task\" not in mm._tool_to_provider\n+\n+    # Non-conflicting tool survives\n+    assert mm.has_tool(\"honcho_search\")\n+    assert \"honcho_search\" in mm._tool_to_provider\n+\n+    # Manager never advertises a schema it would refuse to route\n+    schema_names = {s.get(\"name\") for s in mm.get_all_tool_schemas()}\n+    assert \"clarify\" not in schema_names\n+    assert \"delegate_task\" not in schema_names\n+    assert \"honcho_search\" in schema_names\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}