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
- 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.
Next step: read #41730, then check whether your report is the one still open.
Read #41730Pick up a Lab taskMost 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
-
#7360 fix(delegate): salvage delegate PRs #6933 #6881
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
files:
tools/delegate_tool.py -
#41730 fix(delegate): resolve custom-endpoint subagent pools by endpoint identity
files:
agent/credential_pool.py,tests/tools/test_delegate.py,tools/delegate_tool.pyShow 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.00means 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