Repository navigation
A new Pi chat names the model it will run on its first frame - #26821
Merged
Merged
Conversation
…s configured default ACP's rule (only a new session that restored no model pick and was sent none runs its config's default; an effort it picked says nothing) moves beside the catalog entry types so other agents' adapters report through the same step. An empty listing (a signed-out agent) now says nothing instead of "no listed model", so it can never retire a saved default.
…t model Pi reads `.pi/settings.json` in the chat's folder and loads project extensions from `.pi/extensions` (and the `extensions`/`packages` lists). A settings file that sets defaultProvider, defaultModel, defaultThinkingLevel, modelThinkingLevels or enabledModels, loads extension code, or can't be parsed counts as the project picking the model; extension code may register providers or set the model itself. Key names come from Pi 1.0.4's own settings manager and docs, read without running Pi.
…i starts on Pi's model listing marks no model as the one it runs, so a new Pi chat with no saved pick showed "Model" and then changed to the model its session reported. A new Pi session (not one resumed or forked from a saved conversation) that restored no model pick and was sent none now reports the model and, unless it picked one, the thinking level it started on, through the same host step as Grok, OpenCode and OMP. The host saves them as the account's default behind the project-config check, so the next new Pi chat names them on its first frame. A signed-out Pi, which lists no model, saves nothing.
…t ran The saved list takes each model's effort menu from the newer listing. Pi's `--list-models` menu stops at high, while a chat's own menu adds xhigh or max for models whose thinking map allows them. After a background re-list, a learned xhigh was no longer in the menu, so the first frame dropped it. When the configured effort is only in the other listing's menu, that menu is kept.
…rted on A Pi chat re-derived its configured default from Pi's current state on every options read, so a model an extension switched to mid-chat (extensions can call setModel) was saved as the account default, and the next new chat named the wrong model. The choice is now computed once, after start-up restores the chat's saved picks, and every later read reports that snapshot. A pick made in the chat no longer matters to it. Tests cover an extension switch, a pick made in the chat, a forked chat, and a learned xhigh surviving a newer re-list.
brennanb2025
marked this pull request as ready for review
October 9, 2026 20:00
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.
ELI5
When you open a new Pi chat without having picked a model, the model pill first says "Model" and then, a few seconds later, changes to the model Pi actually started on (for example "GPT-6 · high"). That jump happens on every new Pi chat. Claude, Grok, OpenCode and OMP stopped doing this in #26407: once one chat has started with no pick, Orca remembers which model the agent runs by default and shows it on the next new chat straight away. Pi was left out of that change (listed there as temporary). This PR gives Pi the same behaviour.
What Changed
What you see
.pi/settings.jsonpicks a model or thinking level, or that has project extensions (which can choose a model themselves), keeps the "Model" placeholder until its chat reports, as before. Orca can't know what that project's settings resolve to, so it doesn't guess.xhighormaxthinking level stays named after Orca re-lists Pi's models in the background, even though Pi's model listing only shows levels up tohigh. The "Pi isn't signed in" notice for that chat (Show "Pi isn't signed in" in native chat before the first send #26743) is unchanged.Mechanism
unpickedSessionConfiguredChoice), and both ACP and Pi call it. The rule: only a new session (not one resumed or forked from a saved conversation) that restored no model pick and was sent none reports the model it runs; it reports the thinking level too unless that was picked. It reports nothing when the agent runs no model (fix(acp): start chats for agents that report no model #26587's no-model chats). It also reports nothing when the agent lists no models. That check is only a safeguard: the host never saves an empty listing, so it never learned or forgot a default from one before this PR either, and Grok/OpenCode/OMP behave exactly as before.--session/--fork) finishes starting and restoring the chat's saved picks, it applies the shared rule once to what Pi then reports, and keeps that answer. Every later options read hands the host that same answer alongside the current model list, exactly as Grok/OpenCode/OMP report theirs. A model changed later in the chat, by a pick or by an extension calling Pi'ssetModel, can't change it. The host saves it as the account's configured default only after the workspace check below passes, so the next new chat's saved-list read already names it..pi/settings.jsonin the chat's folder (Orca checks from the folder up to its repository root, a wider check than Pi's own). It counts as the project choosing the model if it setsdefaultProvider,defaultModel,defaultThinkingLevel,modelThinkingLevelsorenabledModels(Pi starts on the firstenabledModelsmatch), listsextensionsorpackages, or can't be parsed. A non-empty.pi/extensions/folder also counts, because extension code can register providers or set the model. Settings for permissions, tools, themes and so on don't count. Key names come from Pi 1.0.4's own settings manager, startup model code (findInitialModel) and settings docs, read from the installed package without running Pi. Pi's own account folder (PI_CODING_AGENT_DIR) is account config, not a project layer.--list-modelsmenu stops athigh; a chat's own menu addsxhigh/maxfor models whose thinking map allows them. When the learned effort is only in the other listing's menu, the saved list now keeps that menu, so a background re-list can't drop it (before, the first frame fell back to no named effort). This is in the shared merge, but only changes anything when a newer menu lacks a learned effort.Why
This reuses #26407's single path instead of adding a Pi-specific one: the same no-pick rule, the same host save step, the same project-config gate and the same saved-list preload. A Pi-only variant would have been a second copy of the rule to keep in sync. Pi's model listing (
--list-models) can't be the answer because it marks no model as the default, and Pi's startup choice depends on account settings, saved credentials and environment API keys that only Pi resolves, so learning it from a chat Pi itself started is the only source that matches what Pi runs.Orca never sends Pi a model it didn't pick: picks go through Pi's RPC
set_model, which (per Pi's source) doesn't rewrite Pi's saved default, so Orca's own picks never change what Pi starts on.Differences from the common pattern (the composer draws its pills once, from data the client already holds)
packageslist keeps the placeholder even if its extensions never touch the model. Safe: that is today's behaviour; the cost is one transition in those projects, never a wrong model name..pi/settings.json(which Pi ignores) still keeps the placeholder. Follow-up: read Pi's trust store if this shows up in practice.Linked Issue
Follow-up to #26407 (its "Pi names no model until a chat reports one" temporary item). No separate issue.
Visual Proof
Pending coordinator QA. A hidden capture needs a built app plus a stand-in Pi on an isolated profile, which this lane didn't run. The before/after is the same as #26407's Grok/OpenCode/OMP case: the second new Pi chat should open showing the model and thinking level the first one ran, with no "Model" frame.
Testing
New and updated tests, run by file name (macOS):
src/main/pi/rpc-configured-default.test.ts(new, runs on the Node runtime): through Pi's real listing parser, session adapter, catalog store and service, with a scripted Pi child. Pi's listing names nothing (placeholder); a chat with no pick runsopenai/gpt-6athigh(not the listing's first row); the next new chat's first frame, in that workspace and another, namesopenai/gpt-6 · high. A workspace whose.pi/settings.jsonpicks a model keeps the placeholder; a chat in a workspace with.pi/extensionsteaches nothing; a chat that restored a model pick, resumed a conversation or was forked from one teaches nothing; a chat whose model an extension switches after start, or where you pick another model, teaches the model it started on; a learnedxhighsurvives a newer--list-modelsre-list; a signed-out Pi (empty model list) saves nothing and doesn't erase a learned default.agent-project-model-override.test.ts: each Pi key,extensions, invalid JSON and.pi/extensionscount; a BOM-prefixed settings file that sets only tools/theme, another agent's settings, and Pi's own account folder don't.use-structured-agent-session-options.learned-default.test.tsx: adds Pi to the renderer case (second new chat's first frame names the model and effort, no intermediate frame).Fail without the change, checked by putting back the old source and running the tests:
main): the learned-default case, the signed-out case's "doesn't erase" half (it fails onmainonly because Pi learned nothing there; the empty-list safeguard itself isn't what it tests), and the "ignores Pi settings that pick no model" override case.xhighcase fails with the merge restored (no effort named).main.Also passing: ACP options, ACP host configured-default, OpenCode/OMP learned-default, Pi adapter, options, capture replay, registration, probe, turn races, prompt delivery, launch, catalog service/store/unavailable/prewarm, first-frame, Claude/Codex configured-default and effort-at-rest, discovery freshness and catalog-application tests (29 files, 315 tests), and the SQLite runtime boundary test.
pnpm tc:node,pnpm tc:web, mobilepnpm typecheckand test-typecheck ratchet, and oxlint/oxfmt on changed files pass.orca-ci-checks: 22 of 23 pass; the one failure isaudit:code-quality:native, the existing import-cycle warnings inmobile/src/transportandmobile/src/terminal/document(no file of this PR is involved).CI on
b2dfc088715: all 16 run checks pass (static analysis and typecheck, all 5 test shards, relay integration, runtime on macOS/Ubuntu/Windows, packaging); 17 skipped as not selected for this change.Not verified: the running app, a real Pi CLI, SSH/WSL hosts beyond the shared code paths (the host step and workspace check already run on the execution host).
AI Disclosure
Review
Agent skill upstream boundary
Notes
Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance
readdirand one small file read per workspace check for Pi.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)