feat(agent-core-v2): per-workspace subagent model bindings#2034
feat(agent-core-v2): per-workspace subagent model bindings#2034Yorha9e wants to merge 7 commits into
Conversation
🦋 Changeset detectedLatest commit: ba15016 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 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b85ab2f404
ℹ️ 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".
| try { | ||
| result = await deps.question.request( | ||
| { questions: [{ question: item.question, header: 'Subagent', options: item.options }] }, | ||
| { agentId: deps.agentId, signal: deps.signal }, |
There was a problem hiding this comment.
Abort instead of dismissing cancelled binding prompts
When the parent Agent call is cancelled while this prompt is pending, SessionQuestionService handles the supplied signal by dismissing the question and resolving null, not by throwing. askOne then returns undefined, so launch falls through to create and emit a subagent before runAgentTurn observes the aborted signal, leaving a spawned child with no run/terminal event. Check the signal after request returns and rethrow the abort instead of treating it as a user dismissal.
Useful? React with 👍 / 👎.
| let result: QuestionResult; | ||
| try { | ||
| result = await deps.question.request( | ||
| { questions: [{ question: item.question, header: 'Subagent', options: item.options }] }, |
There was a problem hiding this comment.
Provide unique IDs for binding questions
This new direct question request does not set id or toolCallId, so SessionQuestionService.requestId falls back to question:${Date.now()}. When multiple unbound Agent calls ask in the same millisecond, the second pending interaction overwrites the first in the pending map and one tool call can hang forever. Include a unique request id for each model/effort prompt.
Useful? React with 👍 / 👎.
| binding_slot: z | ||
| .string() | ||
| .optional() |
There was a problem hiding this comment.
Hide binding_slot while the experiment is disabled
When subagent-model-selection is off, this parameter is still advertised and accepted in the tool schema (and AgentSwarm adds the same unguarded parameter), changing the LLM/API surface even though resolution ignores it. Keep the new argument out of the schema/description or reject it unless the flag is enabled.
AGENTS.md reference: AGENTS.md:L62-L64
Useful? React with 👍 / 👎.
| /** | ||
| * Context for one interactive ask-once point: `slot` is set when the ask | ||
| * concerns a named binding slot rather than a subagent type, and | ||
| * `missingModel` is set when a stored binding references a model alias that |
There was a problem hiding this comment.
Move non-header comments into the module header
This package allows comments only in the top-of-file block; this new doc block, along with the following helper/interface doc blocks in the file, sits beside declarations and violates the package comment convention. Fold any externally useful responsibility into the file header or remove the local narration.
AGENTS.md reference: packages/agent-core-v2/AGENTS.md:L13-L13
Useful? React with 👍 / 👎.
Resolve [subagent.<type>] / [subagent-slot.<name>] bindings from .kimi-code/local.toml at subagent spawn (slot > type > inherit), add the Agent tool binding_slot parameter, and carry the effective model and thinking effort on subagent.spawned. Gated by the subagent-model-selection experiment; behavior is unchanged while the flag is off.
…lots While the subagent-model-selection experiment is on, resuming a subagent no longer realigns it to the caller's model, and AgentSwarm accepts a batch-wide binding_slot applied to every item-spawned member. Flag-off behavior is unchanged.
While the subagent-model-selection experiment is on, the Agent tool asks the user once when an unbound subagent type or requested slot is spawned, or when a stored binding references a vanished model alias, and records the answer (including an explicit keep-inheriting choice) in .kimi-code/local.toml. Non-interactive clients and dismissals fall back to silent inheritance; the swarm path never asks.
…y resume Validate the resumed child's bound model alias before a sticky resume so a stale binding surfaces a clear error instead of failing mid-turn (v1 parity with resolveChildModel). Also cover aliases that exist but cannot be resolved in binding warnings, and warn when inherit=true is set alongside an ignored model or thinking_effort.
When a binding slot was explicitly requested and its interactive ask was dismissed, the resolver fell through to the type section and fired a second, type-level question in the same spawn. A dismissal is a 'stop asking' signal: the type binding still applies when configured, but the type-level ask is now suppressed whenever a slot was explicitly requested.
Fold PR-added narration into file headers per the package comment convention, rethrow the abort when a cancelled binding prompt resolves as a dismissal, give binding questions unique request ids, and stop advertising binding_slot in the Agent/AgentSwarm tool schemas while the subagent-model-selection experiment is disabled. Pin the tools-snapshot tests to a disabled flag stub so they pass in either environment.
8cb835e to
ba15016
Compare
Related Issue
Related to #1928 — that PR brings per-workspace subagent model bindings to the v1 engine (
packages/agent-core). This PR ports the same capability topackages/agent-core-v2, where no equivalent exists today.Problem
On the v2 engine, every subagent spawn copies the caller's model and thinking effort verbatim (
Agenttool, swarm, and resume paths alike). Users who organize work around specialist subagents cannot route a subagent type (or a specific spawn) to a different model — something the v1 engine can do via #1928's[subagent.<type>]bindings in.kimi-code/local.toml. As kap-server sessions run on v2, the v1 mechanism covers a shrinking share of real usage.What changed
All behavior is gated behind the
subagent-model-selectionexperiment flag (envKIMI_CODE_EXPERIMENTAL_SUBAGENT_MODEL_SELECTION, default off). With the flag off, behavior is byte-for-byte identical to today, including wire payloads.IProjectLocalConfigService(theworkspaceLocalConfigdomain, renamed by feat: agent-core-v2 permission/workspace refactors and transcript durability #2021 — this branch is rebased past it and follows the new naming) learns to read and write[subagent.<type>]and[subagent-slot.<name>]sections in.kimi-code/local.toml, preserving unrelated TOML content (same file format as the v1 side, so one workspace config serves both engines).session/subagent/bindingResolution.ts) resolves the effective model/thinking for each spawn with precedence slot > type binding > inherit caller. Stale aliases (not configured or no longer resolvable) warn and fall through;inherit: truerecords an explicit keep-inheriting choice.Agenttool and the swarm scheduler — resolve through it. TheAgenttool gains an optionalbinding_slotparameter (the model is named by user configuration, never by the LLM);AgentSwarmaccepts a batch-widebinding_slotfor item-spawned members.ISessionQuestionServiceand records the answer — including an explicit "keep inheriting" choice. One spawn asks at most once; non-interactive clients and dismissals fall back to silent inheritance; the swarm path never asks.subagent.spawnedcarries the effectivemodelAlias/thinkingEffortwhen the flag is on (the protocol schema already reserved these fields; the kap-server zod schema gains them additively).Seven commits: the binding MVP, sticky resume + swarm slots, ask-once, three follow-up fixes (fail-fast resume validation, at-most-one-ask per spawn, review feedback), and a mechanical rename-follow for #2021 (
workspaceLocalConfig→projectLocalConfig).Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update (experimental, off by default; docs can land when the flag graduates).Verification:
typecheckandlint:domaingreen for@moonshot-ai/agent-core-v2(921 files) andtypecheckgreen for@moonshot-ai/kap-server; the agent-core-v2 vitest run on this branch matches the clean-mainbaseline failure-for-failure on this Windows host (only the pre-existing platform failures remain — the #2021-era ones included — and the branch adds 64 passing tests on top).