Skip to content

feat(agent-core): named binding slots for instance-level subagent model routing#2020

Open
Yorha9e wants to merge 8 commits into
MoonshotAI:mainfrom
Yorha9e:feat/subagent-binding-slots
Open

feat(agent-core): named binding slots for instance-level subagent model routing#2020
Yorha9e wants to merge 8 commits into
MoonshotAI:mainfrom
Yorha9e:feat/subagent-binding-slots

Conversation

@Yorha9e

@Yorha9e Yorha9e commented Jul 21, 2026

Copy link
Copy Markdown

Related Issue / PR

Stacked on #1928only the last 2 commits are new; this branch is rebased onto it and will be rebased again onto main once #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 explore spawns 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.toml under [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.

  • Storage: [subagent-slot.<name>] read/write in workspace-local.ts, independent from [subagent.<type>].
  • Agent tool: new binding_slot parameter. 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).
  • Shared spawn path: the stored type-binding read moves into SessionSubagentHost.spawn via an injected SubagentBindingResolver (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.
  • AgentSwarm: new whole-batch binding_slot parameter, resolved once per run (never per member) and stamped onto item-based spawn tasks through SubagentBatch. Resumed members keep their configuration; swarm launches never ask interactively.
  • Everything stays gated behind the subagent-model-selection experiment (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

  • New coverage: slot storage round-trips (independence from type bindings, side-by-side, clear), slot precedence over type binding, slot ask-once creation, unconfigured/stale slot warnings (both tools), host-level type-binding resolution on the shared spawn path, explicit-override wins over stored binding, stale alias ignored with inheritance, swarm slot stamping (spawn-only, resume untouched).
  • Full affected suites pass (workspace-local, subagent-host, subagent-batch, builtin tools, Agent tool — 151 tests); pnpm run typecheck clean repo-wide.
  • E2E on a locally built CLI (kimi -p): planned next; will update here once run.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue, or explained the problem above.
  • I have added tests that prove my feature works.
  • Ran gen-changesets skill, or this PR needs no changeset.
  • Ran gen-docs skill, or this PR needs no doc update.

@changeset-bot

changeset-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 25eea94

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@moonshot-ai/kimi-code Minor

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

yorha9e added 8 commits July 21, 2026 22:08
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
@Yorha9e
Yorha9e force-pushed the feat/subagent-binding-slots branch from 5479c7a to 25eea94 Compare July 21, 2026 14:09
@Yorha9e
Yorha9e marked this pull request as ready for review July 21, 2026 14:10

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 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".

Comment on lines +243 to +247
if (
slotBinding !== undefined &&
slotBinding.inherit !== true &&
(slotBinding.model !== undefined || slotBinding.thinkingEffort !== undefined)
) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant