Skip to content

feat(agent-core): dual-model-routing — separate subagent model and thinking effort#1996

Open
kassieclaire wants to merge 8 commits into
MoonshotAI:mainfrom
kassieclaire:feat/dual-model-routing
Open

feat(agent-core): dual-model-routing — separate subagent model and thinking effort#1996
kassieclaire wants to merge 8 commits into
MoonshotAI:mainfrom
kassieclaire:feat/dual-model-routing

Conversation

@kassieclaire

@kassieclaire kassieclaire commented Jul 21, 2026

Copy link
Copy Markdown

Related Issue

Resolve #568 — in particular this comment: run the main agent on a top-tier reasoning model while dispatching subagents to faster, cheaper models.

Problem

See linked issue. Subagents always inherit the main agent's model and thinking effort; there is no way to route delegated work to a different model.

What changed

Adds the opt-in dual-model-routing experimental feature (default off; enable via /experiments, KIMI_CODE_EXPERIMENTAL_DUAL_MODEL_ROUTING, or the master KIMI_CODE_EXPERIMENTAL_FLAG). When enabled, delegated subagents run on a dedicated model and thinking effort instead of inheriting the main agent's. When disabled, behavior is unchanged everywhere.

  • Config: new default_subagent_model and default_subagent_thinking_effort fields (config.toml schema + patch schema + TOML round-trip).
  • Runtime: SessionSubagentHost resolves model/effort from the live session override → config default → parent inheritance, gated by the flag, on all delegated creation paths (spawn, resume, retry — including AgentSwarm, which launches through the same host methods). Live overrides persist into session metadata and survive /reload, resume, and fork.
  • RPC / SDK: session-level setSubagentModel / getSubagentModel / setSubagentThinking / getSubagentThinking with eager alias validation (mirroring Agent.setModel) and a clear CONFIG_INVALID rejection when the flag is off. The SDK exposes Session.setSubagentModel / setSubagentThinkingEffort and SessionStatus.subagentModel / subagentThinkingEffort; getStatus guards the new RPCs so it keeps working against older cores that lack them.
  • TUI: /model opens a scope picker (Main agent / Subagents) when the flag is on; the subagent picker applies both model and effort (persisted as defaults or session-only); the footer shows a subagents: badge only when the subagent model is distinct from the main one; state is hydrated on login, resume, reload, and experiment toggles, and cleared on logout.
  • The /btw exception: the side-question agent always inherits the main agent's model and effort. It replays the main agent's projected history (same prompt prefix plus a small reminder), so it shares the warm prefix cache — routing it elsewhere would re-process the whole conversation on a cold cache for a lightweight text-only side channel.
  • Docs: English and Chinese configuration docs, env-var reference, and a minor changeset for @moonshot-ai/kimi-code and @moonshot-ai/kimi-code-sdk.

Relation to other open PRs

This takes a different approach from #1841 (an LLM-facing model directory with a model argument on Agent/AgentSwarm) and #1928 (per-workspace, per-type bindings in local.toml): routing here is purely user-driven session/config state — the model never sees or self-selects it — and it covers thinking effort alongside the model. #589 (/swarm-model) is superseded by the /model scope picker.

Validation

  • Tests: 6522 passed across packages/agent-core, packages/node-sdk, and apps/kimi-code. New coverage: flag-on routing on spawn/resume/retry, /btw inheritance with a subagent model configured, RPC round-trip/clear/invalid-alias/flag-off paths, getStatus older-core back-compat, scope-picker and footer components, config round-trips, experiment-toggle hydration.
  • pnpm run typecheck clean repo-wide; pnpm oxlint 0 warnings / 0 errors on all changed files.
  • Built the CLI locally and smoke-tested the /model scope picker with the flag enabled.
  • Worked on this PR using the built CLI from this PR -- works on the CLI version of the interface, still need more user testing, hence the experimental flag

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. (changeset included: .changeset/dual-model-routing.md, minor for both publishable packages)
  • Ran gen-docs skill, or this PR needs no doc update. (docs updated, en + zh)

Closes #568 (comment)

Co-authored by forgecode and kimi-code (Kimi K3 and GLM-5.2).

…e subagent models

Add the dual-model-routing experimental flag so the main agent and its
subagents can run on different models (e.g. Kimi K3 for the main agent,
GLM-5.2 for subagents).

When enabled (default off), subagents use a dedicated subagent model
instead of inheriting the parent agent model:

- Config: new default_subagent_model field (config.toml + patch schema)
- Runtime: SessionSubagentHost resolves the subagent model from the live
  session override or config default, gated by the flag; all four
  subagent creation paths (spawn, resume, retry, btw) honor it
- RPC: Session-level setSubagentModel/getSubagentModel added to the
  SessionAPI surface; SessionStatus carries subagentModel
- SDK: Session.setSubagentModel + SessionStatus.subagentModel exposed
- TUI: /model opens a scope picker (Main agent / Subagents) when the
  flag is on; the footer shows both models; state is hydrated on
  login, resume, reload, and experiment toggles

When disabled, all UI and runtime behavior is unchanged — subagents
inherit the parent model, the footer shows one model, and /model keeps
its singular behavior.
…ng hardening

Extend the dual-model-routing experimental feature so subagents can run a
dedicated thinking effort alongside the dedicated model, and harden the
original routing based on review:

- Thinking effort: new default_subagent_thinking_effort config field,
  live session override, setSubagentThinking/getSubagentThinking RPCs,
  SDK Session.setSubagentThinkingEffort + SessionStatus.subagentThinkingEffort.
  The /model subagent picker applies the selected effort (previously
  silently dropped), and the footer badge / scope picker display it.
- btw exception: the /btw side-question agent always inherits the main
  agent's model and effort, even when the flag is on. It replays the main
  agent's prompt prefix and shares its warm prefix cache, so rerouting it
  would re-process the whole conversation on a cold cache for a
  lightweight text-only side channel.
- Validation: setSubagentModel validates the alias eagerly via the
  provider manager (mirroring Agent.setModel) instead of failing deep
  inside a later subagent spawn; both subagent setters reject with
  CONFIG_INVALID when the flag is off rather than storing dormant values.
- Persistence: live subagent overrides are written to session metadata
  (custom) and rehydrated on resume, so they survive /reload, resume,
  and fork like the main model does.
- SDK: getStatus guards the new RPCs with typeof checks so it keeps
  working against older cores that lack them.
- TUI: footer badge only renders when the subagent model is distinct
  from the main model; logout/provider-removal clears subagent state;
  the subagent picker drops the misleading main-conversation cache
  warning; no-op selections short-circuit.
- Tests: flag-on routing across spawn/resume/retry, btw inheritance with
  a subagent model configured, Session RPC surface (round-trip, clear,
  invalid alias, flag-off), getStatus against older cores, scope selector
  and footer components, experiment-toggle hydration. Also fix the
  node-sdk experimental-feature list assertion that was failing at HEAD
  (missing dual-model-routing entry).
Mirror the English docs for the dual-model-routing experimental feature:
default_subagent_model / default_subagent_thinking_effort rows plus the
tip block (btw exception, session create/resume semantics) in
config-files.md, and the KIMI_CODE_EXPERIMENTAL_DUAL_MODEL_ROUTING row in
env-vars.md.
Copilot AI review requested due to automatic review settings July 21, 2026 05:25
@changeset-bot

changeset-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 30f7418

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

This PR includes changesets to release 4 packages
Name Type
@moonshot-ai/kimi-code Minor
@moonshot-ai/kimi-code-sdk Minor
@moonshot-ai/acp-adapter Patch
kimi-code Patch

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

Copilot AI 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.

Pull request overview

Adds an opt-in experimental feature (dual-model-routing) to route delegated subagents (spawn/resume/retry) to a dedicated model and thinking-effort, configurable via new config defaults and session-level RPC/SDK setters, with TUI support and docs updates. This fits the codebase’s ongoing work on model routing controls while keeping default behavior unchanged when the flag is off.

Changes:

  • Add new config fields (default_subagent_model, default_subagent_thinking_effort) plus TOML/schema round-trips and session persistence for live overrides.
  • Add session RPC + Node SDK support for getting/setting subagent model/thinking (with older-core back-compat in getStatus).
  • Update the TUI /model flow to support main vs subagent scope selection and render a subagent badge when appropriate; add tests and docs.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/node-sdk/test/session-get-status.test.ts Adds coverage for getStatus behavior with/without subagent RPCs.
packages/node-sdk/test/config.test.ts Registers the new experimental flag in harness config tests.
packages/node-sdk/src/types.ts Extends SessionStatus with subagentModel and subagentThinkingEffort.
packages/node-sdk/src/session.ts Adds Session.setSubagentModel / setSubagentThinkingEffort helpers.
packages/node-sdk/src/rpc.ts Defines new subagent RPC types/methods and guards getStatus for older cores.
packages/agent-core/test/session/subagent-host.test.ts Adds test coverage for model/effort separation and /btw inheritance exception.
packages/agent-core/test/session/init.test.ts Adds round-trip + gating tests for new session RPCs without spinning up a full agent.
packages/agent-core/test/flags/resolver.test.ts Ensures the new flag is registered and env-driven.
packages/agent-core/test/config/configs.test.ts Tests TOML parse/serialize for new config fields.
packages/agent-core/src/session/subagent-host.ts Routes delegated subagents through new model/effort resolvers; documents /btw exception.
packages/agent-core/src/session/rpc.ts Implements set/getSubagentModel and set/getSubagentThinking RPC methods with flag gating.
packages/agent-core/src/session/index.ts Persists and rehydrates live subagent overrides from session metadata.
packages/agent-core/src/rpc/core-impl.ts Wires new subagent RPC methods into the core RPC surface.
packages/agent-core/src/rpc/core-api.ts Adds the new session-level RPC payload/result types.
packages/agent-core/src/flags/registry.ts Registers dual-model-routing experimental flag metadata.
packages/agent-core/src/config/toml.ts Adds new fields to scalar TOML serialization.
packages/agent-core/src/config/schema.ts Adds new config + patch schema fields for subagent defaults.
docs/zh/configuration/env-vars.md Documents the new env var for enabling the experiment.
docs/zh/configuration/config-files.md Documents new config keys and behavior notes (incl. /btw exception).
docs/en/configuration/env-vars.md Documents the new env var for enabling the experiment.
docs/en/configuration/config-files.md Documents new config keys and behavior notes (incl. /btw exception).
apps/kimi-code/test/tui/components/dialogs/model-scope-selector.test.ts Adds tests for the new model scope picker component.
apps/kimi-code/test/tui/components/chrome/footer.test.ts Adds tests for conditional subagent badge rendering.
apps/kimi-code/test/tui/commands/experiments.test.ts Extends experiment toggle tests to cover subagent state sync paths.
apps/kimi-code/src/tui/types.ts Extends AppState with subagentModel and subagentThinkingEffort.
apps/kimi-code/src/tui/kimi-tui.ts Hydrates subagent model/effort from SessionStatus during session view reload.
apps/kimi-code/src/tui/controllers/auth-flow.ts Initializes/clears subagent model/effort during auth/login/logout flows (flag-aware).
apps/kimi-code/src/tui/components/index.ts Exports the new dialog component.
apps/kimi-code/src/tui/components/dialogs/model-scope-selector.ts Implements the main/subagent scope picker used by /model when flag is on.
apps/kimi-code/src/tui/components/chrome/footer.ts Adds subagent badge rendering logic (flag-aware).
apps/kimi-code/src/tui/commands/config.ts Updates /model to select scope first and adds subagent model picker + persistence logic.
.changeset/dual-model-routing.md Adds minor changeset entries describing the new feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +39 to +53
const mainName = modelDisplayName(opts.currentModel, opts.availableModels[opts.currentModel]);
let subagentName: string;
if (opts.currentSubagentModel !== undefined && opts.currentSubagentModel.length > 0) {
const model = modelDisplayName(
opts.currentSubagentModel,
opts.availableModels[opts.currentSubagentModel],
);
const effortSuffix =
opts.currentSubagentThinkingEffort !== undefined && opts.currentSubagentThinkingEffort.length > 0
? ` (effort: ${opts.currentSubagentThinkingEffort})`
: '';
subagentName = `${model}${effortSuffix}`;
} else {
subagentName = '(inherits main model)';
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 142292e — when the model is inherited but an effort is configured, the label now reads (inherits main model, effort: high). Covered by a new test in model-scope-selector.test.ts.

Comment on lines +659 to +661
private persistSubagentOverrides(): void {
const custom: Record<string, unknown> = { ...this.metadata.custom };
if (this.subagentModelAlias !== undefined) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is a false positive for plain objects: { ...undefined } is a no-op in JS (produces {}), so { ...this.metadata.custom } with custom === undefined does not throw — it yields an empty object. The field is also typed as a required Record<string, any> (SessionMeta.custom, session/index.ts:140) and initialized to {} (session/index.ts:195). I added a regression test (setSubagentModel persists when session metadata has no custom map) that explicitly sets custom: undefined and asserts the setter rebuilds the map instead of throwing.

@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: 86e052d17e

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const effortChanged = config.defaultSubagentThinkingEffort !== effort;
if (!modelChanged && !effortChanged) return false;
await host.harness.setConfig({
...(modelChanged ? { defaultSubagentModel: alias } : {}),

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 Strip env-only aliases before saving subagent defaults

When KIMI_MODEL_* is active, the picker can include the synthetic __kimi_env_model__ alias, and this new persistence path writes that alias directly into default_subagent_model. Unlike defaultModel, the write path does not restore/drop env-injected subagent defaults, so choosing the env model here commits default_subagent_model = "__kimi_env_model__" to config.toml; after restarting without those env vars, delegated subagents resolve an unconfigured alias and fail. Please handle the env alias the same way the main default is stripped/restored before saving.

Useful? React with 👍 / 👎.

@kassieclaire kassieclaire Jul 21, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed in 142292e. stripEnvModelConfig now mirrors the defaultModel handling for defaultSubagentModel: when it points at the env alias, the value is restored from config.raw['default_subagent_model'] (or dropped if raw has none) instead of being persisted. Covered by three new tests in env-model.test.ts (restore-from-raw, drop-when-no-raw, keep-non-alias).

* `defaultModel` (the main-agent model). Only takes effect while the
* flag is on; otherwise subagents inherit the parent's live model.
*/
defaultSubagentModel: z.string().optional(),

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 Clear subagent defaults when removing provider models

This new defaultSubagentModel alias also needs to participate in provider removal. I checked removeKimiProvider in packages/agent-core/src/rpc/core-impl.ts: it deletes all model aliases for the provider and clears defaultModel when it pointed at one of them, but it never clears defaultSubagentModel. If a user removes a provider that owned the subagent default, the stale alias remains in config; with dual-model-routing enabled and no live override, the next delegated subagent uses that missing alias and fails model/provider resolution. Please clear this field alongside defaultModel when its alias is removed.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 142292e. removeKimiProvider now clears defaultSubagentModel alongside defaultModel when the removal deletes its model alias. Covered by a new test in runtime.test.ts that seeds both defaults, removes the provider owning the subagent model, and asserts the subagent default is cleared on the returned config and absent from the on-disk config.toml.

- Scope picker: show the configured subagent thinking effort even when
  the model is inherited, instead of a plain "(inherits main model)"
  label that dropped the effort suffix.
- stripEnvModelConfig: restore default_subagent_model from raw when it
  points at the runtime-only env alias (mirroring default_model), so
  saving via /model while KIMI_MODEL_* is active cannot persist
  __kimi_env_model__ to config.toml.
- removeKimiProvider: clear default_subagent_model when the removal
  deletes its model alias (mirroring default_model), so a stale alias
  cannot fail provider resolution on the next delegated spawn.

Tests: scope-selector inherited-effort label, env-alias restore/drop/keep
for the subagent default, provider removal clearing the subagent default.
@kassieclaire
kassieclaire force-pushed the feat/dual-model-routing branch from c437157 to 142292e Compare July 21, 2026 05:50
Replace placeholder values (262144 / 131072) with the real context
window sizes from models.dev: kimi-k3 = 1,048,576, glm-5.2 = 1,000,000.
Both are 1M-class models; the old numbers reflected stale data.
The footer badge was gated on model-alias distinctness only, hiding two
real routing differences:

- Same alias, different thinking effort: subagents run at a different
  effort than the main agent — now surfaced via the existing effort
  suffix ("Kimi K3 · high").
- Same underlying model via a different provider (e.g. kimi-k3 on
  managed:kimi-code vs opencode-go): the badge now shows a provider
  prefix ("opencode-go/Kimi K3") so the routes are distinguishable.

The distinctness check now compares three dimensions: alias, provider,
and thinking effort. When all three match the main agent the badge
stays hidden as before.
@kassieclaire

kassieclaire commented Jul 21, 2026

Copy link
Copy Markdown
Author

Badge now surfaces effort-only and provider-only routing differences

Why this commit (f9473d4c) exists. The earlier footer implementation gated the subagents: badge purely on model-alias string equality (subagentAlias !== state.model). That hid two real routing scenarios the feature is meant to make visible:

  1. Same model, different thinking effort. A user keeps both the main agent and subagents on the same model (e.g. kimi-k3) but sets a higher/lower subagent effort. The badge was hidden even though subagents behave differently. Now surfaced via the existing · <effort> suffix (e.g. Kimi K3 · high).

  2. Same model name, different provider. Two aliases can point at the same underlying model served by different providers — e.g. the main agent uses kimi-k3 via managed:kimi-code, subagents use kimi-k3-opencode via opencode-go. The badge appeared (aliases differ) but showed only "Kimi K3", indistinguishable from the main route. The badge now prepends the provider id when it differs from the main model's provider (e.g. opencode-go/Kimi K3), making the two routes visually distinct.

What changed in the gate. The distinctness check now compares three independent dimensions — alias, provider, and thinking effort. The badge is shown when any of the three differs from the main agent; it stays hidden only when all three match (i.e. subagents are genuinely identical to the main agent).

Tests added (footer.test.ts):

  • Same kimi-k3 alias for both main and subagent, low vs high effort → badge renders Kimi K3 · high.
  • Same underlying model (kimi-k3) via different providers (managed:kimi-code vs opencode-go) → badge renders opencode-go/Kimi K3.

# Conflicts:
#	docs/en/configuration/config-files.md
… thinking effort

Port the dual-model-routing experimental feature to the v2 agent engine.
When the dual-model-routing flag is enabled, delegated subagents (Agent
tool spawn/resume, AgentSwarm spawn/resume) run on a dedicated model and
thinking effort instead of inheriting the parent's. The /btw side-question
agent always inherits (cache affinity). Resolution chain: session metadata
override -> [subagent] config default -> parent inheritance. Inert when the
flag is off.

v2 engine (packages/agent-core-v2):
- Flag: register dual-model-routing via registerFlagDefinition (same
  id/env/default/surface as v1)
- Config: extend [subagent] section with defaultSubagentModel and
  defaultSubagentThinkingEffort
- Service: new Session-scoped ISubagentRoutingService that resolves the
  effective model/effort, persists live overrides to session metadata
  (custom.subagentModelAlias / custom.subagentThinkingEffort), and loads
  them on construction
- Hooks: Agent tool and Swarm service route model+thinking through the
  service on spawn and resume (realignChildModel now syncs both fields)
- Tests: 16 new tests covering flag off/on, override set/clear, config
  default priority, metadata persistence, and load-on-construction

Protocol (packages/protocol):
- Add subagent_model and subagent_thinking_effort to
  sessionAgentConfigSchema and sessionStatusResponseSchema

Server (packages/agent-core-v2/app/sessionLegacy):
- Wire subagent_model / subagent_thinking_effort through
  applyAgentConfig and assembleStatus via ISubagentRoutingService

Web UI (apps/kimi-web):
- Types, wire mapper, and state composable for the subagent model
- Composer subagent model pill (shown only when dual-model-routing flag
  is enabled), with inline model picker and clear button
- i18n strings (en + zh)

@Jiliac Jiliac 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.

A few findings from reading the branch, each verified against the code. Posting inline; hope they're useful.

// No-op guard: skip the RPC round-trips and status flash when neither the
// alias nor the effort actually changed.
if (
alias === host.state.appState.subagentModel &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The no-op guard in performSubagentModelSwitch returns before the persist block, so a session-only choice can never be promoted to a saved default. Repro: pick a subagent model "session only", then re-open /model → Subagents → same model/effort → "persist as default". The guard (alias === appState.subagentModel && effort === appState.subagentThinkingEffort) is true, so it prints "Already using … for subagents." and persistSubagentModelSelection is never reached; config.toml stays empty. The main-model flow handles exactly this case: performModelSwitch still persists on a runtime no-op and reports "Saved … as default." Suggest only short-circuiting when persist === false.

: undefined;
const mainEntry = state.availableModels[state.model];
const mainProvider = mainEntry ? effectiveModelAlias(mainEntry).provider : undefined;
const isDistinct =

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The footer subagents: badge can never render the "inherits main model, different effort" state. The badge's stated contract (f9473d4) is that it shows when any of alias, provider, or effort differs, but both branches of isDistinct require subagentAlias !== undefined, and subagentModelDisplayName returns '' for an undefined alias. This state is reachable purely from config: with default_subagent_thinking_effort = "high" and no default_subagent_model, Session.getSubagentThinkingEffort() falls back to the config default independently of the model (session/index.ts:638-640), so subagents genuinely run the main model at high, yet no badge ever renders. The scope picker already acknowledges this state with its "(inherits main model, effort: high)" label.

Related: the "same alias, different provider" clause at line 343 is dead code, since an identical alias keys into the same availableModels entry and the providers can't differ there; the provider prefix only ever fires on the different-alias branch.

return this.getSubagentThinkingEffort() ?? parentThinkingEffort;
}

async setSubagentModel(alias: string | undefined): Promise<void> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The agent-core-v2 write path is missing two guards that the agent-core implementation has, so the two engines diverge on the same client input.

  1. Flag gating: agent-core's setSubagentModel / setSubagentThinking throw CONFIG_INVALID when dual-model-routing is off, but in agent-core-v2 the flag is only checked in the getters. sessionLegacyService.applyAgentConfig calls the setters unconditionally, and they persist custom.subagentModelAlias into session metadata even when the flag is off, so the write silently succeeds and then activates later if the flag is turned on.

  2. Alias validation: agent-core eagerly resolves the alias so the next spawn fails fast, but agent-core-v2 stores any string, and AgentProfileService.bind() doesn't compensate (modelCatalog.get() returning undefined doesn't throw). A typo'd alias therefore binds silently, fails at the first LLM call, and recurs on every resume because it's persisted.

/** True when the connected server has the dual-model-routing experimental
* flag enabled (read from the daemon config's `experimental` map). Gates the
* subagent model UI in the composer. */
const dualModelRoutingEnabled = computed<boolean>(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

dualModelRoutingEnabled reads config.experimental['dual-model-routing'] from GET /config, which kap-server builds via toConfigResponse(config.getAll()), a pure projection of the persisted [experimental] section with no env overlay. A user who enables the feature via KIMI_CODE_EXPERIMENTAL_DUAL_MODEL_ROUTING (as env-vars.md documents) gets a server that fully routes subagents, but the web pill never renders. The web UI ends up divergent from both the TUI (which gates on resolved flag state) and the server's own behavior: subagents route, with no indication or control in the web UI. Suggest surfacing resolved flags on the wire, or gating on the status response instead.

this.emitSubagentSpawned(parent, agentId, profileName, runOptions);
try {
child.config.update({ modelAlias: parent.config.modelAlias });
child.config.update({ modelAlias: this.resolveSubagentModelAlias(parent), thinkingEffort: this.resolveSubagentThinkingEffort(parent) });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Probably an intentional consistency win, but worth a deliberate call since it technically breaks the "flag off = unchanged everywhere" claim: before this PR, resume and retry realigned only modelAlias to the parent; they now also push thinkingEffort, which with the flag off resolves to the parent's current effort. If the user changed the main agent's effort between spawn and resume, the resumed subagent's effort now follows it, where it previously kept its spawn-time value. If strict flag-off invariance is intended, the effort update could be omitted when getSubagentThinkingEffort() returns undefined.

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.

Feature: Allow configuring a separate model for subagents

3 participants