Repository navigation
feat(agent): switch models mid-task - #1408
Merged
Merged
Conversation
A task was locked to its model after the first message (#622), because a local agent could not follow a change of context window or vision. The Pi runtime follows a switch itself now: `set_model` records it, clamps the thinking level and tells extensions, `read_image` follows the model's vision, and the check before each prompt compacts against the new window. - Runtime: a run on another model switches the task's session, loaded or not; a cold task opens on the model it last used and then switches, so every switch is recorded the same way. The task's stored model follows from the start of the run. A switch takes the account's thinking default as it is now, as Pi's switch takes its settings' default. - `set_task_model` takes a selection for any task's next run; the store no longer refuses a model patch after the first message. - Thinking: a started task previews and keeps a pick for the model it will switch to, without switching before its next run. - ACP: the model selector keeps every model after the first prompt, a started task accepts another model, and a task whose model is gone loads on the default instead of being refused. - Desktop: the model menu lists every model in a task, with no lock icon or note. A pick applies from the next message; the selector follows a switch the task makes here or on another surface. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A run that switches a started task to another model records a Maple notice entry, "Switched to Kimi K3", and shows it live as the run starts. It renders through the existing notice row, as "Stopped by user" does, so reopening the task shows it from the stored entries. ACP keeps notices out of its stream and shows the model in its selector. Pi's own `model_change` entries are not projected: telling a switch from the first selection or a new window for the same model needs the previous model, which the incremental and chunked timeline rebuilds do not carry, and the store has no catalog names. The catalog's label precedence moves from the composer to the runtime, so the notice and the model menu name models the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ndow Pi compacts with the session's model, in one request. Before the first prompt on a model with a smaller window, its threshold check compacts a history the new model may not be able to read: a 500k-token DeepSeek task moved to a 262k model sends the summary request to the 262k model. A Maple extension on Pi's `before_compact` takes that case. When the branch's last complete reply comes from another model with a larger window, that model writes the summary with Pi's own `compact`. The model is read from the stored reply, so this holds across restarts. When the catalog no longer offers it, or its summary fails, the new model summarizes the history trimmed to its window, oldest messages first. The extension returns no summary otherwise: on the same model, after a switch to a window at least as large, when the new model takes the whole history, and when both attempts fail. Pi then compacts as before. The pi crates are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- UPSTREAM.md: the pi crates mirror upstream Pi, bugs included; only blockers are fixed locally, review findings that match upstream are recorded and left, and Maple behavior goes in `maple-agent`, usually as an extension. - README and the scenario checklist describe switching models mid-task and the compaction after a switch to a smaller window. - The catalog's reason for leaving aliases out no longer rests on the model lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The switch notice was published live without the identity of its stored row. A compaction during the same run reloads the task's history and merges the run's live rows by that identity, so the desktop showed the notice twice until the task was opened again. The run now links the live row to the notice's entry, as it links message rows, and the row carries the stored identity once it commits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A Maple Work task can now switch models between messages, on the desktop, over ACP and on remote computers, which share the runtime. Based on
mastera3e19782.Why the lock existed, and why it can go
set_modelrecords amodel_change, clamps the thinking level and tells extensions;read_imagefollows the model's vision, and text-only models get image placeholders and descriptions;What changed
Runtime
set_task_modelaccepts a selection for a started task's next run, and the store no longer refuses that patch.ACP
session/set_config_optionaccepts another model for a started task.session/loadof a task whose model the catalog no longer offers opens it on the default model, which takes over with the next prompt. Before, the load was refused.Desktop
Transcript
model_changeentries are not projected. Telling a real switch apart from a first selection, or from a new window for the same model, needs the previous model. The incremental and chunked timeline rebuilds don't carry it, and the store has no catalog names.Compaction after a switch to a smaller window
agent/switch_compaction.rsis a Maple extension on Pi'sbefore_compact. The pi crates are unchanged: the diff touches nothing undercrates/pi-*.compact, using the parameters Pi uses (retry policy, thinking level, session id).UPSTREAM.mdnow records the rule this follows. The pi crates mirror upstream, bugs included, and are changed only for a blocker. Review findings that match upstream are recorded and left. Maple behavior goes inmaple-agent, usually as an extension.Known limitations
compactalso appends those lists to the summary text, which the next summary updates.Validation
just ciinapps/maple-agentpassed on the final commit: format, Clippy with-D warningsfor every feature set, and the workspace and headless test suites. 2,868 tests passed, 0 failed (10 ignored).just ci-lint,just ci-test) on each of the five commits; the last run passed 2,514 tests, 0 failed (8 ignored).model_changes, and the notice shown live and stored; a selection for a started task; thinking for a pending model;session/set_config_option;Live run
Local
maple-dev-envstack: OpenSecret, billing and the Continuum proxy. Pro fixture account, a debug build of this branch, and a scratch project in/tmp/modelswitch-qa.For (c) and (d), a local-only patch to
services/opensecret/src/model_config.rswas used. It was never committed and was reverted afterwards:enabled: false.(a) DeepSeek task switched to Kimi K3. A started task's menu offers every model:
DeepSeek read the codename from a file. After the switch, Kimi K3 answers from that earlier context:
(b) Image task switched to text-only GLM 5.3. DeepSeek saw the image natively. After the switch, GLM called
read_image, which ran in describe mode (it takescontext) and returned the vision helper's description. GLM's two requests show in the local Continuum proxy's log; the DeepSeek and Kimi turns don't.Restart. After quitting and relaunching the app, switched tasks open on their current model, with their "Switched to" line:
Thinking and context follow the pick. With DeepSeek picked but not yet sent in a Kimi task, the thinking menu offers DeepSeek's levels (off, low, high, xhigh, max):
The context ring of a ~60k-token task fills against the picked model's window:
(c) Shrink path. A DeepSeek task read five ~12k-token files, then switched to GLM 5.3 (patched to 49,152). The compaction before the prompt was summarized by DeepSeek, and GLM answered from the summary:
The proxy logged no GLM request until the reply, two seconds later.
(d) Fallback. A second DeepSeek task held six ~10k-token messages, seeded over ACP. DeepSeek was then disabled on the local server, and the task switched to GLM 5.3:
The oldest message was left out, GLM took the ~120 KB summary request, and it answered correctly:
Found live and fixed in the last commit: when a run that switched also compacted, the desktop showed the switch line twice until the task was reopened. The live notice now carries its stored row's identity, and a regression test checks it.
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.