From 3f2babb5d1fd3c70578d887b7831e309910b05b4 Mon Sep 17 00:00:00 2001 From: Mikael Hugo Date: Wed, 13 May 2026 02:40:41 +0200 Subject: [PATCH] fix(auto): block refusing executor model temporarily to force escalation on retry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When classifyExecutorRefusal detects an executor refusal, the model is now temporarily blocked (1-hour TTL) via the existing blocked-models mechanism. This ensures that on retry — whether automatic or manual — the router skips the refusing model and the tier-escalation path in selectAndApplyModel picks a higher-tier alternative. This satisfies AC1 of self-feedback entry sf-mp3bm6u0-2fskt8. AC2 (refusal pattern detection) was already satisfied by the existing apology-no-tools pattern in classifyExecutorRefusal. Refs: sf-mp3bm6u0-2fskt8 --- .../extensions/sf/auto/phases-unit.js | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/sf/auto/phases-unit.js b/src/resources/extensions/sf/auto/phases-unit.js index d9e5e6ee4..e32b1c96c 100644 --- a/src/resources/extensions/sf/auto/phases-unit.js +++ b/src/resources/extensions/sf/auto/phases-unit.js @@ -38,6 +38,7 @@ import { readAutonomousSolverState, recordAutonomousSolverMissingCheckpointRetry, } from "../autonomous-solver.js"; +import { blockModel } from "../blocked-models.js"; import { resumeAutoAfterProviderDelay } from "../bootstrap/provider-error-resume.js"; import { debugLog } from "../debug-logger.js"; import { PROJECT_FILES } from "../detection.js"; @@ -759,12 +760,30 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) { } catch { // best-effort } + // Temporarily block the refusing model so the router skips it on retry. + // This satisfies AC1 of sf-mp3bm6u0-2fskt8: the executor model is + // escalated because the blocked model will be excluded from selection. + try { + const refusedProvider = s.currentUnitModel?.provider ?? ""; + const refusedId = s.currentUnitModel?.id ?? ""; + if (refusedProvider && refusedId) { + blockModel( + s.basePath, + refusedProvider, + refusedId, + `executor-refused: ${refusal.pattern}`, + { expiresAt: Date.now() + 60 * 60 * 1000 }, // 1 hour + ); + } + } catch { + // best-effort — blocking must not break the refusal handler + } try { appendAutonomousSolverCheckpoint(s.basePath, { unitType, unitId, outcome: "blocked", - summary: `Executor (${executorModel}) refused the task. Pattern: ${refusal.pattern}. Repair-prompting the same model cannot produce progress; escalate the executor model or unblock this unit manually.`, + summary: `Executor (${executorModel}) refused the task. Pattern: ${refusal.pattern}. The model has been temporarily blocked and will be skipped on retry; escalate the executor model or unblock this unit manually.`, completedItems: [], remainingItems: [ `Re-run ${unitType} ${unitId} with a more capable executor model — current routing selected an incapable model.`,