diff --git a/hermes_cli/config_defaults.py b/hermes_cli/config_defaults.py index 49b11d168f..b5ecdbfa2e 100644 --- a/hermes_cli/config_defaults.py +++ b/hermes_cli/config_defaults.py @@ -2077,6 +2077,15 @@ DEFAULT_CONFIG = { # "codex_responses", or "anthropic_messages". Empty = auto-detect # from URL (e.g. /anthropic suffix → anthropic_messages). Set this # explicitly for non-standard endpoints the heuristic can't detect. + # Per-child request settings sent on every delegation API call, on all + # three resolution branches (direct base_url, named provider, and + # parent-inherit). Top-level keys are API kwargs (e.g. service_tier); + # an "extra_body" sub-dict is merged into the request's extra_body — + # e.g. {"extra_body": {"provider": {"sort": "throughput"}}} routes + # OpenRouter delegation children to the fastest provider. Precedence: + # these explicit values merge OVER runtime/parent-derived overrides + # (explicit keys win; extra_body deep-merged one level). + "request_overrides": {}, # When delegate_task narrows child toolsets explicitly, preserve any # MCP toolsets the parent already has enabled. On by default so # narrowing (e.g. toolsets=["web","browser"]) expresses "I want these diff --git a/tests/tools/test_delegate_request_overrides.py b/tests/tools/test_delegate_request_overrides.py index 3fa77cc61a..c66fdfbdfe 100644 --- a/tests/tools/test_delegate_request_overrides.py +++ b/tests/tools/test_delegate_request_overrides.py @@ -1,17 +1,26 @@ -"""Regression tests for delegation.request_overrides on the direct-endpoint branch. +"""Regression tests for the delegation.request_overrides config key. -The direct base_url branch of _resolve_delegation_credentials (delegation.base_url -set, provider=custom) used to drop delegation.request_overrides on the floor — -the named-provider branch forwards runtime request_overrides (delegate_tool.py -"request_overrides": dict(runtime.get("request_overrides") or {})), but the -direct branch returned no key. That made it impossible to give delegation -children OpenRouter routing hints (extra_body.provider = {"sort": "throughput"}) -when delegating straight to openrouter.ai/api/v1 via base_url+api_key. +PR #90953 (salvage): ``delegation.request_overrides`` is an explicit dict of +per-child request settings that must be honored on EVERY resolution branch of +``_resolve_delegation_credentials``: + +1. direct base_url (provider=custom) — the branch the original PR fixed, +2. named provider (delegation.provider set, no base_url), +3. parent-inherit (neither provider nor base_url). + +Precedence contract (post-#98237): explicit config values merge OVER +runtime/parent-derived overrides — top-level explicit keys win; the +``extra_body`` sub-dict is deep-merged one level so runtime extra_body keys +survive unless the explicit key redefines them. Nested values are deep-copied +so transport-side mutation cannot leak back into config. """ -import pytest +from unittest.mock import MagicMock, patch -from tools.delegate_tool import _resolve_delegation_credentials +from tools.delegate_tool import ( + _merge_request_overrides, + _resolve_delegation_credentials, +) def _cfg(**overrides): @@ -24,6 +33,20 @@ def _cfg(**overrides): return cfg +def _parent(**attrs): + parent = MagicMock() + parent._delegate_depth = 0 + # MagicMock attributes are MagicMocks (non-dict) by default; set real + # values for the ones the resolution path inspects. + parent.request_overrides = attrs.pop("request_overrides", None) + for k, v in attrs.items(): + setattr(parent, k, v) + return parent + + +# ── Branch 1: direct base_url ────────────────────────────────────────────── + + def test_direct_branch_forwards_request_overrides(): """delegation.request_overrides flows through the direct-endpoint branch.""" cfg = _cfg( @@ -35,6 +58,8 @@ def test_direct_branch_forwards_request_overrides(): assert creds["request_overrides"] == { "extra_body": {"provider": {"sort": "throughput"}}, } + # Shape parity with the named-provider branch: max_output_tokens present. + assert "max_output_tokens" in creds def test_direct_branch_absent_request_overrides_stays_none(): @@ -50,3 +75,182 @@ def test_direct_branch_non_dict_request_overrides_stays_none(): _cfg(request_overrides=bad), parent_agent=None ) assert creds["request_overrides"] is None + + +def test_direct_branch_deep_copies_nested_extra_body(): + """Transport-side mutation of the child's overrides must not leak back + into the config dict (copy.deepcopy, not a shallow dict()).""" + source = {"extra_body": {"provider": {"sort": "throughput"}}} + cfg = _cfg(request_overrides=source) + creds = _resolve_delegation_credentials(cfg, parent_agent=None) + creds["request_overrides"]["extra_body"]["provider"]["sort"] = "mutated" + creds["request_overrides"]["extra_body"]["injected"] = True + assert source == {"extra_body": {"provider": {"sort": "throughput"}}} + + +@patch("hermes_cli.runtime_provider.resolve_runtime_provider") +def test_explicit_merges_over_runtime_on_provider_alongside_base_url(mock_resolve): + """Precedence on the provider-alongside-base_url path (#98237 interplay): + explicit delegation.request_overrides merges OVER the named provider's + runtime overrides — runtime extra_body keys survive unless redefined, + explicit top-level keys win, and max_output_tokens is preserved.""" + mock_resolve.return_value = { + "provider": "custom", + "base_url": "https://provider-default.example/v1", + "api_key": "provider-key", + "api_mode": "chat_completions", + "request_overrides": { + "service_tier": "default", + "extra_body": {"thinking": {"type": "disabled"}, "provider": {"sort": "price"}}, + }, + "max_output_tokens": 8192, + } + cfg = _cfg( + provider="mimo", + request_overrides={ + "service_tier": "flex", + "extra_body": {"provider": {"sort": "throughput"}}, + }, + ) + creds = _resolve_delegation_credentials(cfg, parent_agent=None) + assert creds["request_overrides"] == { + # explicit top-level key wins + "service_tier": "flex", + "extra_body": { + # runtime extra_body key survives (not redefined) + "thinking": {"type": "disabled"}, + # explicit extra_body key wins over runtime's + "provider": {"sort": "throughput"}, + }, + } + assert creds["max_output_tokens"] == 8192 + + +# ── Branch 2: named provider (no base_url) ───────────────────────────────── + + +@patch("hermes_cli.runtime_provider.resolve_runtime_provider") +def test_named_provider_branch_honors_explicit_key(mock_resolve): + """The named-provider branch merges the explicit key over the provider's + runtime overrides — the config key never silently no-ops.""" + mock_resolve.return_value = { + "provider": "openrouter", + "base_url": "https://openrouter.ai/api/v1", + "api_key": "runtime-key", + "api_mode": "chat_completions", + "request_overrides": {"extra_body": {"reasoning": {"enabled": True}}}, + "max_output_tokens": 4096, + } + cfg = { + "model": "deepseek/deepseek-v4-flash-0731", + "provider": "openrouter", + "request_overrides": {"extra_body": {"provider": {"sort": "throughput"}}}, + } + creds = _resolve_delegation_credentials(cfg, parent_agent=None) + assert creds["request_overrides"] == { + "extra_body": { + "reasoning": {"enabled": True}, + "provider": {"sort": "throughput"}, + } + } + + +@patch("hermes_cli.runtime_provider.resolve_runtime_provider") +def test_named_provider_branch_without_explicit_key_unchanged(mock_resolve): + """Without the config key the named-provider branch behaves as before.""" + mock_resolve.return_value = { + "provider": "openrouter", + "base_url": "https://openrouter.ai/api/v1", + "api_key": "runtime-key", + "api_mode": "chat_completions", + "request_overrides": {"extra_body": {"reasoning": {"enabled": True}}}, + "max_output_tokens": 4096, + } + cfg = {"model": "m", "provider": "openrouter"} + creds = _resolve_delegation_credentials(cfg, parent_agent=None) + assert creds["request_overrides"] == {"extra_body": {"reasoning": {"enabled": True}}} + + +# ── Branch 3: parent-inherit (no provider, no base_url) ──────────────────── + + +def test_inherit_branch_honors_explicit_key_over_parent(): + """Pure-inherit setups still apply delegation.request_overrides, merged + over the parent agent's own request_overrides.""" + parent = _parent( + request_overrides={ + "service_tier": "default", + "extra_body": {"thinking": {"type": "disabled"}}, + } + ) + cfg = { + "model": "", + "provider": "", + "request_overrides": {"extra_body": {"provider": {"sort": "throughput"}}}, + } + creds = _resolve_delegation_credentials(cfg, parent) + assert creds["request_overrides"] == { + "service_tier": "default", + "extra_body": { + "thinking": {"type": "disabled"}, + "provider": {"sort": "throughput"}, + }, + } + + +def test_inherit_branch_without_key_stays_none(): + """No explicit key and no parent overrides → None (old contract: the + child's construction path falls back to the parent's request_overrides).""" + parent = _parent(request_overrides=None) + creds = _resolve_delegation_credentials({"model": "", "provider": ""}, parent) + assert creds["request_overrides"] is None + + +def test_inherit_branch_deep_copies_parent_overrides(): + """Parent's nested overrides must be deep-copied on the inherit branch.""" + parent_overrides = {"extra_body": {"thinking": {"type": "disabled"}}} + parent = _parent(request_overrides=parent_overrides) + cfg = { + "model": "", + "provider": "", + "request_overrides": {"extra_body": {"provider": {"sort": "throughput"}}}, + } + creds = _resolve_delegation_credentials(cfg, parent) + creds["request_overrides"]["extra_body"]["thinking"]["type"] = "mutated" + assert parent_overrides == {"extra_body": {"thinking": {"type": "disabled"}}} + + +# ── Merge helper unit tests ──────────────────────────────────────────────── + + +def test_merge_helper_both_none(): + assert _merge_request_overrides(None, None) is None + assert _merge_request_overrides({}, {}) is None + assert _merge_request_overrides("junk", 42) is None + + +def test_merge_helper_explicit_only(): + assert _merge_request_overrides(None, {"a": 1}) == {"a": 1} + + +def test_merge_helper_runtime_only(): + assert _merge_request_overrides({"a": 1}, None) == {"a": 1} + + +def test_merge_helper_explicit_top_level_wins(): + assert _merge_request_overrides({"a": 1, "b": 2}, {"a": 9}) == {"a": 9, "b": 2} + + +def test_merge_helper_extra_body_one_level_merge(): + merged = _merge_request_overrides( + {"extra_body": {"keep": 1, "clash": "runtime"}}, + {"extra_body": {"clash": "explicit", "new": 2}}, + ) + assert merged == {"extra_body": {"keep": 1, "clash": "explicit", "new": 2}} + + +def test_merge_helper_non_dict_runtime_extra_body_replaced(): + merged = _merge_request_overrides( + {"extra_body": "junk"}, {"extra_body": {"a": 1}} + ) + assert merged == {"extra_body": {"a": 1}} diff --git a/tools/delegate_tool.py b/tools/delegate_tool.py index 1de913bbdf..f38e35b124 100644 --- a/tools/delegate_tool.py +++ b/tools/delegate_tool.py @@ -1994,9 +1994,17 @@ def _build_child_agent( provider_require_parameters=child_provider_require_parameters, provider_data_collection=child_provider_data_collection, request_overrides=( - dict(override_request_overrides or {}) - if override_provider - else dict(getattr(parent_agent, "request_overrides", {}) or {}) + # override_request_overrides is honored whenever set — + # including the inherit branch (override_provider=None), + # where _resolve_delegation_credentials already merged + # delegation.request_overrides OVER the parent's values. + dict(override_request_overrides) + if override_request_overrides is not None + else ( + {} + if override_provider + else dict(getattr(parent_agent, "request_overrides", {}) or {}) + ) ), openrouter_min_coding_score=child_openrouter_min_coding_score, tool_progress_callback=child_progress_cb, @@ -4456,6 +4464,43 @@ def _resolve_child_credential_pool( return None +def _merge_request_overrides(runtime_overrides, explicit_overrides): + """Merge explicit ``delegation.request_overrides`` over runtime-derived ones. + + Precedence contract: the explicit config key WINS over runtime-derived + (provider-catalog or parent-inherited) overrides. Top-level keys from the + explicit dict replace same-named runtime keys; the ``extra_body`` sub-dict + is deep-merged ONE level — runtime ``extra_body`` keys survive unless the + explicit dict redefines that exact key. This keeps provider personality + (e.g. ``thinking: {type: disabled}``) intact while letting users layer + routing hints (e.g. ``extra_body.provider = {"sort": "throughput"}``) on + top. + + Both inputs are deep-copied (``copy.deepcopy``) so transport-side mutation + of the child's request kwargs can never leak back into the loaded config + dict or the provider runtime cache. + + Returns ``None`` when both sides are empty/non-dict. + """ + import copy as _copy + + runtime_overrides = runtime_overrides if isinstance(runtime_overrides, dict) else None + explicit_overrides = explicit_overrides if isinstance(explicit_overrides, dict) else None + if not runtime_overrides and not explicit_overrides: + return None + merged = _copy.deepcopy(runtime_overrides) if runtime_overrides else {} + explicit = _copy.deepcopy(explicit_overrides) if explicit_overrides else {} + runtime_extra = merged.get("extra_body") + explicit_extra = explicit.pop("extra_body", None) + merged.update(explicit) + if isinstance(runtime_extra, dict) and isinstance(explicit_extra, dict): + runtime_extra.update(explicit_extra) + merged["extra_body"] = runtime_extra + elif explicit_extra is not None: + merged["extra_body"] = explicit_extra + return merged or None + + def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict: """Resolve credentials for subagent delegation. @@ -4483,6 +4528,18 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict: configured_api_key = str(cfg.get("api_key") or "").strip() or None configured_api_mode = str(cfg.get("api_mode") or "").strip().lower() or None + # delegation.request_overrides: explicit per-child request settings from + # config. Honored on EVERY resolution branch (direct base_url, named + # provider, and parent-inherit) so the key never silently no-ops. + # Precedence: explicit merges OVER runtime/parent-derived overrides via + # _merge_request_overrides (top-level explicit keys win; extra_body is + # deep-merged one level). Non-dict values are ignored. + explicit_request_overrides = ( + cfg.get("request_overrides") + if isinstance(cfg.get("request_overrides"), dict) + else None + ) + # Native-SDK providers (Bedrock, Vertex, Google GenAI) speak their own # wire protocol — they cannot be reached via OpenAI chat_completions against # a base_url. For these, always fall through to resolve_runtime_provider() @@ -4494,23 +4551,23 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict: _is_native_sdk_provider = _provider_lower in _NATIVE_SDK_PROVIDERS if configured_base_url and not _is_native_sdk_provider: - # delegation.request_overrides: same semantics as the named-provider - # branch below (runtime.get("request_overrides")) — a dict merged into - # the child's API kwargs by the transport's profile path. Keys are - # top-level kwargs (e.g. service_tier); an "extra_body" sub-dict is - # merged into extra_body. This is how a direct-endpoint delegation - # (provider=custom) forwards OpenRouter routing hints such as - # extra_body.provider = {"sort": "throughput"} to its children — - # the child's CustomProfile does not emit provider preferences, and - # the parent-inheritance path is deliberately cleared when - # delegation.provider/base_url overrides the parent (see the + # delegation.request_overrides: an explicit dict of per-child request + # settings merged into the child's API kwargs by the transport's + # profile path. Keys are top-level kwargs (e.g. service_tier); an + # "extra_body" sub-dict is merged into extra_body. This is how a + # direct-endpoint delegation (provider=custom) forwards OpenRouter + # routing hints such as extra_body.provider = {"sort": "throughput"} + # to its children — the child's CustomProfile does not emit provider + # preferences, and the parent-inheritance path is deliberately cleared + # when delegation.provider/base_url overrides the parent (see the # provider-preference clearing in _build_child_agent). - configured_request_overrides = cfg.get("request_overrides") - request_overrides = ( - dict(configured_request_overrides) - if isinstance(configured_request_overrides, dict) - else None - ) + # + # Precedence: explicit delegation.request_overrides MERGES OVER any + # runtime-derived overrides (see _merge_request_overrides) — top-level + # explicit keys win; extra_body is deep-merged one level so runtime + # extra_body keys survive unless the explicit key redefines them. + # (explicit_request_overrides is parsed once at the top of this + # function and applied to every branch.) # When delegation.api_key is not set, return None so _build_child_agent # falls back to the parent agent's API key via the credential inheritance @@ -4577,6 +4634,12 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict: exc, ) + # Explicit delegation.request_overrides merges OVER the runtime-derived + # overrides (explicit wins; extra_body deep-merged one level). + request_overrides = _merge_request_overrides( + request_overrides, explicit_request_overrides + ) + return { "model": configured_model, "provider": provider, @@ -4588,14 +4651,22 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict: } if not configured_provider: - # No provider override — child inherits everything from parent + # No provider override — child inherits everything from parent. + # delegation.request_overrides still applies: merge the explicit key + # OVER the parent's own request_overrides so the config key works even + # in pure-inherit setups (never a silent no-op). None when neither + # side has values → _build_child_agent falls back to the parent's + # request_overrides unchanged. return { "model": configured_model, "provider": None, "base_url": None, "api_key": None, "api_mode": None, - "request_overrides": None, + "request_overrides": _merge_request_overrides( + getattr(parent_agent, "request_overrides", None), + explicit_request_overrides, + ), "max_output_tokens": None, } @@ -4639,7 +4710,13 @@ def _resolve_delegation_credentials(cfg: dict, parent_agent) -> dict: "base_url": runtime.get("base_url"), "api_key": api_key, "api_mode": runtime.get("api_mode"), - "request_overrides": dict(runtime.get("request_overrides") or {}), + # Explicit delegation.request_overrides merges OVER the named + # provider's runtime overrides (explicit wins; extra_body deep-merged + # one level) — same precedence as the direct-base_url branch above. + "request_overrides": _merge_request_overrides( + runtime.get("request_overrides"), explicit_request_overrides + ) + or {}, "max_output_tokens": runtime.get("max_output_tokens"), "command": runtime.get("command"), "args": list(runtime.get("args") or []), diff --git a/website/docs/user-guide/configuration.md b/website/docs/user-guide/configuration.md index 539bd0bf8d..8fe27bd397 100644 --- a/website/docs/user-guide/configuration.md +++ b/website/docs/user-guide/configuration.md @@ -2568,6 +2568,10 @@ delegation: # base_url: "http://localhost:1234/v1" # Direct OpenAI-compatible endpoint (takes precedence over provider) # api_key: "local-key" # API key for base_url (falls back to OPENAI_API_KEY) # api_mode: "" # Wire protocol for base_url: "chat_completions", "codex_responses", or "anthropic_messages". Empty = auto-detect from URL (e.g. /anthropic suffix → anthropic_messages). Set explicitly for non-standard endpoints the heuristic can't detect. + # request_overrides: # Per-child request settings sent on every subagent API call (all resolution branches). + # extra_body: # Merged into the request's extra_body — e.g. OpenRouter routing hints: + # provider: + # sort: throughput max_concurrent_children: 3 # Parallel children per batch (floor 1, no ceiling). Also via DELEGATION_MAX_CONCURRENT_CHILDREN env var. worktree_isolation: false # Give each child its own git worktree branched from HEAD (local backend + git repos only; inspired by Muse Code). See Subagent Delegation → Worktree Isolation. max_spawn_depth: 1 # Delegation tree depth cap (1-3, clamped). 1 = flat (default): parent spawns leaves that cannot delegate. 2 = orchestrator children can spawn leaf grandchildren. 3 = three levels. @@ -2578,6 +2582,19 @@ delegation: **Direct endpoint override:** If you want the obvious custom-endpoint path, set `delegation.base_url`, `delegation.api_key`, and `delegation.model`. That sends subagents directly to that OpenAI-compatible endpoint and takes precedence over `delegation.provider`. If `delegation.api_key` is omitted, Hermes falls back to `OPENAI_API_KEY` only. When `delegation.provider` is set alongside `delegation.base_url`, the explicit endpoint and key still win, but that provider's request settings (`extra_body` overrides and max output tokens from your `custom_providers` entry) are carried into the subagent. +**Per-child request settings (`request_overrides`):** `delegation.request_overrides` is a dict of request settings sent on every subagent API call. Top-level keys are API kwargs (e.g. `service_tier`); an `extra_body` sub-dict is merged into the request's `extra_body`. It is honored on **all three** resolution branches — direct `base_url`, named `provider`, and pure inherit — so the key always takes effect. Precedence: explicit `request_overrides` values merge **over** any runtime- or parent-derived overrides — top-level explicit keys win, and `extra_body` is deep-merged one level so runtime `extra_body` keys (e.g. a provider's `thinking: {type: disabled}` personality) survive unless your key redefines them. The canonical use case is OpenRouter routing hints for delegation children: + +```yaml +delegation: + model: "deepseek/deepseek-v4-flash-0731" + base_url: "https://openrouter.ai/api/v1" + api_key: "sk-or-..." + request_overrides: + extra_body: + provider: + sort: throughput # route children to the fastest OpenRouter provider +``` + **Wire protocol (`api_mode`):** Hermes auto-detects the wire protocol from `delegation.base_url` (e.g. paths ending in `/anthropic` → `anthropic_messages`; Codex / native Anthropic / Kimi-coding hostnames keep their existing detection). For endpoints the heuristic can't classify — for example Azure AI Foundry, MiniMax, Zhipu GLM, or LiteLLM proxies fronting an Anthropic-shaped backend — set `delegation.api_mode` explicitly to one of `chat_completions`, `codex_responses`, or `anthropic_messages`. Leave it empty (the default) to keep auto-detection. The delegation provider uses the same credential resolution as CLI/gateway startup. All configured providers are supported: `openrouter`, `nous`, `copilot`, `zai`, `kimi-coding`, `minimax`, `minimax-cn`. When a provider is set, the system automatically resolves the correct base URL, API key, and API mode — no manual credential wiring needed. diff --git a/website/docs/user-guide/features/delegation.md b/website/docs/user-guide/features/delegation.md index 1266eeb69b..52db42d895 100644 --- a/website/docs/user-guide/features/delegation.md +++ b/website/docs/user-guide/features/delegation.md @@ -188,7 +188,7 @@ delegation: provider: "openrouter" # optional: route children to a different provider ``` -Resolution order: `delegation.base_url` (direct endpoint) takes precedence, then `delegation.provider` (full credential bundle resolved via the runtime provider system), and when neither is set children inherit the parent's provider and credentials; `delegation.model` applies in all cases, and when it is empty children inherit the parent's model. Setting `delegation.provider` alongside `delegation.base_url` keeps the explicit endpoint but carries that provider's request overrides and max output tokens into the child. +Resolution order: `delegation.base_url` (direct endpoint) takes precedence, then `delegation.provider` (full credential bundle resolved via the runtime provider system), and when neither is set children inherit the parent's provider and credentials; `delegation.model` applies in all cases, and when it is empty children inherit the parent's model. Setting `delegation.provider` alongside `delegation.base_url` keeps the explicit endpoint but carries that provider's request overrides and max output tokens into the child. An explicit `delegation.request_overrides` dict is honored on every branch and merges over those runtime-derived values (see [Configuration](#configuration) below). Note that the pin is global: `delegate_task` has no per-task model parameter, so every child in a batch runs on the configured delegation model. For quality-sensitive subtasks that need a stronger model, either leave `delegation.model` unset for that session or hand the task to the [kanban board](kanban.md#per-task-model-override), which does support a per-task model override. @@ -512,10 +512,23 @@ delegation: base_url: "http://localhost:1234/v1" api_key: "local-key" # api_mode: "anthropic_messages" # Optional. Wire protocol override for base_url ("chat_completions", "codex_responses", or "anthropic_messages"). Empty = auto-detect from URL (e.g. /anthropic suffix). Set explicitly for endpoints the heuristic can't classify (Azure AI Foundry, MiniMax, Zhipu GLM, LiteLLM proxies, …). + +# Send per-child request settings on every subagent API call — e.g. OpenRouter +# routing hints when delegating straight to openrouter.ai via base_url: +delegation: + model: "deepseek/deepseek-v4-flash-0731" + base_url: "https://openrouter.ai/api/v1" + api_key: "sk-or-..." + request_overrides: + extra_body: + provider: + sort: throughput # children route to the fastest OpenRouter provider ``` When `base_url` points at an Anthropic-compatible endpoint — for example a path ending in `/anthropic`, an Azure Foundry Claude route, or a MiniMax `/anthropic` proxy — `api_mode` is auto-detected as `anthropic_messages` so the subagent uses the right wire format without you setting anything. Set `api_mode` explicitly when the auto-detection guess is wrong (rare). +`delegation.request_overrides` works on **all three** resolution branches — direct `base_url`, named `provider`, and pure inherit — so it always takes effect. Top-level keys are API kwargs (e.g. `service_tier`); an `extra_body` sub-dict is merged into the request's `extra_body`. Explicit values merge **over** runtime- or parent-derived overrides: explicit top-level keys win, and `extra_body` is deep-merged one level, so a provider's own request personality (e.g. `thinking: {type: disabled}`) survives unless your key redefines it. See [Configuration → Delegation](../configuration.md#delegation) for details. + :::tip The agent handles delegation automatically based on the task complexity. You don't need to explicitly ask it to delegate — it will do so when it makes sense. :::