NousResearch/hermes-agent

delegate_task per-call model/provider override and config handling

Cluster 3719 · agent. Data as of

2 open items. Fix merged in #41730 on 8 Jun 2026. Check it covers your case.

The short answer

1open issue
1open PR
80items grouped
Known?
Yes. 80 related items grouped here, 44 issues already closed.
Fix event
A fix was merged to main in PR #41730. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PRs #16894, #41730. That is separate from the fix event.
Still open
1 issue and 1 PR.

Most members concern delegate_task: requests and PRs to accept per-call model/provider overrides, plus reports that subagents inherit the parent model or ignore delegation config such as base_url. A few related items touch endpoint, credential-pool, and ACP command handling during delegation.

Open issues1

  • Issue#51540delegation.model ignored for opencode-go provider

Open PRs1

  • PR#58337fix(delegate): keep child credential pools provider-scoped

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #7360 fix(delegate): salvage delegate PRs #6933 #6881

    merged to main

    files: run_agent.py, tests/run_agent/test_agent_guardrails.py, tests/tools/test_delegate.py, tools/delegate_tool.py

  • #16894 fix(delegate): clear acp_command when override_provider is set

    merged to main

    files: tools/delegate_tool.py

  • #41730 fix(delegate): resolve custom-endpoint subagent pools by endpoint identity

    merged to main

    files: agent/credential_pool.py, tests/tools/test_delegate.py, tools/delegate_tool.py

    Show the diff
    • agent/credential_pool.py
      @@ -374,7 +374,7 @@ def _iter_custom_providers(config: Optional[dict] = None):
               yield _normalize_custom_pool_name(name), entry
       
       
      -def get_custom_provider_pool_key(base_url: str, provider_name: Optional[str] = None) -> Optional[str]:
      +def get_custom_provider_pool_key(base_url: Optional[str], provider_name: Optional[str] = None) -> Optional[str]:
           """Look up the custom_providers list in config.yaml and return 'custom:<name>' for a matching base_url.
       
           When provider_name is given, prefer matching by name first (solving the case where
    • tests/tools/test_delegate.py
      @@ -1518,6 +1518,73 @@ def test_different_provider_load_failure_returns_none(self):
       
               self.assertIsNone(result)
       
      +    # --- Custom-endpoint identity resolution (issue #7833) ---
      +
      +    def test_custom_different_endpoint_does_not_inherit_parent_pool(self):
      +        """A child on custom endpoint B must not inherit the parent's custom
      +        endpoint A pool just because both normalize to provider='custom'."""
      +        parent = _make_mock_parent()
      +        parent.provider = "custom"
      +        parent.base_url = "https://endpoint-a.example.com/v1"
      +        parent._credential_pool = [redacted:secret-assignment]"parent_custom_a_pool")
      +
      +        child_pool = MagicMock(name="endpoint_b_pool")
      +        child_pool.has_credentials.return_value = True
      +
      +        def fake_key(base_url, provider_name=None):
      +            return {
      +                "https://endpoint-a.example.com/v1": "custom:endpoint-a",
      +                "https://endpoint-b.example.com/v1": "custom:endpoint-b",
      +            }.get(base_url)
      +
      +        with patch("agent.credential_pool.get_custom_provider_pool_key", side_effect=fake_key), \
      +             patch("agent.credential_pool.load_pool", return_value=child_pool) as load_mock:
      +            result = _resolve_child_credential_pool(
      +                "custom", parent, "https://endpoint-b.example.com/v1"
      +            )
      +
      +        # Loaded the child's OWN endpoint pool, not the parent's.
      +        load_mock.assert_called_once_with("custom:endpoint-b")
      +        self.assertIs(result, child_pool)
      +        self.assertIsNot(result, parent._credential_pool)
      +
      +    def test_custom_same_endpoint_shares_parent_pool(self):
      +        """A child on the SAME custom endpoint as the parent reuses the parent's
      +        pool so rotation/cooldown state stays synchronized."""
      +        parent = _make_mock_parent()
      +        parent.provider = "custom"
      +        parent.base_url = "https://endpoint-a.example.com/v1"
      +        parent._credential_pool = [redacted:secret-assignment]"parent_custom_a_pool")
      +
      +        with patch(
      +            "agent.credential_pool.get_custom_provider_pool_key",
      +            return_value="custom:endpoint-a",
      +        ):
      +            result = _resolve_child_credential_pool(
      +                "custom", parent, "https://endpoint-a.example.com/v1"
      +            )
      +
      +        self.assertIs(result, parent._credential_pool)
      +
      +    def test_custom_unregistered_endpoint_returns_none(self):
      +        """A raw delegation.base_url with no matching custom_providers entry
      +        must NOT inherit the parent's pool — return None so the child keeps its
      +        fixed delegated credential."""
      +        parent = _make_mock_parent()
      +        parent.provider = "custom"
      +        parent.base_url = "https://endpoint-a.example.com/v1"
      +        parent._credential_pool = [redacted:secret-assignment]"parent_custom_a_pool")
      +
      +        with patch(
      +            "agent.credential_pool.get_custom_provider_pool_key",
      +            return_value=None,
      +        ):
      +            result = _resolve_child_credential_pool(
      +                "custom", parent, "https://raw-unregistered.example.com/v1"
      +            )
      +
      +        self.assertIsNone(result)
      +
           def test_build_child_agent_assigns_parent_pool_when_shared(self):
               parent = _make_mock_parent()
               mock_pool = MagicMock()
    • tools/delegate_tool.py
      @@ -1184,7 +1184,9 @@ def _child_thinking(text: str) -> None:
       
           # Share a credential pool with the child when possible so subagents can
           # rotate credentials on rate limits instead of getting pinned to one key.
      -    child_pool = _resolve_child_credential_pool(effective_provider, parent_agent)
      +    child_pool = _resolve_child_credential_pool(
      +        effective_provider, parent_agent, effective_base_url
      +    )
           if child_pool is not None:
               child._credential_pool = [redacted:secret-assignment]
       
      @@ -2368,7 +2370,11 @@ def delegate_task(
           )
       
       
      -def _resolve_child_credential_pool(effective_provider: Optional[str], parent_agent):
      +def _resolve_child_credential_pool(
      +    effective_provider: Optional[str],
      +    parent_agent,
      +    effective_base_url: Optional[str] = None,
      +):
           """Resolve a credential pool for the child agent.
       
           Rules:
      @@ -2377,12 +2383,60 @@ def _resolve_child_credential_pool(effective_provider: Optional[str], parent_age
           2. Different provider -> try to load that provider's own pool.
           3. No pool available -> return None and let the child keep the inherited
              fixed credential behavior.
      +
      +    Custom endpoints are a special case: every direct ``delegation.base_url``
      +    runtime collapses to ``provider="custom"``, so bare provider equality would
      +    treat two *different* custom endpoints as interchangeable and let the child
      +    inherit the parent's pool. Leasing from that pool then overwrites the
      +    child's delegated ``base_url`` with the parent's endpoint (issue #7833).
      +    We therefore resolve custom runtimes by endpoint identity (the
      +    ``custom:<name>`` pool key derived from the base_url) and only share the
      +    parent's pool when both resolve to the *same* custom endpoint.
           """
           if not effective_provider:
               return getattr(parent_agent, "_credential_pool", None)
       
           parent_provider = getattr(parent_agent, "provider", None) or ""
           parent_pool = getattr(parent_agent, "_credential_pool", None)
      +
      +    # Custom endpoints: distinguish by endpoint identity, not the bare "custom"
      +    # provider string. Two custom runtimes are only interchangeable when they
      +    # resolve to the same custom:<name> pool key.
      +    if effective_provider == "custom":
      +        try:
      +            from agent.credential_pool import get_custom_provider_pool_key, load_pool
      +
      +            child_key = get_custom_provider_pool_key(effective_base_url)
      +            if child_key is None:
      +                # Unregistered endpoint (raw delegation.base_url with no
      +                # matching custom_providers entry) -> no shared pool exists.
      +                # Keep the child's fixed delegated credential rather than
      +                # risk inheriting the parent's custom endpoint.
      +                return None
      +
      +            # Reuse the parent's pool only when it is the same custom endpoint.
      +            parent_key = get_custom_provider_pool_key(
      +                getattr(parent_agent, "base_url", None)
      +            )
      +            if (
      +                parent_pool is not None
      +                and parent_provider == "custom"
      +                and parent_key is not None
      +                and parent_key == child_key
      +            ):
      +                return parent_pool
      +
      +            pool = load_pool(child_key)
      +            if pool is not None and pool.has_credentials():
      +                return pool
      +        except Exception as exc:
      +            logger.debug(
      +                "Could not resolve custom credential pool for child endpoint '%s': %s",
      +                effective_base_url,
      +                exc,
      +            )
      +        return None
      +
           if parent_pool is not None and effective_provider == parent_provider:
               return parent_pool
       

31 PRs closed without merging

From the close comments: duplicate 7 · done elsewhere 6 · superseded by another PR 3 · closed without comment 2 · merged via another change 2 · stale or abandoned 2 · won't fix / by design 1 · other 8

  • #3734 feat: per-call model/provider override on delegate_task · closed without comment
  • #6933 fix(delegate): sync self.base_url with client_kwargs after credential resolution · merged via another change → #7360
  • #8485 feat: per-skill model routing + supervisor/execution model config · other
  • #9255 feat(delegate): add task-tier profiles with per-task reasoning_effort and pool validation · stale or abandoned
  • #9737 feat(delegate): automatic tier routing + task-tier profiles with per-task reasoning_effort · stale or abandoned
Show 26 more
  • #10996 feat(tools): add model parameter to delegate_task for per-task model selection · other → #9737
  • #16503 fix(delegate): detect /anthropic URL suffix for proxy endpoints (#7833) · superseded by another PR → #13567
  • #16839 fix(delegate): clear acp_command when override_provider is set · merged via another change → #16894
  • #17581 Fix delegate_task model/provider precedence and expose per-task overrides · other → a6f08ff, #10995
  • #17718 fix: honor per-task model override in delegate_task · other
  • #23769 fix(delegation): accept per-call model override on delegate_task · duplicate → #23649
  • #25026 feat(delegate): per-task model and provider override for delegate_task subagents · duplicate → #3172, #16163, #17756, #15789, #14974
  • #26486 fix(delegation): honor provider overrides for subagents · closed without comment
  • #34773 feat(delegate): per-task model/provider override in delegate_task tasks array · duplicate → #14974, #17756, #20000, #23266, #32555, #34752, #34764, #15789
  • #34776 feat(phase34754): Centralized Model Complexity Configuration · duplicate → #34752, #34727, #10995, #13663
  • #35033 feat(delegate): per-task model/provider overrides (+ fix two latent delegation bugs) · duplicate → #14974, #25530, #23649, #17756
  • #36790 feat(delegate): add per-task model/provider routing · won't fix / by design
  • #37578 feat: add model/provider override param to delegate_task tool · other
  • #39282 feat(delegate_task): add per-task model/provider override · done elsewhere
  • #41826 feat(delegation): add per-task model override for delegate_task · duplicate → #6771, #14974, #15789, #12794, #16163
  • #41865 fix(delegate): honor model parameter end-to-end (fixes #41821, closes #41814) · other → #41814, #53846
  • #43134 feat(delegate_tool): per-task model/provider override · superseded by another PR → #43185
  • #43185 fix(delegate_tool): address PR #43134 multi-provider review feedback · other
  • #50015 feat(delegate_task): per-call model/provider overrides · duplicate → #6771, #15789, #47014, #47295, #46880, #32711, #6306, #23769, #12715, #12794, #12503, #7586
  • #51650 fix(delegation): fix opencode-go subagents ignoring delegation.model config · done elsewhere → 9ae1fa9, #51540
  • #55175 fix(delegation): resolve api_key from provider env when base_url + provider set · done elsewhere → efc1c2d
  • #59670 fix(tools): respect config model in delegate_task subagent spawn (#58298) · done elsewhere → 0263f1d, #58298
  • #61258 fix(delegation): prevent credential pool from overriding explicit delegation.base_url · done elsewhere → f2c11ff, #61195
  • #61275 fix(delegate): keep delegation.base_url authoritative against credential-pool rotation · done elsewhere → f2c11ff
  • #64299 feat(delegate): per-call and per-task model/provider overrides for delegate_task · superseded by another PR → #63461
  • #72834 feat: add per-call model/provider override to delegate_task · other
Closed issues · 44
  • Closed#3719Feature: Per-call model/provider override on delegate_task
  • Closed#5012[Feature]: Allow delegate_task to accept provider/model parameters
  • Closed#5508feat: per-skill model routing + supervisor/execution model config
  • Closed#6306Feature: Allow per-task model selection in delegate_task
  • Closed#6825Bug: delegation.base_url correctly resolved but overwritten before first API call
  • Closed#7833delegate_task can overwrite delegated custom endpoint with parent custom credential pool
  • Closed#10995feat: delegate_task should support per-task model selection
  • Closed#14974[Feature]: delegation-per-task-endpoint-overrides
  • Closed#15789feat: per-task model/provider overrides in delegate_task
  • Closed#16816[Bug]: Add delegation.acp_command: none config option
  • Closed#17685delegate_task: per-task model override is silently ignored — children always use parent's model
  • Closed#17732Feature: per-call model/provider override in delegate_task
  • Closed#18591Feature: Per-task model override for delegate_task subagents
  • Closed#19567TUI delegation subagent incorrectly routed to copilot-acp instead of configured deepseek provider
  • Closed#23467delegate_task model parameter silently discarded — subagents always inherit parent model
  • Closed#24263Feature: Per-sub-agent provider/model selection in delegate_task
  • Closed#26482delegation model override ignored — subagents always inherit parent provider/model
  • Closed#27294[Bug] delegate_task ignores delegation.base_url — subagent always inherits parent model
  • Closed#27295[Bug] delegate_task ignores delegation.base_url — subagent always inherits parent model
  • Closed#27296[Bug] delegate_task ignores delegation.base_url — subagent always inherits parent model
  • Closed#31155delegation.model override ignored — subagents always inherit parent model
  • Closed#32711delegate_task() lacks model and provider parameters — prevents per-call subagent routing
  • Closed#34462[TASK] Implement Per-Call Provider and Model Overrides for delegate_task
  • Closed#34489Per-Task Model Override in delegate_task
  • Closed#34727[FEATURE] Task Delegation + Intelligent Model Selection (Phases 7 & 3)
  • Closed#34764feat(delegate): per-task model/provider override in delegate_task tasks array
  • Closed#35437feat: add model/provider/base_url/api_format parameters to delegate_task (consolidating #5012, #10995, #17685, #23467, #32711)
  • Closed#37574feat: add model/provider override param to delegate_task tool
  • Closed#41814Feature: Per-task model override for delegate_task
  • Closed#44900feat(delegation): add model override to delegate_task
  • Closed#47014Feature Request: Per-call model override for delegate_task
  • Closed#47295feat: per-call model routing in delegate_task (multi-model delegation)
  • Closed#49332delegate_task model override ignored — subagents use wrong model, consume unauthorized credits
  • Closed#50202[Feature]: per-call model/provider override for delegate_task (multi-model subagent orchestration)
  • Closed#51401Feature Request: Per-Task Model Override in delegate_task
  • Closed#52331[Feature]: Per-call model routing for delegate_task subagents
  • Closed#53846delegate_task model parameter not honored — child agents always inherit parent session model
  • Closed#56891Feature: Expose model parameter on delegate_task for per-call model selection
  • Closed#58298Subagent delegation ignores config, always uses credential-pool model (glm-4-flash)
  • Closed#58731Feature Request: Per-subagent model override in delegate_task
  • Closed#61195[Bug]: delegation.base_url correctly resolved through init_agent() but subagent's actual API call still routes to OpenRouter → 401
  • Closed#61622feat: support per-subagent model selection in delegate_task
  • Closed#62731Feature Request: Allow delegate_task to use different models per subtask
  • Closed#66536[Feature]: Per-call model/provider override for delegate_task
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.03: 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
#7360, #16894, #41730
Closed issues
44
Attempts
31 PRs closed without merging
Fix link checked
8 Jun 2026

For your agent

The same facts, typed.