Fix model fallback across main/background/sync agents

This commit is contained in:
VespianRex
2026-02-19 04:41:00 +02:00
parent d8da89fd5b
commit f5f1d1d4c2
44 changed files with 2500 additions and 605 deletions
+4 -1
View File
@@ -1,5 +1,6 @@
import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types"
import type { ExecutorContext, ParentContext } from "./executor-types"
import type { FallbackEntry } from "../../shared/model-requirements"
import { getTimingConfig } from "./timing"
import { storeToolMetadata } from "../../features/tool-metadata-store"
import { formatDetailedError } from "./error-formatting"
@@ -12,7 +13,8 @@ export async function executeBackgroundTask(
parentContext: ParentContext,
agentToUse: string,
categoryModel: { providerID: string; modelID: string; variant?: string } | undefined,
systemContent: string | undefined
systemContent: string | undefined,
fallbackChain?: FallbackEntry[],
): Promise<string> {
const { manager } = executorCtx
@@ -27,6 +29,7 @@ export async function executeBackgroundTask(
parentAgent: parentContext.agent,
parentTools: getSessionTools(parentContext.sessionID),
model: categoryModel,
fallbackChain,
skills: args.load_skills.length > 0 ? args.load_skills : undefined,
skillContent: systemContent,
category: args.category,
@@ -1,6 +1,7 @@
import type { ModelFallbackInfo } from "../../features/task-toast-manager/types"
import type { DelegateTaskArgs } from "./types"
import type { ExecutorContext } from "./executor-types"
import type { FallbackEntry } from "../../shared/model-requirements"
import { mergeCategories } from "../../shared/merge-categories"
import { SISYPHUS_JUNIOR_AGENT } from "./sisyphus-junior-agent"
import { resolveCategoryConfig } from "./categories"
@@ -16,6 +17,7 @@ export interface CategoryResolutionResult {
modelInfo: ModelFallbackInfo | undefined
actualModel: string | undefined
isUnstableAgent: boolean
fallbackChain?: FallbackEntry[] // For runtime retry on model errors
error?: string
}
@@ -177,5 +179,6 @@ Available categories: ${categoryNames.join(", ")}`,
modelInfo,
actualModel,
isUnstableAgent,
fallbackChain: requirement?.fallbackChain,
}
}
+5 -2
View File
@@ -8,6 +8,7 @@ import { getAgentDisplayName, getAgentConfigKey } from "../../shared/agent-displ
import { normalizeSDKResponse } from "../../shared"
import { log } from "../../shared/logger"
import { getAvailableModelsForDelegateTask } from "./available-models"
import type { FallbackEntry } from "../../shared/model-requirements"
import { resolveModelForDelegateTask } from "./model-selection"
export async function resolveSubagentExecution(
@@ -15,7 +16,7 @@ export async function resolveSubagentExecution(
executorCtx: ExecutorContext,
parentAgent: string | undefined,
categoryExamples: string
): Promise<{ agentToUse: string; categoryModel: { providerID: string; modelID: string; variant?: string } | undefined; error?: string }> {
): Promise<{ agentToUse: string; categoryModel: { providerID: string; modelID: string; variant?: string } | undefined; fallbackChain?: FallbackEntry[]; error?: string }> {
const { client, agentOverrides } = executorCtx
if (!args.subagent_type?.trim()) {
@@ -46,6 +47,7 @@ Create the work plan directly - that's your job as the planning agent.`,
let agentToUse = agentName
let categoryModel: { providerID: string; modelID: string; variant?: string } | undefined
let fallbackChain: FallbackEntry[] | undefined = undefined
try {
const agentsResult = await client.app.agents()
@@ -92,6 +94,7 @@ Create the work plan directly - that's your job as the planning agent.`,
const agentOverride = agentOverrides?.[agentConfigKey as keyof typeof agentOverrides]
?? (agentOverrides ? Object.entries(agentOverrides).find(([key]) => key.toLowerCase() === agentConfigKey)?.[1] : undefined)
const agentRequirement = AGENT_MODEL_REQUIREMENTS[agentConfigKey]
fallbackChain = agentRequirement?.fallbackChain
if (agentOverride?.model || agentRequirement || matchedAgent.model) {
const availableModels = await getAvailableModelsForDelegateTask(client)
@@ -135,5 +138,5 @@ Create the work plan directly - that's your job as the planning agent.`,
}
}
return { agentToUse, categoryModel }
return { agentToUse, categoryModel, fallbackChain }
}
+5 -1
View File
@@ -3,7 +3,7 @@ import type { DelegateTaskArgs, ToolContextWithMetadata } from "./types"
import type { ExecutorContext, ParentContext } from "./executor-types"
import { getTaskToastManager } from "../../features/task-toast-manager"
import { storeToolMetadata } from "../../features/tool-metadata-store"
import { subagentSessions } from "../../features/claude-code-session-state"
import { subagentSessions, syncSubagentSessions, setSessionAgent } from "../../features/claude-code-session-state"
import { log } from "../../shared/logger"
import { formatDuration } from "./time-formatter"
import { formatDetailedError } from "./error-formatting"
@@ -40,6 +40,8 @@ export async function executeSyncTask(
const sessionID = createSessionResult.sessionID
syncSessionID = sessionID
subagentSessions.add(sessionID)
syncSubagentSessions.add(sessionID)
setSessionAgent(sessionID, agentToUse)
if (onSyncSessionCreated) {
log("[task] Invoking onSyncSessionCreated callback", { sessionID, parentID: parentContext.sessionID })
@@ -59,6 +61,7 @@ export async function executeSyncTask(
if (toastManager) {
toastManager.addTask({
id: taskId,
sessionID,
description: args.description,
agent: agentToUse,
isBackground: false,
@@ -145,6 +148,7 @@ session_id: ${sessionID}
} finally {
if (syncSessionID) {
subagentSessions.delete(syncSessionID)
syncSubagentSessions.delete(syncSessionID)
}
}
}
+4 -1
View File
@@ -164,6 +164,7 @@ Prompts MUST be in English.`
let modelInfo: import("../../features/task-toast-manager/types").ModelFallbackInfo | undefined
let actualModel: string | undefined
let isUnstableAgent = false
let fallbackChain: import("../../shared/model-requirements").FallbackEntry[] | undefined
if (args.category) {
const resolution = await resolveCategoryExecution(args, options, inheritedModel, systemDefaultModel)
@@ -176,6 +177,7 @@ Prompts MUST be in English.`
modelInfo = resolution.modelInfo
actualModel = resolution.actualModel
isUnstableAgent = resolution.isUnstableAgent
fallbackChain = resolution.fallbackChain
const isRunInBackgroundExplicitlyFalse = args.run_in_background === false || args.run_in_background === "false" as unknown as boolean
@@ -206,6 +208,7 @@ Prompts MUST be in English.`
}
agentToUse = resolution.agentToUse
categoryModel = resolution.categoryModel
fallbackChain = resolution.fallbackChain
}
const systemContent = buildSystemContent({
@@ -217,7 +220,7 @@ Prompts MUST be in English.`
})
if (runInBackground) {
return executeBackgroundTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent)
return executeBackgroundTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent, fallbackChain)
}
return executeSyncTask(args, ctx, options, parentContext, agentToUse, categoryModel, systemContent, modelInfo)