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) <noreply@anthropic.com>
This commit is contained in:
TÂCHES 2026-03-19 18:01:25 -06:00 committed by GitHub
parent eb2939760f
commit 69c0f68ac8
2 changed files with 34 additions and 3 deletions

View file

@ -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"];

View file

@ -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<ModelSelectionResult> {
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) {