Repository navigation
Models: a fallback model is for that run only, never the saved choice - #2465
Merged
Merged
Conversation
AbirAbbas
added a commit
that referenced
this pull request
Nov 6, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas
added a commit
that referenced
this pull request
Nov 6, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas
force-pushed
the
workstreams/l-fix9-e1
branch
from
November 6, 2026 14:44
f0825c6 to
3ecb8bb
Compare
On a local launch whose configured model is not listed (no key for its provider, say, with only a local Ollama running), the terminal picks the first listed model so the conversation can run. It skipped the profile's talk row, but it switched through the ordinary model setter, so the fallback became the conversation's saved model: the agent reopened on it (also in the app, where the configured provider worked), and errand routes that fall to the conversation's model followed it. The chat manual already said automatic replacements do not overwrite a saved preference. The conversation now has a run-only model door (session UseModelForNow, carried over the linked-local wire, older engines keep the old setter). Requests run on the fallback while the saved model, provider, identity and main/fallback pair keep naming the configured choice; the app reports the configured model. The terminal's availability reconcile, the implicit credit-default follow, the move after disconnecting the running model's service, and the linked-local source refresh use it, and every later reconcile returns to the configured model as soon as it is listed again. An explicit choice, including choosing the fallback that is already running, and a fresh service connect still save as before. A chat continued from another computer keeps its existing replacement rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eued words and new chats keep the saved choice Review round: - The terminal asks the engine for the run-only model through its ordered off-loop command, as every engine door must; sends wait for the acknowledged switch, and an explicit pick queued behind it supersedes it. - Renaming a service relabels the saved choice and a run-only model separately, so a rename never saves the fallback. - An explicit choice clears a run-only model word still queued for the next request. - start_chat with no model gives the new chat its parent's configured model; the new chat applies its own availability reconcile. - The implicit credit-default follow is restored as it was: it is the build's default moving, not an availability fallback. Existing tests gain only the acknowledgment step the asynchronous switch needs; no assertion changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…odel switch Review round 2: - A parked message (text and attachments) stays queued while its chat's run-only switch is pending or refused, and is sent once on the acknowledged model; pending switches travel with the chat when the window moves to another. - A deferred service rename is bound to the chat it was made for and is retired by a later explicit pick, so it never saves into another chat or over a newer choice. - Two new test fixtures gain the provider base URL their sessions require. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AbirAbbas
force-pushed
the
workstreams/l-fix9-e1
branch
from
November 6, 2026 15:13
3ecb8bb to
ed52c29
Compare
…he window holding it Review round 3: holding sends at the surface while a run-only switch was unacknowledged kept missing send roads (a send deferred behind a replay, a hidden chat's queued send). The surface no longer holds anything: it shows the replacement at once, and every engine call that starts a turn (a plain send, an attachment send, a follow-up, an errand send) waits inside its own command for that agent's switch still on the ordered door line, so the engine always sees the switch before the message. A refused switch still lets the message go; the next reconcile retries. A hidden chat reconciles when it comes back to the front, as before. Existing tests keep only the acknowledgment steps the asynchronous switch still needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 4: resuming a turn stopped by a takeover started the turn without waiting for the chat's run-only switch still on the door line. It now waits inside its command like every other turn start; a cancelled wait leaves the resume for later, a refused switch still allows one resume. 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.
Summary
On a local terminal launch whose configured model is not in the listed catalog (for example no key for its provider while a local Ollama is running), the terminal picks the first listed model so the conversation can run. It did not write the profile's talk row, but it switched through the ordinary model setter, so the fallback became the conversation's saved model. The agent then reopened on it (in the desktop app too, where the configured provider worked), and errand routes that fall to the conversation's own model reported it. The chat manual already said "Automatic replacements do not overwrite your saved preference."
Change: a fallback is for that run only.
session.Agent.UseModelForNow/ConfiguredModel(new, additive): requests use the fallback while the saved model, provider,ModelIdentity.Selected/Modelsand any main/fallback pair keep naming the configured choice. Execution receipts (requested/active/last) still record what actually ran. An explicitSetModel/SetConversationModelsclears the run-only model and saves as before, including when the person chooses the fallback that is already running.internal/remote); an older engine without it keeps the old setter behaviour, and no other wire error falls back to a saving write.internal/tui3): the availability reconcile and the move after disconnecting the running model's service use the run-only door, asked through the ordered off-loop door line like every engine door. The surface shows the replacement at once and holds nothing; every engine call that starts a turn (plain, attachment, follow-up and errand sends, held and replayed ones included) waits inside its own command for that agent's switch still on the line, so the engine always sees the switch before the message. A refused switch still lets the message go and the next reconcile retries; an explicit pick queued behind it supersedes it. A hidden chat reconciles when it comes back to the front, as before. Renaming a service relabels the saved choice and a run-only model separately, so a rename never saves the fallback; a deferred rename stays with its own chat and a later explicit pick retires it. Every later reconcile (catalog arrival, connection, turn end) prefers the configured model and returns to it as soon as it is listed. A fresh service connect still moves the conversation onto that service and saves it; that is the person's intent, andTestConnectingAServiceMovesTheConversationOntoItsPreferredModel/TestTheMovedModelIsTheOneTheNextLaunchOpensOnare unchanged.start_chatwithout a model gives the new chat its parent's configured model; the new chat applies its own availability reconcile.agents.list/agents.open).--hostdoors, and the continued-chat replacement for a chat opened on another computer where its model is not set up (that one is persisted by design; later local opens rely on it).No profile row, tier, role, task-model or app setup write was fed by an availability decision; the trace is in the PR's tests (
profile bytes unchanged).Validation contract → tests
TestRunOnlyModelSurvivesSavingAsTheConfiguredChoice,TestRunOnlyModelKeepsConfiguredProviderWhenDisconnected,TestRunOnlyModelCrossesTheWireWithoutReplacingTheSavedPair,TestCatalogRecoveryUsesConfiguredModelOnTheNextRequest,TestDisconnectMoveStaysTemporaryUntilExplicitlyChosen,TestLinkedLocalSourceRefreshKeepsRunOnlyModelTemporary,TestAppReopensConfiguredModelAfterRunOnlyReplacementTestListedModelReplacementKeepsProfileAndReturnsToConfiguredModel,TestSetupCanExplicitlyChooseTheRunOnlyModel,TestServiceRenamePreservesConfiguredAndRunOnlySelections,TestDeferredRenameStaysWithItsConversationAcrossASwitch,TestLaterExplicitChoiceRetiresTheDeferredRename,TestRunOnlyModelFailureDoesNotCallThePersistentSetter,TestAppReopensConfiguredModelAfterRunOnlyReplacementTestAppReopensConfiguredModelAfterRunOnlyReplacement,TestRunOnlyModelSurvivesSavingAsTheConfiguredChoiceTestCatalogRecoveryUsesConfiguredModelOnTheNextRequest,TestListedModelReplacementKeepsProfileAndReturnsToConfiguredModel,TestExplicitModelChoiceClearsRunOnlyModelTestExplicitChoiceSupersedesQueuedRunOnlyModel,TestExplicitPickSupersedesAnUnacknowledgedRunOnlySelection,TestPendingExplicitPickSavesAfterLeavingTheConversation,TestStartChatInheritsConfiguredModelFromTemporaryParent,TestRequestedChatSavesConfiguredModelBeforeItsOwnAvailabilityReconcile,TestExplicitModelChoiceClearsRunOnlyModel,TestExplicitPairSelectionClearsTheRunOnlyMain,TestSetupCanExplicitlyChooseTheRunOnlyModel,TestServiceConnectionPersistsIdleAndDeferredMovesTestRemoteCatalogDoesNotSelectARunOnlyModel,TestRunOnlyModelFallsBackToTheOlderEngineSetter,TestLegacyRunOnlySelectionUsesTheOffLoopDoor,TestRunOnlySelectionOrdersTheSendOffTheLoop,TestParkedMessageFollowsItsOrderedModelAtStreamClose,TestReplayDeferredSendFollowsItsOrderedModel,TestHeldSendFollowsItsOwnOrderedModel,TestIdleFollowUpFollowsTheOrderedSelection,TestAlternateTurnStartFollowsTheOrderedSelection,TestRefusedRunOnlySelectionStillSendsOnce,TestCancelingSendEndsTheModelWait,TestHiddenChatReconcilesAvailabilityOnlyWhenItReturns,TestCreditDefaultSurvivesListedModelSubmission, existingTestNoEngineDoorIsAskedFromTheUpdateLoop(unchanged, same debt)Existing tests changed
No assertion changed. Because the run-only switch now lands asynchronously, a few existing tests that drive a replacement add one step that lets it land before their unchanged assertions (
modeldefault_test.go,modelservices_test.godisconnect,provideraccess_test.go).TestConnectingAServiceMovesTheConversationOntoItsPreferredModelandTestTheMovedModelIsTheOneTheNextLaunchOpensOnkeep their assertions: a fresh connect still saves.Review round
A fresh review found one blocker (the new door was asked from the update loop, which
TestNoEngineDoorIsAskedFromTheUpdateLoopforbids) and four majors (a service rename saved the fallback; a queued run-only word outlived an explicit pick; the credit-default follow fought the reconcile;start_chatinherited the live fallback). All fixed as above; the credit follow is restored to its old behaviour rather than made run-only. Review 2 found two majors in a send gate the fix had added (parked sends, deferred renames across chats); review 3 found two more send roads that gate missed (replay-deferred sends, hidden chats). The gate was replaced by per-agent send ordering, which needs no surface state.🤖 Generated with Claude Code