commanders run on Sonnet, not the account default

every new commander came up on whatever the owner last picked in a chat —
Fable, currently. that default is chosen for interactive work, and a
commander is the opposite: long-running, mostly repeating one settled job,
and started by a button rather than by somebody thinking about which model
they want.

COMMANDER_DEFAULT_MODEL, in both places, because the two ways of starting a
commander do not share code — the server's /commanders/new route and the
detail component's own first turn.

per-surface defaults belong in settings eventually. "commanders default to
Sonnet" is the decision; this is where it lives until there is a better
place for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
2026-08-25 05:06:39 +00:00
co-authored by Claude Opus 5
parent af41144689
commit facf1a5f9e
4 changed files with 40 additions and 9 deletions
+14
View File
@@ -47,6 +47,20 @@ import { snapshotClaudeUsage } from './usage-store';
// Default model when no user preference is set
export const DEFAULT_MODEL = 'claude-code';
/**
* What a commander runs on, regardless of the account's default.
*
* A commander is long-running and mostly does the same settled thing over and over, so it is the one
* surface where the account default — chosen for interactive work — is the wrong answer. Pinned here
* rather than left to the picker so a commander created today does not depend on what the owner last
* selected for a chat.
*
* Temporary in the sense that per-surface defaults belong in settings; permanent in the sense that
* "commanders default to Sonnet" is the decision, and this is where it lives until there is a better
* place for it.
*/
export const COMMANDER_DEFAULT_MODEL = 'claude-code/sonnet';
// Harness selection: the `claude-code` provider runs through the Claude sidecar; every other provider
// (opencode/anthropic/openai/… — all `providerID/modelID` ids) runs through the OpenCode server.
const isClaudeModel = (model: string): boolean => model.startsWith('claude-code');
+3 -2
View File
@@ -28,7 +28,7 @@ import {
type ChatIdentity,
type ClaudeSessionSummary,
} from '../chat/claude-sessions';
import { collectRunningTasks, startClaudeTurn, getUserDefaultModel, DEFAULT_MODEL } from '../chat/websocket';
import { collectRunningTasks, startClaudeTurn, COMMANDER_DEFAULT_MODEL } from '../chat/websocket';
import {
listCommandHqCategories,
createCommandHqCategory,
@@ -373,7 +373,8 @@ commandHqRouter.post('/commanders/new', async (ctx) => {
if (credentialError) console.warn(`[command-hq] ${row.dirName}: no credential minted — ${credentialError}`);
const sessionId = randomUUID();
const model = (await getUserDefaultModel(user.id)) || DEFAULT_MODEL;
// Not the account default: a commander runs on Sonnet — see COMMANDER_DEFAULT_MODEL.
const model = COMMANDER_DEFAULT_MODEL;
const outcome = await new Promise<{ ok: true; transcriptId: string } | { ok: false; error: string }>((resolve) => {
// 30s covers a slow spawn (member login checks, a cold sidecar) without holding an HTTP connection