From 69c0f68ac80d2818d975550843a2ce714c02dc79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Thu, 19 Mar 2026 18:01:25 -0600 Subject: [PATCH] fix: wire escalateTier into auto-loop retry path (#1505) (#1519) When a unit fails and is retried, the model tier is now escalated (light -> standard -> heavy) if dynamic routing is enabled and escalate_on_failure is not explicitly disabled. This connects the existing escalateTier() function, which was fully implemented and tested but never called at runtime. Co-authored-by: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-loop.ts | 12 ++++++++- .../extensions/gsd/auto-model-selection.ts | 25 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/resources/extensions/gsd/auto-loop.ts b/src/resources/extensions/gsd/auto-loop.ts index 3b333ba95..9ba92275f 100644 --- a/src/resources/extensions/gsd/auto-loop.ts +++ b/src/resources/extensions/gsd/auto-loop.ts @@ -456,6 +456,7 @@ export interface LoopDeps { prefs: GSDPreferences | undefined, verbose: boolean, startModel: { provider: string; id: string } | null, + retryContext?: { isRetry: boolean; previousTier?: string }, ) => Promise<{ routing: { tier: string; modelDowngraded: boolean } | null }>; startUnitSupervision: (sctx: { s: AutoSession; @@ -1182,6 +1183,14 @@ export async function autoLoop( unitId, }); + // Detect retry and capture previous tier for escalation + const isRetry = !!( + s.currentUnit && + s.currentUnit.type === unitType && + s.currentUnit.id === unitId + ); + const previousTier = s.currentUnitRouting?.tier; + // Closeout previous unit if (s.currentUnit) { await deps.closeoutUnit( @@ -1335,7 +1344,7 @@ export async function autoLoop( ); } - // Select and apply model + // Select and apply model (with tier escalation on retry) const modelResult = await deps.selectAndApplyModel( ctx, pi, @@ -1345,6 +1354,7 @@ export async function autoLoop( prefs, s.verbose, s.autoModeStartModel, + { isRetry, previousTier }, ); s.currentUnitRouting = modelResult.routing as AutoSession["currentUnitRouting"]; diff --git a/src/resources/extensions/gsd/auto-model-selection.ts b/src/resources/extensions/gsd/auto-model-selection.ts index 70be37671..70b2fa7e1 100644 --- a/src/resources/extensions/gsd/auto-model-selection.ts +++ b/src/resources/extensions/gsd/auto-model-selection.ts @@ -7,8 +7,9 @@ import type { ExtensionAPI, ExtensionContext } from "@gsd/pi-coding-agent"; import type { GSDPreferences } from "./preferences.js"; import { resolveModelWithFallbacksForUnit, resolveDynamicRoutingConfig } from "./preferences.js"; +import type { ComplexityTier } from "./complexity-classifier.js"; import { classifyUnitComplexity, tierLabel } from "./complexity-classifier.js"; -import { resolveModelForComplexity } from "./model-router.js"; +import { resolveModelForComplexity, escalateTier } from "./model-router.js"; import { getLedger, getProjectTotals } from "./metrics.js"; import { unitPhaseLabel } from "./auto-dashboard.js"; @@ -33,6 +34,7 @@ export async function selectAndApplyModel( prefs: GSDPreferences | undefined, verbose: boolean, autoModeStartModel: { provider: string; id: string } | null, + retryContext?: { isRetry: boolean; previousTier?: string }, ): Promise { const modelConfig = resolveModelWithFallbacksForUnit(unitType); let routing: { tier: string; modelDowngraded: boolean } | null = null; @@ -60,8 +62,27 @@ export async function selectAndApplyModel( const shouldClassify = !isHook || routingConfig.hooks !== false; if (shouldClassify) { - const classification = classifyUnitComplexity(unitType, unitId, basePath, budgetPct); + let classification = classifyUnitComplexity(unitType, unitId, basePath, budgetPct); const availableModelIds = availableModels.map(m => m.id); + + // Escalate tier on retry when escalate_on_failure is enabled (default: true) + if ( + retryContext?.isRetry && + retryContext.previousTier && + routingConfig.escalate_on_failure !== false + ) { + const escalated = escalateTier(retryContext.previousTier as ComplexityTier); + if (escalated) { + classification = { ...classification, tier: escalated, reason: "escalated after failure" }; + if (verbose) { + ctx.ui.notify( + `Tier escalation: ${retryContext.previousTier} → ${escalated} (retry after failure)`, + "info", + ); + } + } + } + const routingResult = resolveModelForComplexity(classification, modelConfig, routingConfig, availableModelIds); if (routingResult.wasDowngraded) {