{"repo":"NousResearch/hermes-agent","public_id":"19447","display_label":"Composite toolset expansion in delegate_task intersection","summary":"Members appear to center on delegate_task toolset intersection when composite toolsets (e.g. hermes-cli) are involved. One item reports this as a bug, and the others are titled as fixes that expand composite toolsets before the intersection, one of them noted as a salvage of #19455.","subsystem":null,"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":21300,"merged_at":"2026-05-07T13:41:43+00:00"},"resolution":{"state":"resolved","by":"pr-21300"},"freshness":{"state":"verified","last_verified_at":"2026-05-07T13:41:43+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.5},"fix_edges":[{"issue_number":19447,"pr_number":21300,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-07T13:41:43+00:00"}],"members":[{"kind":"issue","number":19447,"title":"Bug: delegate_task toolset intersection fails with composite toolsets (e.g. hermes-cli)","url":null,"state":"closed"},{"kind":"pr","number":19455,"title":"fix(delegate): expand composite toolsets before intersection in delegate_task","url":null,"state":"closed","close_reason":{"category":"silent_close","implication":"active"},"merged":false},{"kind":"pr","number":19468,"title":"fix(tools): expand composite toolsets in delegate intersection","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#19455","number":19455}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":21300,"title":"fix(delegate): expand composite toolsets before intersection (salvage #19455)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tests/tools/test_delegate_composite_toolsets.py","tools/delegate_tool.py"],"fix_hunks":[{"file":"tests/tools/test_delegate_composite_toolsets.py","hunks":["@@ -0,0 +1,46 @@\n+\"\"\"Tests for composite toolset expansion in delegate_task intersection.\"\"\"\n+\n+import unittest\n+from unittest.mock import patch\n+\n+from tools.delegate_tool import _expand_parent_toolsets\n+\n+\n+class TestExpandParentToolsets(unittest.TestCase):\n+    \"\"\"Verify _expand_parent_toolsets recognises individual toolsets within composites.\"\"\"\n+\n+    def test_composite_hermes_cli_expands_web(self):\n+        \"\"\"hermes-cli includes web_search/web_extract → 'web' should be in expansion.\"\"\"\n+        expanded = _expand_parent_toolsets({\"hermes-cli\"})\n+        self.assertIn(\"web\", expanded)\n+        self.assertIn(\"terminal\", expanded)\n+        self.assertIn(\"browser\", expanded)\n+        # Original composite is preserved\n+        self.assertIn(\"hermes-cli\", expanded)\n+\n+    def test_individual_toolset_unchanged(self):\n+        \"\"\"When parent already uses individual toolsets, expansion keeps them.\"\"\"\n+        expanded = _expand_parent_toolsets({\"web\", \"terminal\"})\n+        self.assertIn(\"web\", expanded)\n+        self.assertIn(\"terminal\", expanded)\n+\n+    def test_empty_parent_toolsets(self):\n+        expanded = _expand_parent_toolsets(set())\n+        self.assertEqual(expanded, set())\n+\n+    def test_unknown_toolset_passthrough(self):\n+        \"\"\"Unknown toolset names pass through without error.\"\"\"\n+        expanded = _expand_parent_toolsets({\"nonexistent-toolset-xyz\"})\n+        self.assertIn(\"nonexistent-toolset-xyz\", expanded)\n+\n+    def test_intersection_with_expanded_composite(self):\n+        \"\"\"End-to-end: requesting ['web'] from parent with ['hermes-cli'] yields ['web'].\"\"\"\n+        parent_toolsets = {\"hermes-cli\"}\n+        expanded = _expand_parent_toolsets(parent_toolsets)\n+        toolsets = [\"web\"]\n+        child_toolsets = [t for t in toolsets if t in expanded]\n+        self.assertEqual(child_toolsets, [\"web\"])\n+\n+\n+if __name__ == \"__main__\":\n+    unittest.main()"]},{"file":"tools/delegate_tool.py","hunks":["@@ -462,6 +462,37 @@ def _is_mcp_toolset_name(name: str) -> bool:\n     return bool(target and str(target).startswith(\"mcp-\"))\n \n \n+def _expand_parent_toolsets(parent_toolsets: set) -> set:\n+    \"\"\"Expand composite toolsets so individual toolset names are recognized.\n+\n+    When a parent uses a composite toolset like ``hermes-cli`` (which bundles\n+    all core tools), the child may request individual toolsets such as ``web``\n+    or ``terminal``.  A simple name-based intersection would reject them\n+    because ``\"web\" != \"hermes-cli\"``.\n+\n+    This helper collects the tool names from each parent toolset, then adds\n+    the names of any individual toolsets whose tools are a *subset* of the\n+    parent's available tools.  The original parent toolset names are preserved.\n+    \"\"\"\n+    parent_tool_names: set = set()\n+    for ts_name in parent_toolsets:\n+        ts_def = TOOLSETS.get(ts_name)\n+        if ts_def:\n+            parent_tool_names.update(ts_def.get(\"tools\", []))\n+\n+    if not parent_tool_names:\n+        return set(parent_toolsets)\n+\n+    expanded = set(parent_toolsets)\n+    for ts_name, ts_def in TOOLSETS.items():\n+        if ts_name in expanded:\n+            continue\n+        ts_tools = ts_def.get(\"tools\", [])\n+        if ts_tools and set(ts_tools).issubset(parent_tool_names):\n+            expanded.add(ts_name)\n+    return expanded\n+\n+\n def _preserve_parent_mcp_toolsets(\n     child_toolsets: List[str], parent_toolsets: set[str]\n ) -> List[str]:","@@ -907,8 +938,11 @@ def _build_child_agent(\n         parent_toolsets = set(DEFAULT_TOOLSETS)\n \n     if toolsets:\n-        # Intersect with parent — subagent must not gain tools the parent lacks\n-        child_toolsets = [t for t in toolsets if t in parent_toolsets]\n+        # Intersect with parent — subagent must not gain tools the parent lacks.\n+        # Expand composite toolsets (e.g. hermes-cli) so that individual\n+        # toolset names (e.g. web, terminal) are recognised during intersection.\n+        expanded_parent = _expand_parent_toolsets(parent_toolsets)\n+        child_toolsets = [t for t in toolsets if t in expanded_parent]\n         if _get_inherit_mcp_toolsets():\n             child_toolsets = _preserve_parent_mcp_toolsets(\n                 child_toolsets, parent_toolsets\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}