fix(triage): drop defaultModel from triage candidate pool

Operator's settings.json defaultModel is for general dispatch (typically
a cheap/flash pick — gemini-3-flash-preview in current config). Mixing
it into the triage candidate pool gave it a chance to win on cost
tie-break against agentic-better but pricier options from the explicit
enabledModels allowlist.

Triage is agentic-heavy; restrict its candidate pool to the operator's
enabledModels (kimi-coding/* + minimax/* + zai/* + …) and let the
agentic-weighted router pick. Also fixes the wildcard expansion path
which was calling a non-existent ai.getModelsByProvider — now correctly
uses ai.getModels(provider).

Dogfood confirms: router now picks kimi-coding/kimi-for-coding
(agentic 90) instead of gemini-3-flash-preview (operator default).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mikael Hugo 2026-05-14 16:40:19 +02:00
parent 98d1b2b258
commit 192129a69e

View file

@ -381,8 +381,14 @@ async function resolveTriageModel(providerModelString) {
/**
* Read the operator's enabledModels allowlist from ~/.sf/agent/settings.json
* and expand "provider/*" wildcards against @singularity-forge/ai's MODELS
* catalog. Always also include defaultProvider/defaultModel from the same
* settings so the operator's chosen default is never silently dropped.
* catalog.
*
* Intentionally does NOT include defaultProvider/defaultModel. The operator's
* `defaultModel` is for general dispatch (typically a flash/cheap pick like
* gemini-3-flash-preview); triage is an agentic-heavy task and should pick
* from the explicit `enabledModels` allowlist (typically kimi/minimax/zai).
* Mixing the cheap default into the triage candidate pool gives it a chance
* to win on cost tie-break against an agentic-better but pricier option.
*
* Returns null on any failure callers fall back to TRIAGE_FALLBACK_CANDIDATES.
*/
@ -395,13 +401,8 @@ async function readOperatorTriageCandidates() {
const enabled = Array.isArray(settings?.enabledModels)
? settings.enabledModels
: [];
const defaultProvider = settings?.defaultProvider;
const defaultModel = settings?.defaultModel;
const result = new Set();
if (defaultProvider && defaultModel) {
result.add(`${defaultProvider}/${defaultModel}`);
}
// Expand wildcards by walking the pi-ai MODELS catalog for matching
// provider entries. Exact "provider/modelId" entries pass through.
@ -415,11 +416,12 @@ async function readOperatorTriageCandidates() {
result.add(entry);
continue;
}
// Wildcard: enumerate models registered under this provider that
// have an agentic profile we can score. getModelsByProvider is the
// canonical pi-ai accessor; if it isn't available we just skip.
if (typeof ai.getModelsByProvider === "function") {
const models = ai.getModelsByProvider(provider) ?? [];
// Wildcard: enumerate models registered under this provider via
// pi-ai's getModels(provider). If the function isn't available
// in the loaded build we just skip (caller falls back to
// TRIAGE_FALLBACK_CANDIDATES).
if (typeof ai.getModels === "function") {
const models = ai.getModels(provider) ?? [];
for (const m of models) {
if (m?.id) result.add(`${provider}/${m.id}`);
}