Apply turn options to the live Pi session - #2221
Open
SawyerHood wants to merge 1 commit into
Open
Conversation
The Pi bridge read options.model and options.reasoningLevel only when it constructed a session (thread/start, resume, fork). handleTurnStart and handleTurnSteer ignored params.options, so a model or reasoning level picked after the session existed never reached Pi: every later turn, and any manual compaction, ran on the construction model until something rebuilt the session. The runtime stopped diffing execution options in #1640 and expects each bridge to reconcile them itself; Codex and Claude do, Pi did not. Reconcile on every turn/start and turn/steer before dispatch, ahead of both the prompt and /compact branches. The SDK's setModel and setThinkingLevel are deliberately not used: both persist the selection as the user's default in ~/.pi/agent/settings.json, which construction never does. The bridge applies the lower-level pieces instead (auth check, agent state, session-file model_change/thinking_level_change, thinking-level re-clamp). A model that does not resolve or has no credentials fails the turn with that error instead of silently keeping the old model. Fixes #2160 Co-Authored-By: Claude <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.
What was wrong
In a Pi thread, picking a different model (or reasoning level) in the composer changed what bb recorded on
client/turn/requestedbut not what Pi used. The Pi bridge readoptions.model/options.reasoningLevelonly in the session-construction mapping (thread/start,thread/resume,thread/fork);handleTurnStartandhandleTurnSteerignoredparams.optionsentirely. Since #1640 the runtime no longer diffs execution options and classifies every change as "live" (it rides the next turn command and the bridge is expected to reconcile), so nothing ever called into Pi with the new model and nothing rebuilt the session. Every later turn, and any manual/compact(which is just anotherturn/start), kept running on the construction model until a session rebuild (bb thread stop, daemon restart, bridge recovery) happened to occur. Codex and Claude bridges got the per-turn reconciliation in #1640; Pi did not. Report: https://get-bb.github.io/reports/issues/2160.htmlWhat changed
packages/agent-runtime/src/pi/bridge/sdk-session.ts: newPiSdkSession.applyTurnOptions({ model, thinkingLevel }). It resolves the model with the existingresolveConfiguredModelsemantics (provider prefix authoritative, ambiguous bare ids rejected), auth-checks it throughmodelRuntime.checkAuth, swapsagent.state.model, appendsmodel_changeto the session file, then re-clamps the thinking level to the (possibly new) model withclampThinkingLeveland appendsthinking_level_changewhen it changed. Unchanged values are a no-op. It throws when the model cannot be resolved or has no credentials.AgentSession.setModel/setThinkingLevel. Both persist the selection as the user's default into the global~/.pi/agent/settings.json(setDefaultModelAndProvider/setDefaultThinkingLevel), which session construction never does; a per-thread bb pick must not rewrite the user'spiCLI defaults. The lower-level SDK pieces are used instead, same as the SDK's own construction path. The one thing this skips relative tosetModelis themodel_selectextension hook (the extension runner is private onAgentSession).packages/agent-runtime/src/pi/bridge/bridge.ts:applyTurnOptionsOrFailruns at the top ofhandleTurnStart(before both the/compactand prompt branches, so the summarization request also goes to the selected model) and inhandleTurnSteer. A failed resolution fails the turn with the error message (-32000) instead of silently keeping the old model.packages/agent-runtime/src/pi/session-params.ts:buildPiTurnOptionsmaps canonical execution options to the turn-applicable subset, reusing the sametoPiThinkingLevelfloor construction uses.No wire change between server and host daemon (the fix is entirely inside the Pi bridge), so no
HOST_DAEMON_PROTOCOL_VERSIONbump. No CLI/knob changes. I did not add a generic conformance rule: the conformance suite observes only wire messages and cannot tell which model a provider actually ran a turn on without a provider-specific probe, so the guard lives in the Pi bridge tests.How you verified
Tests added (all 8 fail on
mainwith the test changes alone, pass with the fix):packages/agent-runtime/src/pi/bridge/__tests__/bridge.test.ts: drives the real bridge through the JSON-RPC harness with a model-tracking Pi session stand-in.turn/startwith a different model + reasoning level thanthread/startprompts on the new model/level without rebuilding; a/compactturn carrying a new model compacts on it;turn/steerapplies the model before steering; an unresolvable model fails the turn withFailed to resolve Pi model "unsupported/model"and leaves the session's model untouched.packages/agent-runtime/src/pi/bridge/__tests__/sdk-session.test.ts:applyTurnOptionsswitches model + level and appends to the session file whilesetModel,setThinkingLevel,settingsManager.setDefaultModelAndProvider, andsettingsManager.setDefaultThinkingLevelare never called; re-clamps the level tooffwhen the new model has no reasoning; is a no-op for unchanged options; rejects a model whose provider has no credentials and keeps the current model.Commands:
pnpm exec turbo run typecheck test --filter=@bb/agent-runtime(green, 11 Pi suites + the new tests), prettier on touched files.Live, against my own dev instance of this branch with the real Pi SDK (
github-copilotmodels): spawned a Pi thread ongithub-copilot/gpt-5-mini --reasoning-level low("Reply only with ok."), thenbb thread tell --model github-copilot/grok-4.6 --reasoning-level high. The Pi bridge session file shows the second assistant message served bygithub-copilot/grok-4.6with amodel_change+thinking_level_change: highentry appended before it, and the bb event log shows nothread/identitybetween the turns (no rebuild):~/.pi/agent/settings.jsonwas byte-identical (md5) before and after, so the user's Pi defaults were not rewritten.Fixes #2160