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

Subagents delegated to a custom endpoint were misrouted when the parent
ran on a different custom endpoint. Both runtimes collapse to
provider="custom", so _resolve_child_credential_pool() treated them as
interchangeable and handed the child the parent's pool. Leasing from it
then overwrote the child's delegated base_url with the parent's endpoint
via _swap_credential() — the child sent the delegated model name to the
wrong endpoint.

Custom runtimes now resolve by endpoint identity (the custom:<name> pool
key derived from base_url). The parent pool is reused only when both
parent and child resolve to the same custom endpoint; unregistered raw
endpoints return None so the child keeps its fixed delegated credential.
Non-custom provider paths are unchanged.

Fixes #7833.
This commit is contained in:
Teknium 2026-06-07 22:05:14 -07:00 committed by GitHub
parent bddc5fd087
commit 48ae8029aa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 124 additions and 3 deletions

View file

@ -1184,7 +1184,9 @@ def _build_child_agent(
# 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 = child_pool
@ -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