fix(delegation): accept per-call model override on delegate_task#23769
Open
Tranquil-Flow wants to merge 2 commits into
Open
fix(delegation): accept per-call model override on delegate_task#23769Tranquil-Flow wants to merge 2 commits into
Tranquil-Flow wants to merge 2 commits into
Conversation
Collaborator
|
Duplicate of #23649 — same per-call model override fix for delegate_task, filed earlier today. Part of a saturated feature cluster (5+ competing PRs for this feature). |
This was referenced May 13, 2026
Soju06
pushed a commit
to Soju06/hermes-agent
that referenced
this pull request
May 22, 2026
Remove delegate-per-task-model from the active runtime patch manifest. The live configuration does not route subagents with per-call model overrides, and upstream PR NousResearch#23769 remains open with failing checks and duplicate overlap. Origin: local-author Upstream-PR: none Patch-State: local-only
The `delegate_task` tool schema and Python signature did not accept a
`model` parameter, so a model emitting
`delegate_task(model="...", goal="...")` had the override silently
dropped at the dispatch boundary — the child inherited the parent's
model regardless. `delegation.model` from `config.yaml` was already
plumbed through via `_resolve_delegation_credentials` (creds["model"]);
the gap was only at the per-call surface.
Adds a top-level `model` parameter plus a per-task `model` field with
the precedence:
per-task model > top-level model > delegation.model (from config)
`None` at any layer falls through to the next, so existing config-only
deployments are unaffected.
Six surgical changes:
* `delegate_task()` signature accepts `model: Optional[str] = None`
* Single-task path embeds `model` in the synthesized task dict so the
same precedence rule applies whether the caller used `goal=` or
`tasks=[...]`
* `_build_child_agent` call site replaces the bare `creds["model"]`
with the layered `task_model` resolution
* Top-level schema advertises `model`
* Per-task schema advertises `model`
* Registry handler lambda forwards `args.get("model")`
`_build_child_agent` already accepted a `model` parameter and resolved
it via `model or parent_agent.model`, so no changes needed there.
Tests
-----
Six new tests under `TestDelegateModelArg`:
* `test_schema_advertises_top_level_model` / `_per_task_model` —
schema audits the new fields exist with `type: string`
* `test_registry_handler_forwards_model_arg` — dispatching via the
registry passes `model` through (catches schema-without-handler
drift, the original failure mode)
* `test_top_level_model_reaches_child_build` — top-level `model=`
arrives at `_build_child_agent` (fails on current main)
* `test_per_task_model_wins_over_top_level` — per-task override
beats top-level for that task only (fails on current main)
* `test_delegation_config_model_used_when_no_per_call_override` —
regression guard: existing `delegation.model` config path still
flows through when no per-call value is given
All 133 tests in `tests/tools/test_delegate.py` pass.
Round-1 reviewer caught: ``_resolve_delegation_credentials`` was called once before the task loop using ``delegation.model`` from config, so ``api_mode`` / ``base_url`` were resolved against the configured default model. For providers whose transport depends on ``target_model`` — Azure Foundry (chat-completions vs responses by model lineage), openai-codex (codex_responses vs chat_completions per slug), and OpenCode — a call like ``delegate_task(model="gpt-5.3-codex")`` would inherit the wrong transport and the child would run the requested model over the wrong api_mode. Adds an ``override_model`` parameter to ``_resolve_delegation_credentials`` that takes precedence over ``cfg["model"]`` when computing the ``target_model`` passed into ``resolve_runtime_provider``. The task loop re-resolves credentials per task when ``task_model`` differs from the model that produced the outer ``creds``; otherwise the outer ``creds`` are reused so the common single-model path stays single-call. Regression test --------------- ``test_per_call_model_drives_runtime_resolution`` simulates an Azure-Foundry-style resolver that flips ``api_mode`` based on whether the target_model contains ``codex``. With ``delegation.model=gpt-5`` in config and ``delegate_task(model="gpt-5-codex")`` at the call site, the child must receive ``override_api_mode="responses"`` (resolved against the per-call model), not ``chat_completions`` (resolved against the config model). Test verified to fail on the round-1 code path (``chat_completions != responses``) and pass on the round-2 fix. All 134 tests in ``tests/tools/test_delegate.py`` pass.
f1ba60d to
30f51da
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Fixes a silent-drop bug in subagent delegation: the
delegate_tasktool accepted nomodelkwarg and the schema did not expose amodelfield, so a model emittingdelegate_task(model="claude-sonnet-4.6", goal="...")had its override silently discarded at the dispatch boundary — the child inherited the parent's model regardless.delegation.modelfromconfig.yamlwas already plumbed through_resolve_delegation_credentials(the reporter's "silently ineffective" claim for that path was stale); the gap was only at the per-call surface.Adds a top-level
modelparameter plus a per-taskmodelfield with the precedence:per-task model>top-level model>delegation.model(from config)Noneat any layer falls through to the next, so existing config-only deployments are unaffected.For providers whose transport selection depends on
target_model(Azure Foundry chat-completions-vs-responses by model lineage, openai-codex codex_responses-vs-chat_completions per slug, OpenCode routes), the task loop re-resolves credentials per task whentask_modeldiffers from the model that produced the outercreds. The common single-model path stays single-call.Related Issue
Closes #23467
Type of Change
Changes Made
tools/delegate_tool.py:delegate_task()signature acceptsmodel: Optional[str] = Nonemodelin the synthesized task dict so per-task precedence applies whether the caller usedgoal=ortasks=[...]task_modelwithper-task > top-level > delegation.modelprecedence and re-resolves credentials whentask_model != creds["model"]_resolve_delegation_credentialsgains anoverride_modelparameter that wins overcfg["model"]fortarget_modelcomputationmodel; per-task schema advertisesmodelargs.get("model")_build_child_agentalready accepted amodelparameter and resolved it viamodel or parent_agent.model, so no changes needed there.How to Test
134/134 pass, including 7 new tests under
TestDelegateModelArg:modelfieldsargs.get("model")(catches schema-without-handler drift, the original failure mode)model=reaches_build_child_agentfor every taskmodelwins over top-level for that task onlydelegation.modelconfig path still flows through when no per-call value is given (regression guard)Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/ADELEGATE_TASK_SCHEMA)Screenshots / Logs