Skip to content

feat(delegation): optional per-task model selection in delegate_task - #55286

Closed
teknium1 wants to merge 1 commit into
mainfrom
kilocode-port/per-task-delegation-model
Closed

feat(delegation): optional per-task model selection in delegate_task#55286
teknium1 wants to merge 1 commit into
mainfrom
kilocode-port/per-task-delegation-model

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

delegate_task can now fan a single task out across different models — review the same code with several models in parallel — when the user opts in via delegation.allow_model_selection.

Ported (adapted) from Kilo-Org/kilocode#11786, which added per-task model + reasoning-variant selection to that project's agent_manager chat tool (the direct analog of our delegate_task).

What it does

With delegation.allow_model_selection: true, the agent can name a model per task"opus", "gpt-5", "glm", or a full vendor/model slug. The provider is resolved, not dictated: names are matched leniently against the parent's current provider (aggregator-aware), exactly the way the /model command resolves them. Off by default, so the documented "subagents inherit the parent model" contract is unchanged unless you opt in.

How it was adapted

  • No new model tool. Kilo Code shipped a separate agent_manager_models discovery tool. We skip it to respect the footprint ladder — the per-task model field rides on the existing delegate_task schema and is the only addition.
  • Reuse, don't reinvent. Kilo Code reimplemented lenient name→provider resolution. We route through the existing hermes_cli.model_switch.switch_model() pipeline (/model's resolution chain), so vendor/model slug conversion, fuzzy aliasing, and cross-provider fallback all come for free.
  • Cache-safe schema gating. The model field is injected in _build_dynamic_schema_overrides() only when the flag is on. The flag is a stable per-session config value, so the system prompt stays byte-stable for the life of a conversation — prompt caching is preserved.
  • Fail loud, not silent. An unresolvable model name returns a clear per-task tool error rather than silently falling back to the default model (which would mask the agent's intent).

Changes

  • tools/delegate_tool.py: _get_allow_model_selection(), _resolve_task_model_creds(), per-task creds resolution in the child-build loop, top-level model param, handler wiring, conditional schema injection.
  • hermes_cli/config.py: delegation.allow_model_selection (default False).
  • website/docs/user-guide/features/delegation.md: documents the flag + behavior.
  • tests/tools/test_delegate_model_selection.py: 10 new tests.

Validation

Flag off (default) Flag on
model in tool schema absent present (top-level + per-task)
per-task model honored no (inherits parent) yes (resolved via model_switch)
static schema mutated no no (deep-copied)
  • 10 new tests pass; resolution E2E-verified with real imports ("sonnet"anthropic/claude-sonnet-4.6 on the parent aggregator; full slug passthrough; bad name → ValueError).
  • 154 existing delegate tests + 1147 tests/hermes_cli/ tests green (config validation, config drift, model_switch all unaffected).

Infographic

per-task-model-selection

Port from Kilo-Org/kilocode#11786. When delegation.allow_model_selection
is enabled, delegate_task accepts an optional per-task `model` field so the
agent can fan work out across different models (e.g. review the same code
with Opus, GPT-5, and GLM in parallel).

The agent names a model the way a human would ("opus", "gpt-5", "glm", or a
full vendor/model slug); resolution reuses the existing aggregator-aware
model_switch pipeline (the same chain /model uses), so the provider is
resolved — not dictated — preferring the parent's current provider.

Gated off by default to preserve the documented "subagents inherit the
parent model" contract and avoid silently routing work to a more expensive
model. The schema field only appears when the flag is on (built in
_build_dynamic_schema_overrides, so prompt caching stays valid for the life
of a session). Unresolvable names return a clear per-task tool error rather
than silently falling back to the default model.
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: kilocode-port/per-task-delegation-model vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 11738 on HEAD, 11723 on base (🆕 +15)

🆕 New issues (11):

Rule Count
invalid-argument-type 7
invalid-assignment 3
unresolved-attribute 1
First entries
tools/delegate_tool.py:3206: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> str, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[str]]` cannot be called with key of type `Literal["properties"]` on object of type `list[str]`
tools/delegate_tool.py:3205: [invalid-assignment] invalid-assignment: Cannot assign to a subscript on an object of type `str`
tests/tools/test_delegate_model_selection.py:76: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> str, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[str]]` cannot be called with key of type `Literal["properties"]` on object of type `list[str]`
tools/delegate_tool.py:2383: [unresolved-attribute] unresolved-attribute: Attribute `strip` is not defined on `list[str] & ~AlwaysFalsy` in union `(Any & ~AlwaysFalsy) | (str & ~AlwaysFalsy) | (list[str] & ~AlwaysFalsy) | Literal[""]`
tools/delegate_tool.py:3206: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `bound method str.__getitem__(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str` cannot be called with key of type `Literal["model"]` on object of type `str`
tools/delegate_tool.py:3206: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> str, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[str]]` cannot be called with key of type `Literal["model"]` on object of type `list[str]`
tests/tools/test_delegate_model_selection.py:76: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> Unknown, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[Unknown]]` cannot be called with key of type `Literal["tasks"]` on object of type `list[Unknown]`
tools/delegate_tool.py:3205: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["model"]` and value of type `dict[str, str]` on object of type `list[str]`
tools/delegate_tool.py:3200: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["model"]` and value of type `dict[str, str]` on object of type `dict[Unknown | str, dict[str, str | dict[str, str] | dict[str, str | dict[str, dict[str, str] | dict[str, str | dict[str, str]] | dict[str, str | list[str]]] | list[str]] | list[str]]]`
tests/tools/test_delegate_model_selection.py:76: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `bound method str.__getitem__(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str` cannot be called with key of type `Literal["properties"]` on object of type `str`
tests/tools/test_delegate_model_selection.py:76: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `bound method str.__getitem__(key: SupportsIndex | slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> str` cannot be called with key of type `Literal["tasks"]` on object of type `str`

✅ Fixed issues: none

Unchanged: 6174 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@alt-glitch alt-glitch added type/feature New feature or request comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard tool/delegate Subagent delegation P3 Low — cosmetic, nice to have labels Jun 30, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Cluster note: per-task model selection in delegate_task is a saturated feature cluster with several open community PRs (#50015 per-call model/provider overrides, #6771 the earliest per-task override) tracking issues #41814 / #44900 / #10995 / #6306. This PR (by the project lead) implements the opt-in delegation.allow_model_selection variant that routes through the existing model_switch.switch_model() resolution and gates the schema cache-safely. Linking the cluster so a maintainer can pick a canonical implementation and close the rest; not marking any as duplicate.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: LGTM

Per-task model selection for delegate_task, gated behind config flag.

Looks Good

  • Off by default: preserves subagent-inherits-parent-model contract
  • Reuses existing model_switch resolution pipeline (no new model tool)
  • Cache-safe: model field only in dynamic schema overrides when flag is on
  • 128 lines of tests covering schema gating, resolution, and error cases
  • Ported from Kilo-Org/kilocode with footprint ladder compliance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have tool/delegate Subagent delegation type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants