Skip to content

feat: replace the secondary-model experiment with the [subagent] model pool - #2700

Draft
7Sageer wants to merge 7 commits into
mainfrom
feat/subagent-model-pool
Draft

feat: replace the secondary-model experiment with the [subagent] model pool#2700
7Sageer wants to merge 7 commits into
mainfrom
feat/subagent-model-pool

Conversation

@7Sageer

@7Sageer 7Sageer commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

No tracking issue — the problem is explained below.

Problem

The experimental secondary model ([secondary_model] + KIMI_SECONDARY_MODEL / KIMI_SECONDARY_EFFORT env vars + the agent-file model_preference field) gives subagents a single fallback model and is wired through SDK session-apply APIs (Session.applyPersistedSecondaryModel, a synthesized __secondary__ model entry). The main agent cannot choose a subagent model per spawn, and the recipe does not fit the v2 engine's config-section architecture.

What changed

v2 engine — declarative subagent model pool

  • [subagent.models] maps configured [models] aliases to the selection hints shown to the main agent; [subagent] default_model names the fallback used when a spawn passes no model. A lone default_model (no models table) forms an implicit single-entry pool — the minimal "secondary model" configuration.
  • The Agent / AgentSwarm tools gain a model parameter (advertised only when a pool exists): any pool alias, or the reserved symbolic primary, which binds the caller's own model and thinking level — pool-alias bindings resolve thinking naturally instead.
  • Pool validation fails loudly (CONFIG_INVALID) at session create / resume / fork on a missing or out-of-pool default_model, an unresolvable alias, or a reserved primary key. Tool descriptions mark [default] / [main model]; the caller's alias stays a normal pool entry because only primary inherits the caller's thinking level.
  • The secondary-model experiment is removed from the v2 engine, the SDK (Session.applyPersistedSecondaryModel, SECONDARY_DERIVED_MODEL_ALIAS), kap-server, and the agent-file model_preference field on v2.

TUI

  • /secondary_model re-added: opens a model picker and persists [subagent] default_model, merging into an existing pool with an empty description. The picker hides the no-op Thinking footer; the reserved primary alias is rejected with a rename hint. New spawns pick the value up without a session restart.

kap-server

  • /api/v1/config accepts and echoes subagent; the snake→camel patch conversion now preserves user-defined map keys under providers / models / experimental / raw (an alias like fast_model previously became fastModel), without leaking preserve mode into a colliding alias's own fields.

Default (v1) engine

  • Unchanged: [secondary_model], its env vars, and model_preference keep working there; the v1 config schema learns subagent.defaultModel / models only so a shared config.toml round-trips — v1 ignores them at runtime.

Docs & changesets: en/zh config-files, tools, and slash-commands pages updated; two changesets (minor for @moonshot-ai/kimi-code and @moonshot-ai/kimi-code-sdk).

Behavior examples

Configuration — minimal form (every subagent binds kimi-hs by default) vs a full pool:

# Minimal: a lone default_model is an implicit single-entry pool
[subagent]
default_model = "kimi-hs"

# Full pool: the main agent picks per spawn
[subagent]
default_model = "kimi-hs"
[subagent.models]
kimi-hs = "fast and cheap; daily refactoring, explanations, small edits"
k3 = "frontend work"
fable = "hard reasoning tasks"

What the main agent sees — the Agent / AgentSwarm tool description gains a model parameter and renders the pool (here the caller itself runs k3):

Available models (pass via model):
- kimi-hs [default]: fast and cheap; daily refactoring, explanations, small edits
- k3 [main model]: frontend work
- fable: hard reasoning tasks
- primary (k3): the main model you are running on, bound with your current thinking level; use it for hard, quality-sensitive subagent tasks

The two k3 lines differ only in thinking inheritance: a pool-alias binding carries no explicit thinking level (the subagent resolves it from global config / the model's default effort), while primary inherits the caller's model and current thinking level.

/secondary_model — interactive-only path to the minimal form:

  • Opens the model picker with the Thinking footer hidden (the selection carries no thinking level); picking a model writes [subagent] default_model.
  • If a [subagent.models] table exists and the picked alias is not in it, the alias is added with an empty description (the engine requires the default to be a pool key); without a table only default_model is written.
  • The reserved alias primary is rejected with a rename hint instead of writing a pool the engine would refuse.
  • No live-apply step: the pool resolves per spawn, so the next subagent dispatch picks the new value up without a session restart.
  • v2 engine only — the default engine ignores [subagent] default_model and keeps resolving subagent models through [secondary_model].

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.

7Sageer added 4 commits August 5, 2026 11:33
Add a declarative subagent model pool to agent-core-v2: [subagent.models]
maps [models] entry ids to selection hints rendered in the Agent/AgentSwarm
tool descriptions, and [subagent].default_model picks the spawn model when
the caller passes none. The tools' model parameter becomes a free-form
alias string (stripped when no pool is configured), description rendering
is caller-aware (primary (alias) [main model]), and a session-start
validation service fails fast with CONFIG_INVALID on a missing/invalid
default_model or an unresolvable pool alias.

Remove the secondary-model experiment from the v2 engine, node-sdk,
kap-server, and the TUI (the /secondary_model command), and drop the
agent-profile modelPreference / model_preference frontmatter field on v2.
The legacy v1 engine keeps the experiment unchanged; v2 ignores leftover
[secondary_model] config silently.
…icker mapping

Deep-review follow-ups to the [subagent.models] pool:

- validate the pool before session materialization (after config.ready)
  and before the fork file copy, so a broken pool no longer leaves
  orphaned session dirs or leaked MCP overlay connections; the
  Session-scope validation service stays as a backstop
- reject the reserved "primary" pool alias at startup, and again
  defensively in resolveSubagentBinding so a pool broken by a runtime
  config edit fails loudly at spawn instead of binding the wrong model
- keep the [default] marker when the caller's own model is the pool
  default (primary (alias) [main model] [default])
- recompile the cached tool-args validator when a tool advertises a new
  schema object (mid-session pool edits no longer hit a stale validator)
- map config.invalid to VALIDATION_FAILED in kap-server's session routes,
  the debug transport mapper, and the catch-all error handler
- hide the v1-synthesized __secondary__ entry from the /model and
  /provider pickers again
- fold per-export doc blocks into file headers per package comment
  conventions; add pre-flight/reserved-key/validator/mapping tests and
  document that create/resume/fork all fail on a broken pool
- v2 engine: a pool-less [subagent] default_model forms an implicit
  single-entry pool — validated at session create/resume/fork like an
  explicit pool, and advertised through the Agent/AgentSwarm model
  parameter.
- Tool descriptions: the caller's own alias is a normal pool entry
  marked [main model]; the primary line stays distinct because only it
  inherits the caller's thinking level.
- TUI: /secondary_model returns, persisting [subagent] default_model
  (merging into an existing pool with an empty description); the picker
  hides the no-op Thinking footer and rejects the reserved primary
  alias.
- kap-server: /api/v1/config accepts and echoes subagent; the
  snake-to-camel patch conversion preserves user-defined map keys under
  providers/models/experimental/raw without leaking preserve mode into
  a colliding alias's own fields.
- v1 config schema learns subagent.defaultModel/models so the shared
  config.toml round-trips; the v1 engine still ignores them at runtime.
- Docs (en/zh) and changesets updated.
@changeset-bot

changeset-bot Bot commented Aug 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c620153

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-sdk Minor
@moonshot-ai/kimi-code 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

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