Skip to content

feat: add session-scoped secondary model with Alt+S picker option - #3463

Draft
7Sageer wants to merge 3 commits into
mainfrom
secondary-model-session-override
Draft

feat: add session-scoped secondary model with Alt+S picker option#3463
7Sageer wants to merge 3 commits into
mainfrom
secondary-model-session-override

Conversation

@7Sageer

@7Sageer 7Sageer commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Related Issue

N/A — maintainer PR raised from an internal design discussion (multiple TUI windows sharing secondary_model), no tracking issue.

Problem

[secondary_model] lives in the user-level config.toml, and the v2 engine watches that file and hot-reloads it in every process. The subagent model binding was resolved per spawn from that live global config, so editing the secondary model in window A silently changed the model used by window B's already-running session (observed in practice: the next AgentSwarm batch picked up the other window's selection). Symmetrically, there was no way to choose a secondary model for just the current window — /secondary-model could only persist to the shared file.

What changed

Adopts the established /model pattern (runtime session state + explicit persist):

  • Engine (agent-core-v2): SessionSubagentService now owns a session-scoped secondary model binding. It is seeded from the config pool default at first use — a running session no longer follows global hot reloads — and an explicit selection is persisted in the session directory (secondary-model.json), so it survives resume. Spawn resolution order is now: tool argument > force > session selection > pool default > inherit. Agent / AgentSwarm / TowerSpawn all resolve through it, and the tool description's [default] marker reflects the session value.
  • SDK: new Session.setSecondaryModel() / Session.getSecondaryModel(). The v2 client goes through the in-process session scope (same path as setThinking); the v1 client rejects the setter with NOT_IMPLEMENTED and reads back undefined.
  • TUI: the /secondary-model picker gains the existing Alt+S session-only shortcut (offered on v2 only); Enter now applies the choice to the current session first, then persists it as the default for future sessions.
  • Docs (zh/en): resolution order and picker behavior updated in config-files.md and slash-commands.md.

Tests: new engine cases (seed isolation from mid-session config changes, override precedence, force precedence, implicit pool, persistence round-trip), an SDK end-to-end case including resume, and TUI command cases for both picker paths.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue (N/A — maintainer PR, see 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.

Running windows no longer follow secondary_model changes made elsewhere:
the session captures the pool default at first use, and an explicit
session-scoped selection (Alt+S in /secondary-model) overrides it and is
persisted in the session directory so it survives resume. Enter now also
applies the choice to the current session before persisting it as the
default for future sessions.
@changeset-bot

changeset-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9d30aa0

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

This PR includes changesets to release 3 packages
Name Type
@moonshot-ai/kimi-code-sdk Minor
@moonshot-ai/kimi-code Minor
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

@7Hanrui

7Hanrui commented Sep 2, 2026

Copy link
Copy Markdown

@codex review

@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: 8a3dcd6976

ℹ️ 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".

"@moonshot-ai/kimi-code-sdk": minor
---

Add `Session.setSecondaryModel()` and `Session.getSecondaryModel()` for the session-scoped secondary model on the v2 engine. The v1 client rejects the setter with `NOT_IMPLEMENTED` and the getter reads back `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.

P1 Badge Reduce the SDK changeset entry to one sentence

This entry contains two sentences, while the repository requires every changeset to be one short user-facing sentence stating only what changed. Combine the v1 behavior into the first sentence so the generated release note conforms to the mandated format.

AGENTS.md reference: AGENTS.md:L85-L86

Useful? React with 👍 / 👎.

Comment on lines +88 to +92
const parameters = exposesSubagentModelChoice(
this.config,
this.flags,
this.subagents.secondaryModel,
)

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 Pass the session default into the Agent description

When a session-only model is selected, this makes the Agent schema expose the model parameter, but the description still calls buildSubagentModelDescriptions without this.subagents.secondaryModel. With no configured pool it therefore advertises no model choices at all, and with a pool it continues marking the persisted default rather than the active session default; pass the same session value to the description builder as AgentSwarmTool does.

Useful? React with 👍 / 👎.

Comment on lines +284 to +290
const configPool = resolveSubagentModelPool(config);
const pool: SubagentModelPool =
configPool ??
({
defaultModel: sessionDefault,
models: sessionDefault === undefined ? {} : { [sessionDefault]: '' },
});

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 Include an out-of-pool session default in model descriptions

If [secondary_model.models] exists and the user chooses a different configured alias with Alt-S, configPool wins here and the session alias is not added to pool.models. The subsequent code only emits the default when it is a pool key, so AgentSwarm omits the model actually used by default and displays no [default] marker; build an effective pool that represents the session selection even when it is absent from the persisted pool.

Useful? React with 👍 / 👎.

args.kind === 'reviewer' && !isSubagentModelForced(this.config)
? 'primary'
: undefined,
this.subagents.secondaryModel,

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 Await the restored secondary model before tower spawning

On a resumed session, SessionSubagentService loads secondary-model.json asynchronously and the session lifecycle does not await its ready promise. If a tower spawn reaches this line before that read finishes, the synchronous getter seeds from the current global config and the first worker can launch on the wrong model; resolve the value through the awaited getSecondaryModel() path before constructing the binding, as planSpawn already waits for restoration.

Useful? React with 👍 / 👎.

… await

- build the effective subagent model pool from the config pool plus the
  session-scoped default, so an out-of-pool session selection stays
  requestable and shows the [default] marker (Agent tool included)
- tower spawn awaits the restored session secondary model instead of
  reading the pre-load synchronous getter
- compress the sdk changeset entry to one sentence
@7Hanrui

7Hanrui commented Sep 2, 2026

Copy link
Copy Markdown

@codex review

@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: 035aa98732

ℹ️ 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 +90 to +92
if (!this.secondaryModelSeeded) {
this.secondaryModelSeeded = true;
this.secondaryModelSeed = resolveSubagentModelPool(this.configService)?.defaultModel;

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 Capture the default when the session starts

When a session is created but this getter has not run yet—for example, an SDK caller creates a session before sending its first prompt—the default is not captured until line 92. If another process edits the shared configuration in between, this already-started session adopts the new default, contrary to the documented guarantee that changes affect only newly started sessions. Seed the value during session service construction or materialization rather than on first read.

Useful? React with 👍 / 👎.

Comment on lines +647 to +650
onSessionOnlySelect: host.engineV2
? ({ alias }) => {
host.restoreEditor();
void performSecondaryModelSave(host, alias, false);

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 Hide the session-only action when force is enabled

With [secondary_model].force = true, this callback is still offered for every v2 session, so Alt-S successfully stores the selected alias and reports that new subagents will use it. However, resolveSubagentBinding returns the forced model before consulting the session value, making the action a silent no-op. Suppress or reject the session-only action when force is active instead of displaying a false success message.

Useful? React with 👍 / 👎.

Comment on lines +132 to +135
this.states.set(sessionSecondaryModelKey, model);
await this.documentStore.set(this.sessionContext.metaScope, SECONDARY_MODEL_DOC_KEY, {
model,
});

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 Keep persisted and in-memory model updates consistent

The in-memory state is published before an unqueued asynchronous document write. If the write fails, the setter rejects while getSecondaryModel() and subsequent spawns still use the supposedly failed value; if two SDK calls overlap, the writes can complete out of order and leave memory on one model while a resumed session loads the other. Serialize updates and publish the new state only once its corresponding write succeeds.

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.

2 participants