Skip to content

toolChoice unwired at v2 HEAD — Kimi K3 skips MCP tool calls under default "auto" #37672

Description

@paperview

Summary

At v2 HEAD (v2-test, 529d55b1c3), toolChoice is fully plumbed through the internal LLM layer (packages/core/src/aisdk.ts:421, packages/opencode/src/session/llm.ts:47/234/319, native-request/native-runtime, provider-specific lowerings) but is not wired to either the config layer or the HTTP prompt API. packages/opencode/src/session/prompt.ts:1285 still hard-codes it:

toolChoice: format.type === "json_schema" ? "required" : undefined,

PromptInput (packages/opencode/src/session/prompt.ts:1499) exposed via the HTTP POST /session/:id/prompt endpoint (packages/opencode/src/server/routes/instance/httpapi/groups/session.ts:302) has no toolChoice field either.

Net effect: providers always get "auto" for regular turns. Models that follow auto conservatively (Kimi K3 in particular) frequently answer from context instead of invoking a tool that is available.

Not a duplicate — an update

Filing fresh against v2 HEAD, referencing the above.

Reproduction (v2 daemon + runtime MCP)

Environment: opencode v2 daemon (v2-test, 529d55b1c3), kimi-for-coding/k3, a remote MCP registered via POST /mcp exposing one tool (update_field).

Client sends a user turn like:

"My favorite color is blue. Record it now via update_field (field=\"favorite_color\", value=\"blue\") before replying."

Even with a strict system prompt ("You MUST call update_field before replying. This is mandatory."), Kimi K3 replies "Got it — blue is a great choice" with zero tool calls. Anthropic models on the same MCP entry invoke the tool reliably.

Daemon logs confirm the MCP entry is connected, initialize + tools/list complete cleanly, and update_field is in the returned tool list. The model just declines to call it.

Directly hitting our controller with a synthetic tools/call mutates state correctly, proving the transport is fine — the model simply never emits the call.

Ask

Two-part, both small:

1) Config path. Re-land the fix from #32521/#32518 against v2: add tool_choice: \"auto\" | \"required\" | \"none\" to the agent v2 config schema, map it onto AgentInfo.toolChoice, and use it at prompt.ts:1285 instead of the current ternary. Backwards compatible when unset.

2) HTTP path. Add optional toolChoice: \"auto\" | \"required\" | \"none\" to PromptInput (prompt.ts:1499) so external orchestrators calling POST /session/:id/prompt (runtime MCP bridges like ours, subagent runners) can force tool invocation per-turn without editing global config. Precedence: request > agent config > current default.

The plumbing already exists internally; both changes are essentially the missing edges of a wire that's otherwise complete.

Why this matters more than it used to

v2 promotes runtime MCP as a core feature (#37308). Runtime MCP orchestrators are exactly the callers that need per-turn toolChoice: \"required\" — they know the current turn is a form-fill / structured-mutation step where a tool call is not optional. Without the HTTP surface, orchestrators can't reliably drive Kimi (or any model that's conservative under auto) through MCP-mediated flows.

Willing to help

Happy to open a PR against v2 if there's interest — the diff is small and follows the shape of the two closed PRs. Would appreciate a signal that a review will land before the auto-cleanup fires again.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions