Summary
delegate_task can misroute subagents that target one custom endpoint when the parent agent runs on a different custom endpoint. The child is initialized with the correct delegation base_url, but its credential pool selection later rebinds it to the parent's custom pool because both runtimes collapse to provider="custom".
Reported by Hermes Agent.
Reproduction
- Configure the main Hermes runtime to use one custom endpoint (for example a Codex-compatible endpoint).
- Configure
delegation to use a different custom endpoint and model.
- Run a minimal
delegate_task that should only produce a tiny text response.
- Observe that the delegated child can fail against the parent's endpoint instead of the delegated endpoint.
Observed Behavior
The child is initially built with the delegated custom endpoint, but before the delegated run starts it receives a credential pool derived from the parent custom runtime. When the child leases that pool, _swap_credential() overwrites the delegated base_url with the parent's custom endpoint. This makes the child send the delegated model name to the wrong endpoint.
Expected Behavior
Delegated children using one custom endpoint should never inherit a different custom endpoint's credential pool. Pool selection must distinguish custom runtimes by endpoint identity, not just by the normalized provider name custom.
Code Evidence
- Delegation config is loaded in
tools/delegate_tool.py via _load_config() and _resolve_delegation_credentials().
- Child agents are built in
tools/delegate_tool.py with the correct delegated base_url and api_mode.
- The bug happens in
_resolve_child_credential_pool() in tools/delegate_tool.py, which currently shares the parent's pool whenever effective_provider == parent_provider.
- For custom endpoints, both parent and child often normalize to
provider="custom", even when their base_url values differ.
- Later,
_run_single_child() acquires a lease from that pool and calls child._swap_credential(...), which overwrites the child's base_url with the leased pool entry.
Root Cause
_resolve_child_credential_pool() treats all custom runtimes as interchangeable because it compares only the provider string. It does not account for custom endpoint identity. Named custom providers and explicit delegation.base_url configurations can therefore collapse into the same provider family and incorrectly share pools.
Suggested Fix
When the effective provider is custom, resolve the pool by the delegated endpoint identity (for example via the custom pool key derived from the delegated base_url) instead of sharing the parent pool based only on provider equality. Only reuse the parent's pool when both the provider and the effective custom endpoint match.
Summary
delegate_taskcan misroute subagents that target one custom endpoint when the parent agent runs on a different custom endpoint. The child is initialized with the correct delegationbase_url, but its credential pool selection later rebinds it to the parent's custom pool because both runtimes collapse toprovider="custom".Reported by Hermes Agent.
Reproduction
delegationto use a different custom endpoint and model.delegate_taskthat should only produce a tiny text response.Observed Behavior
The child is initially built with the delegated custom endpoint, but before the delegated run starts it receives a credential pool derived from the parent custom runtime. When the child leases that pool,
_swap_credential()overwrites the delegatedbase_urlwith the parent's custom endpoint. This makes the child send the delegated model name to the wrong endpoint.Expected Behavior
Delegated children using one custom endpoint should never inherit a different custom endpoint's credential pool. Pool selection must distinguish custom runtimes by endpoint identity, not just by the normalized provider name
custom.Code Evidence
tools/delegate_tool.pyvia_load_config()and_resolve_delegation_credentials().tools/delegate_tool.pywith the correct delegatedbase_urlandapi_mode._resolve_child_credential_pool()intools/delegate_tool.py, which currently shares the parent's pool whenevereffective_provider == parent_provider.provider="custom", even when theirbase_urlvalues differ._run_single_child()acquires a lease from that pool and callschild._swap_credential(...), which overwrites the child'sbase_urlwith the leased pool entry.Root Cause
_resolve_child_credential_pool()treats all custom runtimes as interchangeable because it compares only the provider string. It does not account for custom endpoint identity. Named custom providers and explicitdelegation.base_urlconfigurations can therefore collapse into the same provider family and incorrectly share pools.Suggested Fix
When the effective provider is
custom, resolve the pool by the delegated endpoint identity (for example via the custom pool key derived from the delegatedbase_url) instead of sharing the parent pool based only on provider equality. Only reuse the parent's pool when both the provider and the effective custom endpoint match.