{"repo":"NousResearch/hermes-agent","public_id":"609","display_label":"delegate_task per-call model and provider overrides","summary":"These issues and PRs concern adding per-call model, provider, and profile override parameters to the delegate_task tool, along with fixes for bugs where delegation ignores configuration and subagents inherit the parent model or incorrect credentials instead of the intended settings.","subsystem":"agent","counts":{"members":105,"open_issues":4,"open_issues_distinct":4,"open_prs":1,"closed_issues":56,"merged_prs":5,"attempt_prs":40},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":7360},"resolution":{"state":"partially_resolved","by":"pr-16894,pr-41730"},"freshness":{"state":"verified","last_verified_at":"2026-07-08T00:17:39+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.02545787545787549},"fix_edges":[{"issue_number":6825,"pr_number":7360,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":6880,"pr_number":7360,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":7833,"pr_number":41730,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":10653,"pr_number":19744,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":16816,"pr_number":16894,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":18946,"pr_number":60577,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":51435,"pr_number":60577,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":55380,"pr_number":60577,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":57930,"pr_number":60577,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false}],"members":[{"kind":"issue","number":609,"title":"Allow configurable subagent model in config.yaml","url":null,"state":"closed"},{"kind":"issue","number":3719,"title":"Feature: Per-call model/provider override on delegate_task","url":null,"state":"closed"},{"kind":"issue","number":4928,"title":"Feature: add named delegation capability profiles for subagents","url":null,"state":"open"},{"kind":"issue","number":5012,"title":"[Feature]: Allow delegate_task to accept provider/model parameters","url":null,"state":"closed"},{"kind":"issue","number":6306,"title":"Feature: Allow per-task model selection in delegate_task","url":null,"state":"closed"},{"kind":"issue","number":6825,"title":"Bug: delegation.base_url correctly resolved but overwritten before first API call","url":null,"state":"closed"},{"kind":"issue","number":7833,"title":"delegate_task can overwrite delegated custom endpoint with parent custom credential pool","url":null,"state":"closed"},{"kind":"issue","number":9712,"title":"delegate_task(acp_command=...) silently ignored under Anthropic parent","url":null,"state":"closed"},{"kind":"issue","number":10653,"title":"[Bug]: delegation config doesn`t work","url":null,"state":"closed"},{"kind":"issue","number":10995,"title":"feat: delegate_task should support per-task model selection","url":null,"state":"closed"},{"kind":"issue","number":11999,"title":"delegate_task ignores subagent model config — always inherits parent model","url":null,"state":"closed"},{"kind":"issue","number":14974,"title":"[Feature]: delegation-per-task-endpoint-overrides","url":null,"state":"closed"},{"kind":"issue","number":15789,"title":"feat: per-task model/provider overrides in delegate_task","url":null,"state":"closed"},{"kind":"issue","number":16816,"title":"[Bug]: Add delegation.acp_command: none config option","url":null,"state":"closed"},{"kind":"issue","number":17685,"title":"delegate_task: per-task model override is silently ignored — children always use parent's model","url":null,"state":"closed"},{"kind":"issue","number":17732,"title":"Feature: per-call model/provider override in delegate_task","url":null,"state":"closed"},{"kind":"issue","number":18591,"title":"Feature: Per-task model override for delegate_task subagents","url":null,"state":"closed"},{"kind":"issue","number":19567,"title":"TUI delegation subagent incorrectly routed to copilot-acp instead of configured deepseek provider","url":null,"state":"closed"},{"kind":"issue","number":23467,"title":"delegate_task model parameter silently discarded — subagents always inherit parent model","url":null,"state":"closed"},{"kind":"issue","number":24263,"title":"Feature: Per-sub-agent provider/model selection in delegate_task","url":null,"state":"closed"},{"kind":"issue","number":26482,"title":"delegation model override ignored — subagents always inherit parent provider/model","url":null,"state":"closed"},{"kind":"issue","number":27294,"title":"[Bug] delegate_task ignores delegation.base_url — subagent always inherits parent model","url":null,"state":"closed"},{"kind":"issue","number":27295,"title":"[Bug] delegate_task ignores delegation.base_url — subagent always inherits parent model","url":null,"state":"closed"},{"kind":"issue","number":27296,"title":"[Bug] delegate_task ignores delegation.base_url — subagent always inherits parent model","url":null,"state":"closed"},{"kind":"issue","number":31155,"title":"delegation.model override ignored — subagents always inherit parent model","url":null,"state":"closed"},{"kind":"issue","number":32711,"title":"delegate_task() lacks model and provider parameters — prevents per-call subagent routing","url":null,"state":"closed"},{"kind":"issue","number":34318,"title":"delegate_task sends API key to wrong provider endpoint (OpenRouter key → OpenAI)","url":null,"state":"closed"},{"kind":"issue","number":34334,"title":"Optimization: Correct API key routing for delegate_task","url":null,"state":"closed"},{"kind":"issue","number":34462,"title":"[TASK] Implement Per-Call Provider and Model Overrides for delegate_task","url":null,"state":"closed"},{"kind":"issue","number":34489,"title":"Per-Task Model Override in delegate_task","url":null,"state":"closed"},{"kind":"issue","number":34727,"title":"[FEATURE] Task Delegation + Intelligent Model Selection (Phases 7 & 3)","url":null,"state":"closed"},{"kind":"issue","number":34764,"title":"feat(delegate): per-task model/provider override in delegate_task tasks array","url":null,"state":"closed"},{"kind":"issue","number":35409,"title":"Add profile/model override parameter to delegate_task","url":null,"state":"closed"},{"kind":"issue","number":35437,"title":"feat: add model/provider/base_url/api_format parameters to delegate_task (consolidating #5012, #10995, #17685, #23467, #32711)","url":null,"state":"closed"},{"kind":"issue","number":37574,"title":"feat: add model/provider override param to delegate_task tool","url":null,"state":"closed"},{"kind":"issue","number":41814,"title":"Feature: Per-task model override for delegate_task","url":null,"state":"closed"},{"kind":"issue","number":41889,"title":"Feature Request: Cross-profile subagent support in delegate_task","url":null,"state":"open"},{"kind":"issue","number":44900,"title":"feat(delegation): add model override to delegate_task","url":null,"state":"closed"},{"kind":"issue","number":46295,"title":"Feature Request: Add subagent configuration to Model page with provider selection","url":null,"state":"closed"},{"kind":"issue","number":47014,"title":"Feature Request: Per-call model override for delegate_task","url":null,"state":"closed"},{"kind":"issue","number":47295,"title":"feat: per-call model routing in delegate_task (multi-model delegation)","url":null,"state":"closed"},{"kind":"issue","number":49332,"title":"delegate_task model override ignored — subagents use wrong model, consume unauthorized credits","url":null,"state":"closed"},{"kind":"issue","number":50202,"title":"[Feature]: per-call model/provider override for delegate_task (multi-model subagent orchestration)","url":null,"state":"closed"},{"kind":"issue","number":51303,"title":"Custom providers not recognized by delegate_task delegation system  ","url":null,"state":"closed"},{"kind":"issue","number":51401,"title":"Feature Request: Per-Task Model Override in delegate_task","url":null,"state":"closed"},{"kind":"issue","number":51540,"title":"delegation.model ignored for opencode-go provider","url":null,"state":"open"},{"kind":"issue","number":52331,"title":"[Feature]: Per-call model routing for delegate_task subagents","url":null,"state":"closed"},{"kind":"issue","number":53064,"title":"delegation.provider=custom:* resolves wrong base_url (inherits parent provider base_url)","url":null,"state":"closed"},{"kind":"issue","number":53846,"title":"delegate_task model parameter not honored — child agents always inherit parent session model","url":null,"state":"closed"},{"kind":"issue","number":55380,"title":"Bug: delegate_task ignores delegation config — hardcoded to xiaomi","url":null,"state":"closed"},{"kind":"issue","number":56891,"title":"Feature: Expose model parameter on delegate_task for per-call model selection","url":null,"state":"closed"},{"kind":"issue","number":58298,"title":"Subagent delegation ignores config, always uses credential-pool model (glm-4-flash)","url":null,"state":"closed"},{"kind":"issue","number":58731,"title":"Feature Request: Per-subagent model override in delegate_task","url":null,"state":"closed"},{"kind":"issue","number":61195,"title":"[Bug]: delegation.base_url correctly resolved through init_agent() but subagent's actual API call still routes to OpenRouter → 401","url":null,"state":"closed"},{"kind":"issue","number":61622,"title":"feat: support per-subagent model selection in delegate_task","url":null,"state":"closed"},{"kind":"issue","number":62443,"title":"delegate_task subagent always resolves to Groq/llama-3.3-70b-versatile (401) regardless of delegation config","url":null,"state":"closed"},{"kind":"issue","number":62731,"title":"Feature Request: Allow delegate_task to use different models per subtask","url":null,"state":"closed"},{"kind":"issue","number":66536,"title":"[Feature]: Per-call model/provider override for delegate_task","url":null,"state":"closed"},{"kind":"issue","number":66819,"title":"Feature request: Role-based subagents — inherit profile identity (SOUL.md + skills + tools)","url":null,"state":"closed"},{"kind":"issue","number":67699,"title":"feature request: delegate_task should pass env vars or accept a profile parameter","url":null,"state":"open"},{"kind":"pr","number":791,"title":"feat: allow configurable subagent model in config.yaml (#609)","url":null,"state":"closed","close_reason":{"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":3734,"title":"feat: per-call model/provider override on delegate_task","url":null,"state":"closed","close_reason":{"category":"silent_close","implication":"active"},"merged":false},{"kind":"pr","number":4929,"title":"feat(delegation): add named subagent capability profiles","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"ba0bc01d1f","number":0},{"kind":"pr","value":"#5583","number":5583},{"kind":"pr","value":"#44412","number":44412},{"kind":"pr","value":"#64213","number":64213},{"kind":"pr","value":"#4928","number":4928}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":6933,"title":"fix(delegate): sync self.base_url with client_kwargs after credential resolution","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#7360","number":7360}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":7360,"title":"fix(delegate): salvage delegate PRs #6933 #6881","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["run_agent.py","tests/run_agent/test_agent_guardrails.py","tests/tools/test_delegate.py","tools/delegate_tool.py"]},{"kind":"pr","number":9255,"title":"feat(delegate): add task-tier profiles with per-task reasoning_effort and pool validation","url":null,"state":"closed","close_reason":{"category":"stale_abandoned","implication":"active"},"merged":false},{"kind":"pr","number":9737,"title":"feat(delegate): automatic tier routing + task-tier profiles with per-task reasoning_effort","url":null,"state":"closed","close_reason":{"category":"stale_abandoned","implication":"active"},"merged":false},{"kind":"pr","number":10996,"title":"feat(tools): add model parameter to delegate_task for per-task model selection","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#9737","number":9737}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":12053,"title":"fix(delegate): read fresh config.yaml so delegation.model edits take effect (#11999)","url":null,"state":"closed","close_reason":{"category":"stale_abandoned","implication":"active"},"merged":false},{"kind":"pr","number":12105,"title":"fix(delegation): _load_config falls through to hermes_cli config when CLI_CONFIG has no delegation overrides (#11999)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#11999","number":11999}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":16329,"title":"fix(delegation): honor delegated provider routing for subagents","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#19744","number":19744}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":16503,"title":"fix(delegate): detect /anthropic URL suffix for proxy endpoints (#7833)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#13567","number":13567}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":16839,"title":"fix(delegate): clear acp_command when override_provider is set","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#16894","number":16894}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":16894,"title":"fix(delegate): clear acp_command when override_provider is set","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tools/delegate_tool.py"]},{"kind":"pr","number":17581,"title":"Fix delegate_task model/provider precedence and expose per-task overrides","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"a6f08ff0c","number":0},{"kind":"pr","value":"#10995","number":10995}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":17718,"title":"fix: honor per-task model override in delegate_task","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":19744,"title":"fix(delegation): clear parent OpenRouter filters when delegation.provider override is set","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tests/tools/test_delegate.py","tools/delegate_tool.py"],"fix_hunks":[{"file":"tests/tools/test_delegate.py","hunks":["@@ -980,6 +980,48 @@ def test_cross_provider_delegation(self, mock_creds, mock_cfg):\n             self.assertNotEqual(kwargs[\"base_url\"], parent.base_url)\n             self.assertNotEqual(kwargs[\"api_key\"], parent.api_key)\n \n+    @patch(\"tools.delegate_tool._load_config\")\n+    @patch(\"tools.delegate_tool._resolve_delegation_credentials\")\n+    def test_provider_override_clears_parent_openrouter_filters(\n+        self, mock_creds, mock_cfg\n+    ):\n+        \"\"\"Delegated provider should not inherit parent provider-preference filters.\"\"\"\n+        mock_cfg.return_value = {\n+            \"max_iterations\": 45,\n+            \"model\": \"google/gemini-3-flash-preview\",\n+            \"provider\": \"openrouter\",\n+        }\n+        mock_creds.return_value = {\n+            \"model\": \"google/gemini-3-flash-preview\",\n+            \"provider\": \"openrouter\",\n+            \"base_url\": \"https://openrouter.ai/api/v1\",\n+            \"api_key\": \"sk-or-key\",\n+            \"api_mode\": \"chat_completions\",\n+        }\n+        parent = _make_mock_parent(depth=0)\n+        parent.providers_allowed = [\"anthropic/claude-3.5-sonnet\"]\n+        parent.providers_ignored = [\"openai/gpt-4o-mini\"]\n+        parent.providers_order = [\"google/gemini-2.5-pro\"]\n+        parent.provider_sort = \"price\"\n+\n+        with patch(\"run_agent.AIAgent\") as MockAgent:\n+            mock_child = MagicMock()\n+            mock_child.run_conversation.return_value = {\n+                \"final_response\": \"done\",\n+                \"completed\": True,\n+                \"api_calls\": 1,\n+            }\n+            MockAgent.return_value = mock_child\n+\n+            delegate_task(goal=\"Cross-provider test\", parent_agent=parent)\n+\n+            _, kwargs = MockAgent.call_args\n+            self.assertEqual(kwargs[\"provider\"], \"openrouter\")\n+            self.assertIsNone(kwargs[\"providers_allowed\"])\n+            self.assertIsNone(kwargs[\"providers_ignored\"])\n+            self.assertIsNone(kwargs[\"providers_order\"])\n+            self.assertIsNone(kwargs[\"provider_sort\"])\n+\n     @patch(\"tools.delegate_tool._load_config\")\n     @patch(\"tools.delegate_tool._resolve_delegation_credentials\")\n     def test_direct_endpoint_credentials_reach_child_agent(self, mock_creds, mock_cfg):"]},{"file":"tools/delegate_tool.py","hunks":["@@ -1032,6 +1032,23 @@ def _child_thinking(text: str) -> None:\n     # fallback_model parameter (which handles both list and dict forms).\n     parent_fallback = getattr(parent_agent, \"_fallback_chain\", None) or None\n \n+    # Inherit the parent's OpenRouter provider-preference filters by default\n+    # (so subagents routed to the same provider honour the same routing\n+    # constraints).  BUT: when `delegation.provider` is set the user is\n+    # explicitly asking the child to run on a different provider, and\n+    # parent-level OpenRouter filters (e.g. `only=[\"Anthropic\"]`) would\n+    # silently force the child back onto the parent's provider. Clear the\n+    # filters in that case so the delegated provider is honoured.\n+    child_providers_allowed = getattr(parent_agent, \"providers_allowed\", None)\n+    child_providers_ignored = getattr(parent_agent, \"providers_ignored\", None)\n+    child_providers_order = getattr(parent_agent, \"providers_order\", None)\n+    child_provider_sort = getattr(parent_agent, \"provider_sort\", None)\n+    if override_provider:\n+        child_providers_allowed = None\n+        child_providers_ignored = None\n+        child_providers_order = None\n+        child_provider_sort = None\n+\n     child = AIAgent(\n         base_url=effective_base_url,\n         api_key=[redacted:secret-assignment]","@@ -1056,10 +1073,10 @@ def _child_thinking(text: str) -> None:\n         thinking_callback=child_thinking_cb,\n         session_db=getattr(parent_agent, \"_session_db\", None),\n         parent_session_id=getattr(parent_agent, \"session_id\", None),\n-        providers_allowed=parent_agent.providers_allowed,\n-        providers_ignored=parent_agent.providers_ignored,\n-        providers_order=parent_agent.providers_order,\n-        provider_sort=parent_agent.provider_sort,\n+        providers_allowed=child_providers_allowed,\n+        providers_ignored=child_providers_ignored,\n+        providers_order=child_providers_order,\n+        provider_sort=child_provider_sort,\n         tool_progress_callback=child_progress_cb,\n         iteration_budget=None,  # fresh budget per subagent\n     )\n"]}]},{"kind":"pr","number":23769,"title":"fix(delegation): accept per-call model override on delegate_task","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#23649","number":23649}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":25026,"title":"feat(delegate): per-task model and provider override for delegate_task subagents","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#3172","number":3172},{"kind":"pr","value":"#16163","number":16163},{"kind":"pr","value":"#17756","number":17756},{"kind":"pr","value":"#15789","number":15789},{"kind":"pr","value":"#14974","number":14974}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":26486,"title":"fix(delegation): honor provider overrides for subagents","url":null,"state":"closed","close_reason":{"category":"silent_close","implication":"active"},"merged":false},{"kind":"pr","number":34369,"title":"fix(delegate): detect credential/endpoint mismatch before sending request (#34318, #34334)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#34318","number":34318},{"kind":"pr","value":"#34334","number":34334}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":34773,"title":"feat(delegate): per-task model/provider override in delegate_task tasks array","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#14974","number":14974},{"kind":"pr","value":"#17756","number":17756},{"kind":"pr","value":"#20000","number":20000},{"kind":"pr","value":"#23266","number":23266},{"kind":"pr","value":"#32555","number":32555},{"kind":"pr","value":"#34752","number":34752},{"kind":"pr","value":"#34764","number":34764},{"kind":"pr","value":"#15789","number":15789}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":34776,"title":"feat(phase34754): Centralized Model Complexity Configuration","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#34752","number":34752},{"kind":"pr","value":"#34727","number":34727},{"kind":"pr","value":"#10995","number":10995},{"kind":"pr","value":"#13663","number":13663}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":35033,"title":"feat(delegate): per-task model/provider overrides (+ fix two latent delegation bugs)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#14974","number":14974},{"kind":"pr","value":"#25530","number":25530},{"kind":"pr","value":"#23649","number":23649},{"kind":"pr","value":"#17756","number":17756}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":36790,"title":"feat(delegate): add per-task model/provider routing","url":null,"state":"closed","close_reason":{"category":"wontfix_bydesign","implication":"wontfix"},"merged":false},{"kind":"pr","number":37578,"title":"feat: add model/provider override param to delegate_task tool","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":39282,"title":"feat(delegate_task): add per-task model/provider override","url":null,"state":"closed","close_reason":{"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":41730,"title":"fix(delegate): resolve custom-endpoint subagent pools by endpoint identity","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/credential_pool.py","tests/tools/test_delegate.py","tools/delegate_tool.py"],"fix_hunks":[{"file":"agent/credential_pool.py","hunks":["@@ -374,7 +374,7 @@ def _iter_custom_providers(config: Optional[dict] = None):\n         yield _normalize_custom_pool_name(name), entry\n \n \n-def get_custom_provider_pool_key(base_url: str, provider_name: Optional[str] = None) -> Optional[str]:\n+def get_custom_provider_pool_key(base_url: Optional[str], provider_name: Optional[str] = None) -> Optional[str]:\n     \"\"\"Look up the custom_providers list in config.yaml and return 'custom:<name>' for a matching base_url.\n \n     When provider_name is given, prefer matching by name first (solving the case where"]},{"file":"tests/tools/test_delegate.py","hunks":["@@ -1518,6 +1518,73 @@ def test_different_provider_load_failure_returns_none(self):\n \n         self.assertIsNone(result)\n \n+    # --- Custom-endpoint identity resolution (issue #7833) ---\n+\n+    def test_custom_different_endpoint_does_not_inherit_parent_pool(self):\n+        \"\"\"A child on custom endpoint B must not inherit the parent's custom\n+        endpoint A pool just because both normalize to provider='custom'.\"\"\"\n+        parent = _make_mock_parent()\n+        parent.provider = \"custom\"\n+        parent.base_url = \"https://endpoint-a.example.com/v1\"\n+        parent._credential_pool = [redacted:secret-assignment]\"parent_custom_a_pool\")\n+\n+        child_pool = MagicMock(name=\"endpoint_b_pool\")\n+        child_pool.has_credentials.return_value = True\n+\n+        def fake_key(base_url, provider_name=None):\n+            return {\n+                \"https://endpoint-a.example.com/v1\": \"custom:endpoint-a\",\n+                \"https://endpoint-b.example.com/v1\": \"custom:endpoint-b\",\n+            }.get(base_url)\n+\n+        with patch(\"agent.credential_pool.get_custom_provider_pool_key\", side_effect=fake_key), \\\n+             patch(\"agent.credential_pool.load_pool\", return_value=child_pool) as load_mock:\n+            result = _resolve_child_credential_pool(\n+                \"custom\", parent, \"https://endpoint-b.example.com/v1\"\n+            )\n+\n+        # Loaded the child's OWN endpoint pool, not the parent's.\n+        load_mock.assert_called_once_with(\"custom:endpoint-b\")\n+        self.assertIs(result, child_pool)\n+        self.assertIsNot(result, parent._credential_pool)\n+\n+    def test_custom_same_endpoint_shares_parent_pool(self):\n+        \"\"\"A child on the SAME custom endpoint as the parent reuses the parent's\n+        pool so rotation/cooldown state stays synchronized.\"\"\"\n+        parent = _make_mock_parent()\n+        parent.provider = \"custom\"\n+        parent.base_url = \"https://endpoint-a.example.com/v1\"\n+        parent._credential_pool = [redacted:secret-assignment]\"parent_custom_a_pool\")\n+\n+        with patch(\n+            \"agent.credential_pool.get_custom_provider_pool_key\",\n+            return_value=\"custom:endpoint-a\",\n+        ):\n+            result = _resolve_child_credential_pool(\n+                \"custom\", parent, \"https://endpoint-a.example.com/v1\"\n+            )\n+\n+        self.assertIs(result, parent._credential_pool)\n+\n+    def test_custom_unregistered_endpoint_returns_none(self):\n+        \"\"\"A raw delegation.base_url with no matching custom_providers entry\n+        must NOT inherit the parent's pool — return None so the child keeps its\n+        fixed delegated credential.\"\"\"\n+        parent = _make_mock_parent()\n+        parent.provider = \"custom\"\n+        parent.base_url = \"https://endpoint-a.example.com/v1\"\n+        parent._credential_pool = [redacted:secret-assignment]\"parent_custom_a_pool\")\n+\n+        with patch(\n+            \"agent.credential_pool.get_custom_provider_pool_key\",\n+            return_value=None,\n+        ):\n+            result = _resolve_child_credential_pool(\n+                \"custom\", parent, \"https://raw-unregistered.example.com/v1\"\n+            )\n+\n+        self.assertIsNone(result)\n+\n     def test_build_child_agent_assigns_parent_pool_when_shared(self):\n         parent = _make_mock_parent()\n         mock_pool = MagicMock()"]},{"file":"tools/delegate_tool.py","hunks":["@@ -1184,7 +1184,9 @@ def _child_thinking(text: str) -> None:\n \n     # Share a credential pool with the child when possible so subagents can\n     # rotate credentials on rate limits instead of getting pinned to one key.\n-    child_pool = _resolve_child_credential_pool(effective_provider, parent_agent)\n+    child_pool = _resolve_child_credential_pool(\n+        effective_provider, parent_agent, effective_base_url\n+    )\n     if child_pool is not None:\n         child._credential_pool = [redacted:secret-assignment]\n ","@@ -2368,7 +2370,11 @@ def delegate_task(\n     )\n \n \n-def _resolve_child_credential_pool(effective_provider: Optional[str], parent_agent):\n+def _resolve_child_credential_pool(\n+    effective_provider: Optional[str],\n+    parent_agent,\n+    effective_base_url: Optional[str] = None,\n+):\n     \"\"\"Resolve a credential pool for the child agent.\n \n     Rules:","@@ -2377,12 +2383,60 @@ def _resolve_child_credential_pool(effective_provider: Optional[str], parent_age\n     2. Different provider -> try to load that provider's own pool.\n     3. No pool available -> return None and let the child keep the inherited\n        fixed credential behavior.\n+\n+    Custom endpoints are a special case: every direct ``delegation.base_url``\n+    runtime collapses to ``provider=\"custom\"``, so bare provider equality would\n+    treat two *different* custom endpoints as interchangeable and let the child\n+    inherit the parent's pool. Leasing from that pool then overwrites the\n+    child's delegated ``base_url`` with the parent's endpoint (issue #7833).\n+    We therefore resolve custom runtimes by endpoint identity (the\n+    ``custom:<name>`` pool key derived from the base_url) and only share the\n+    parent's pool when both resolve to the *same* custom endpoint.\n     \"\"\"\n     if not effective_provider:\n         return getattr(parent_agent, \"_credential_pool\", None)\n \n     parent_provider = getattr(parent_agent, \"provider\", None) or \"\"\n     parent_pool = getattr(parent_agent, \"_credential_pool\", None)\n+\n+    # Custom endpoints: distinguish by endpoint identity, not the bare \"custom\"\n+    # provider string. Two custom runtimes are only interchangeable when they\n+    # resolve to the same custom:<name> pool key.\n+    if effective_provider == \"custom\":\n+        try:\n+            from agent.credential_pool import get_custom_provider_pool_key, load_pool\n+\n+            child_key = get_custom_provider_pool_key(effective_base_url)\n+            if child_key is None:\n+                # Unregistered endpoint (raw delegation.base_url with no\n+                # matching custom_providers entry) -> no shared pool exists.\n+                # Keep the child's fixed delegated credential rather than\n+                # risk inheriting the parent's custom endpoint.\n+                return None\n+\n+            # Reuse the parent's pool only when it is the same custom endpoint.\n+            parent_key = get_custom_provider_pool_key(\n+                getattr(parent_agent, \"base_url\", None)\n+            )\n+            if (\n+                parent_pool is not None\n+                and parent_provider == \"custom\"\n+                and parent_key is not None\n+                and parent_key == child_key\n+            ):\n+                return parent_pool\n+\n+            pool = load_pool(child_key)\n+            if pool is not None and pool.has_credentials():\n+                return pool\n+        except Exception as exc:\n+            logger.debug(\n+                \"Could not resolve custom credential pool for child endpoint '%s': %s\",\n+                effective_base_url,\n+                exc,\n+            )\n+        return None\n+\n     if parent_pool is not None and effective_provider == parent_provider:\n         return parent_pool\n \n"]}]},{"kind":"pr","number":41826,"title":"feat(delegation): add per-task model override for delegate_task","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#6771","number":6771},{"kind":"pr","value":"#14974","number":14974},{"kind":"pr","value":"#15789","number":15789},{"kind":"pr","value":"#6771","number":6771},{"kind":"pr","value":"#12794","number":12794},{"kind":"pr","value":"#16163","number":16163}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":41865,"title":"fix(delegate): honor model parameter end-to-end (fixes #41821, closes #41814)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#41814","number":41814},{"kind":"pr","value":"#53846","number":53846}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":43134,"title":"feat(delegate_tool): per-task model/provider override","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#43185","number":43185}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":43185,"title":"fix(delegate_tool): address PR #43134 multi-provider review feedback","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":50015,"title":"feat(delegate_task): per-call model/provider overrides","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#6771","number":6771},{"kind":"pr","value":"#6771","number":6771},{"kind":"pr","value":"#15789","number":15789},{"kind":"pr","value":"#47014","number":47014},{"kind":"pr","value":"#47295","number":47295},{"kind":"pr","value":"#46880","number":46880},{"kind":"pr","value":"#32711","number":32711},{"kind":"pr","value":"#6306","number":6306},{"kind":"pr","value":"#23769","number":23769},{"kind":"pr","value":"#6771","number":6771},{"kind":"pr","value":"#12715","number":12715},{"kind":"pr","value":"#12794","number":12794},{"kind":"pr","value":"#12503","number":12503},{"kind":"pr","value":"#7586","number":7586}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":51368,"title":"fix(delegate): preserve named custom provider overrides","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":51650,"title":"fix(delegation): fix opencode-go subagents ignoring delegation.model config","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"9ae1fa9e39057517ef4cb70511e3e294f39e1a2f","number":0},{"kind":"pr","value":"#51540","number":51540}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":53240,"title":"fix(delegation): resolve custom:* provider base_url from config instead of falling through to parent (Fixes #53064)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#53064","number":53064}],"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":55175,"title":"fix(delegation): resolve api_key from provider env when base_url + provider set","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"efc1c2d33","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":55478,"title":"fix(delegate): merge persistent + runtime delegation config sources","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#60577","number":60577},{"kind":"pr","value":"#59982","number":59982}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":58337,"title":"fix(delegate): keep child credential pools provider-scoped","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"bdbb94dc4","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":59670,"title":"fix(tools): respect config model in delegate_task subagent spawn (#58298)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"0263f1d12","number":0},{"kind":"pr","value":"#58298","number":58298}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":60577,"title":"fix(delegation): make active config authoritative for delegation limits","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tests/tools/test_delegate.py","tools/delegate_tool.py"],"fix_hunks":[{"file":"tests/tools/test_delegate.py","hunks":["@@ -13,6 +13,7 @@\n import os\n import threading\n import time\n+import types\n import unittest\n from unittest.mock import MagicMock, patch\n ","@@ -21,6 +22,7 @@\n     DELEGATE_TASK_SCHEMA,\n     DelegateEvent,\n     _get_max_concurrent_children,\n+    _load_config,\n     _LEGACY_EVENT_MAP,\n     MAX_DEPTH,\n     check_delegate_requirements,","@@ -2390,6 +2392,71 @@ def test_progress_callback_task_progress_not_misrendered(self):\n class TestConcurrencyDefaults(unittest.TestCase):\n     \"\"\"Tests for the concurrency default and no hard ceiling.\"\"\"\n \n+    def test_load_config_prefers_active_persistent_config_over_cli_defaults(self):\n+        stale_cli = types.ModuleType(\"cli\")\n+        stale_cli.CLI_CONFIG = {\n+            \"delegation\": {\n+                \"max_iterations\": 45,\n+                \"model\": \"\",\n+                \"provider\": \"\",\n+                \"base_url\": \"\",\n+                \"api_key\": \"\",\n+            }\n+        }\n+        active_config = {\n+            \"delegation\": {\n+                \"max_iterations\": 50,\n+                \"max_concurrent_children\": 50,\n+                \"max_spawn_depth\": 10,\n+            }\n+        }\n+\n+        with patch.dict(\"sys.modules\", {\"cli\": stale_cli}):\n+            with patch(\n+                \"hermes_cli.config.load_config_readonly\", return_value=active_config\n+            ):\n+                self.assertEqual(_load_config()[\"max_concurrent_children\"], 50)\n+                self.assertEqual(_get_max_concurrent_children(), 50)\n+\n+    def test_load_config_falls_back_to_cli_config_when_persistent_load_fails(self):\n+        fallback_cli = types.ModuleType(\"cli\")\n+        fallback_cli.CLI_CONFIG = {\n+            \"delegation\": {\n+                \"max_iterations\": 45,\n+                \"max_concurrent_children\": 8,\n+            }\n+        }\n+\n+        with patch.dict(\"sys.modules\", {\"cli\": fallback_cli}):\n+            with patch(\n+                \"hermes_cli.config.load_config_readonly\",\n+                side_effect=RuntimeError(\"boom\"),\n+            ):\n+                self.assertEqual(_load_config()[\"max_concurrent_children\"], 8)\n+\n+    def test_load_config_prefers_cli_config_when_user_config_ignored(self):\n+        # `hermes chat --ignore-user-config` sets HERMES_IGNORE_USER_CONFIG=1,\n+        # which only load_cli_config() honors. The delegation loader must keep\n+        # CLI_CONFIG authoritative under the flag so user config.yaml\n+        # delegation keys stay suppressed.\n+        ignoring_cli = types.ModuleType(\"cli\")\n+        ignoring_cli.CLI_CONFIG = {\n+            \"delegation\": {\n+                \"max_iterations\": 45,\n+                \"max_concurrent_children\": 4,\n+            }\n+        }\n+        user_config = {\"delegation\": {\"max_concurrent_children\": 50}}\n+\n+        with patch.dict(\"sys.modules\", {\"cli\": ignoring_cli}):\n+            with patch.dict(os.environ, {\"HERMES_IGNORE_USER_CONFIG\": \"1\"}):\n+                with patch(\n+                    \"hermes_cli.config.load_config_readonly\",\n+                    return_value=user_config,\n+                ) as mock_loader:\n+                    self.assertEqual(_load_config()[\"max_concurrent_children\"], 4)\n+                    mock_loader.assert_not_called()\n+\n     @patch(\"tools.delegate_tool._load_config\", return_value={})\n     def test_default_is_three(self, mock_cfg):\n         # Clear env var if set"]},{"file":"tools/delegate_tool.py","hunks":["@@ -3098,26 +3098,40 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict:\n \n \n def _load_config() -> dict:\n-    \"\"\"Load delegation config from CLI_CONFIG or persistent config.\n-\n-    Checks the runtime config (cli.py CLI_CONFIG) first, then falls back\n-    to the persistent config (hermes_cli/config.py load_config()) so that\n-    ``delegation.model`` / ``delegation.provider`` are picked up regardless\n-    of the entry point (CLI, gateway, cron).\n+    \"\"\"Load delegation config from the active Hermes config.\n+\n+    Prefer the shared persistent loader because it follows the active\n+    HERMES_HOME/profile. ``cli.CLI_CONFIG`` is a legacy fallback for entry\n+    points that cannot import the shared loader; importing it first can return\n+    an old default ``delegation`` block and hide user-set keys such as\n+    ``max_concurrent_children``.\n+\n+    Uses ``load_config_readonly()``: every consumer of this dict is read-only\n+    (``.get()`` lookups), and this runs on each ``get_definitions()`` schema\n+    rebuild via ``_get_max_concurrent_children``, so skipping the defensive\n+    deepcopy matters. Do NOT mutate the returned dict.\n+\n+    ``HERMES_IGNORE_USER_CONFIG=1`` (``hermes chat --ignore-user-config``) is\n+    only honored by the legacy ``cli`` loader, not the shared one, so when the\n+    flag is set we keep ``cli.CLI_CONFIG`` authoritative to preserve the\n+    flag's contract of suppressing user config.yaml settings.\n     \"\"\"\n+    prefer_legacy = os.environ.get(\"HERMES_IGNORE_USER_CONFIG\") == \"1\"\n+    if not prefer_legacy:\n+        try:\n+            from hermes_cli.config import load_config_readonly\n+\n+            full = load_config_readonly()\n+            cfg = full.get(\"delegation\") or {}\n+            if isinstance(cfg, dict):\n+                return cfg\n+        except Exception:\n+            pass\n     try:\n         from cli import CLI_CONFIG\n \n         cfg = CLI_CONFIG.get(\"delegation\") or {}\n-        if cfg:\n-            return cfg\n-    except Exception:\n-        pass\n-    try:\n-        from hermes_cli.config import load_config\n-\n-        full = load_config()\n-        return full.get(\"delegation\") or {}\n+        return cfg if isinstance(cfg, dict) else {}\n     except Exception:\n         return {}\n \n"]}]},{"kind":"pr","number":61258,"title":"fix(delegation): prevent credential pool from overriding explicit delegation.base_url","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"f2c11ff30cd5601a4017cae64cbbeac0a481f5c9","number":0},{"kind":"pr","value":"#61195","number":61195}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":61275,"title":"fix(delegate): keep delegation.base_url authoritative against credential-pool rotation","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"f2c11ff30cd5601a4017cae64cbbeac0a481f5c9","number":0}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":64299,"title":"feat(delegate): per-call and per-task model/provider overrides for delegate_task","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#63461","number":63461},{"kind":"pr","value":"#63461","number":63461}],"category":"superseded_by_pr","implication":"superseded"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":72834,"title":"feat: add per-call model/provider override to delegate_task","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false}],"trust":{"default_tier":"verified","includes_suggested":false}}