feat(agent-core): named binding slots for instance-level subagent model routing#2020
feat(agent-core): named binding slots for instance-level subagent model routing#2020Yorha9e wants to merge 8 commits into
Conversation
🦋 Changeset detectedLatest commit: 25eea94 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Add an experimental subagent-model-selection feature (off by default, KIMI_CODE_EXPERIMENTAL_SUBAGENT_MODEL_SELECTION=1) that binds configured model aliases and thinking efforts to subagent types per workspace: - Bindings live in <projectRoot>/.kimi-code/local.toml [subagent.<type>] and are applied mechanically at spawn (workspace binding > profile binding > parent inheritance); the calling agent never sees or overrides them, removing LLM discretion from model routing - First unbound spawn of a type asks the user once interactively and persists the answer, including an explicit keep-inheriting choice - /subagent-model command (list/set/clear) manages bindings via a new session RPC chain (agent-core -> node-sdk -> TUI) - Resume/retry are sticky: a subagent always keeps the model/effort it was configured with (persisted via config.update wire records); no mid-conversation model switches. Unresolvable stored aliases fail fast with a configuration error instead of silently realigning - Agent profiles can bind modelAlias/thinkingEffort per subagent type - Effective model/effort are exposed in the subagent.spawned event, AgentBackgroundTaskInfo, approval label, and tool result text
…xperiment Address review feedback on MoonshotAI#1928: - resume/retry only keep the child's configured model when subagent-model-selection is enabled; with the experiment off, resume realigns to the parent's current model exactly as before - spawn-time profile modelAlias/thinkingEffort bindings are likewise ignored unless the experiment is enabled - docs: workspace bindings apply to Agent tool spawns only; AgentSwarm does not read bindings yet (swarm-wide routing is future work) - note the shared-target limitation of profile override resolution
…del aliases When a stored subagent binding references a model alias missing from the user's models config, interactive spawns re-ask with the missing-model reason and persist the repaired binding; non-interactive spawns inherit the main agent model with an explicit warning in the tool result.
applySubagentOverrides mutated the shared resolved target profile, so
once two owners bound the same subagent type differently the first
processed owner would win for all of them. linkResolvedSubagents now
builds an edge-scoped view of the target ({...target, overrides})
instead of mutating shared state. Single-owner behavior is unchanged;
adds regression coverage for two owners binding the same type.
… dismissed In non-interactive sessions the question channel exists but is never answered (e.g. kimi -p), so the missing-model re-ask always dismisses — previously that path inherited the parent model silently. A dismissed re-ask now emits the same explicit warning as the no-ask path.
Follow-up to the per-workspace type bindings: users pre-configure named slots under [subagent-slot.<name>] and the Agent tool selects one via a new binding_slot parameter — the caller names a slot, never a model. Slots outrank type bindings; unconfigured slots are created through the same ask-once flow, and broken or missing slots fall back to the type binding with an explicit warning where asking is unavailable.
…lots - Move the stored workspace type-binding read into SessionSubagentHost.spawn via an injected SubagentBindingResolver, so AgentSwarm batches resolve the same bindings as Agent tool spawns (per-run overrides still win; stale aliases are ignored with a log warning — interactive repair stays at the tool layer) - AgentSwarm gains a whole-batch binding_slot parameter, resolved once per run and stamped onto item-based spawn tasks through SubagentBatch; resumed members are untouched, unconfigured/stale slots fall back with a warning - Warn on inheritance when the slot stale-binding re-ask is dismissed, matching the type-binding behavior
5479c7a to
25eea94
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 25eea94052
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if ( | ||
| slotBinding !== undefined && | ||
| slotBinding.inherit !== true && | ||
| (slotBinding.model !== undefined || slotBinding.thinkingEffort !== undefined) | ||
| ) { |
There was a problem hiding this comment.
Honor explicit inherit slots before type fallback
When a stored slot binding is { inherit: true } (the value persisted when the user chooses “Keep inheriting from the main agent”), this condition skips returning and continues into the type-binding lookup below. In a workspace that also has [subagent.coder] bound, Agent(..., binding_slot: "foo") will still run on the type-bound model instead of inheriting, and if no type binding exists it can ask again for the type, so the persisted slot choice is not honored; treat inherit: true as a terminal slot result before falling through (and mirror the same fix in the swarm slot resolver).
Useful? React with 👍 / 👎.
| thinking_effort = "high" | ||
| ``` | ||
|
|
||
| The first time an unbound sub-agent type is spawned in a workspace, you are asked once whether to bind a model (answering "keep inheriting" is remembered too). Afterwards the binding is applied mechanically to every new sub-agent of that type spawned through the `Agent` tool — the calling Agent cannot see or override it. (`AgentSwarm` batches do not read bindings yet; swarm-wide model routing is future work.) Manage bindings anytime with the `/subagent-model` command (`list` / `set <type>` / `clear <type>`). Precedence: workspace binding > profile binding (for profiles shipped with the app) > inherit the calling Agent's current model and effort. |
There was a problem hiding this comment.
Correct the AgentSwarm binding guidance
This sentence says AgentSwarm batches do not read bindings, but this commit wires the workspace binding resolver into SessionSubagentHost and AgentSwarm passes binding-slot overrides into queued spawn tasks, so item-based swarms do use subagent bindings when the experiment is enabled. Users may launch a swarm expecting the calling agent’s model and instead route many subagents to a bound model; update both locales and the changeset to match the implementation.
Useful? React with 👍 / 👎.
Related Issue / PR
Stacked on #1928 — only the last 2 commits are new; this branch is rebased onto it and will be rebased again onto
mainonce #1928 merges. Marked draft for that reason.Related: #568 (subagent model routing), #1996 (global subagent default), #1841 (LLM-facing model param).
Problem
#1928 gives per-workspace, per-type bindings: "all
explorespawns in this workspace use model X." Some workflows need instance-level routing — this specific spawn uses slot A, that one uses slot B. The motivating consumer is multi-agent orchestration (a MoA-style flow where debater A runs DeepSeek, debater B runs Claude, the aggregator runs a third model), and per-member swarm routing.What changed (vs #1928)
Named binding slots. Users pre-configure slots in
.kimi-code/local.tomlunder[subagent-slot.<name>]; the caller names a slot, never a model — model routing stays user configuration, preserving #1928's "no LLM-facing model directory" stance.[subagent-slot.<name>]read/write inworkspace-local.ts, independent from[subagent.<type>].binding_slotparameter. Precedence: slot binding > workspace type binding > profile binding > parent inheritance. Unconfigured slots are created through the same ask-once flow; unconfigured/stale slots degrade to the type binding with an explicit warning (never a silent route to a wrong model).SessionSubagentHost.spawnvia an injectedSubagentBindingResolver(options.modelAlias ?? workspace type binding ?? profile binding ?? inherit). AgentSwarm batches therefore resolve the same workspace bindings as Agent tool spawns — the gap called out in feat(agent-core): per-workspace subagent model bindings #1928 review — while interactive asks/warnings stay at the tool layer.binding_slotparameter, resolved once per run (never per member) and stamped onto item-based spawn tasks throughSubagentBatch. Resumed members keep their configuration; swarm launches never ask interactively.subagent-model-selectionexperiment (off by default).Relation to #1996
Complementary granularity, not a duplicate: #1996 is a single global subagent default ("main on a strong model, all subagents cheaper"); this is per-spawn named routing ("this spawn uses slot A"). They compose naturally — global default as the base layer, type bindings (#1928) per workspace, slots per call.
Relation to the review discussion in #1928
This is the member-level routing context sketched there (member 0 → planner, members 1..N → workers, final → synthesizer), delivered as named slots instead of LLM-facing model selection.
Validation
pnpm run typecheckclean repo-wide.kimi -p): planned next; will update here once run.Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.