import type { DelegateTaskArgs, ToolContextWithMetadata, DelegatedModelConfig } from "./types" import type { ExecutorContext, ParentContext, SessionMessage } from "./executor-types" import { DEFAULT_SYNC_POLL_TIMEOUT_MS, getTimingConfig } from "./timing" import { buildTaskPrompt } from "./prompt-builder" import { cancelUnstableAgentTask } from "./cancel-unstable-agent-task" import { publishToolMetadata } from "../../features/tool-metadata-store" import { formatDuration } from "./time-formatter" import { formatDetailedError } from "./error-formatting" import { getSessionTools } from "../../shared/session-tools-store" import { normalizeSDKResponse } from "../../shared" import { QUESTION_DENIED_SESSION_PERMISSION } from "../../shared/question-denied-session-permission" import { resolveMetadataModel } from "./resolve-metadata-model" import { buildTaskMetadataBlock } from "../../features/tool-metadata-store/task-metadata-contract" export async function executeUnstableAgentTask( args: DelegateTaskArgs, ctx: ToolContextWithMetadata, executorCtx: ExecutorContext, parentContext: ParentContext, agentToUse: string, categoryModel: DelegatedModelConfig | undefined, systemContent: string | undefined, actualModel: string | undefined ): Promise { const { manager, client, syncPollTimeoutMs, sisyphusAgentConfig } = executorCtx let cleanupReason: string | undefined let launchedTaskID: string | undefined try { const tddEnabled = sisyphusAgentConfig?.tdd const effectivePrompt = buildTaskPrompt(args.prompt, agentToUse, tddEnabled) const task = await manager.launch({ description: args.description, prompt: effectivePrompt, agent: agentToUse, parentSessionId: parentContext.sessionID, parentMessageId: parentContext.messageID, parentModel: parentContext.model, parentAgent: parentContext.agent, parentTools: getSessionTools(parentContext.sessionID), model: categoryModel, skills: args.load_skills.length > 0 ? args.load_skills : undefined, skillContent: systemContent, category: args.category, sessionPermission: QUESTION_DENIED_SESSION_PERMISSION, }) launchedTaskID = task.id const timing = getTimingConfig() const waitStart = Date.now() let sessionID = task.sessionId while (!sessionID && Date.now() - waitStart < timing.WAIT_FOR_SESSION_TIMEOUT_MS) { if (ctx.abort?.aborted) { cleanupReason = "Parent aborted while waiting for unstable task session start" return `Task aborted while waiting for session to start.\n\nTask ID: ${task.id}` } await new Promise(resolve => setTimeout(resolve, timing.WAIT_FOR_SESSION_INTERVAL_MS)) const updated = manager.getTask(task.id) sessionID = updated?.sessionId } if (!sessionID) { cleanupReason = "Unstable task session start timed out before session became available" return formatDetailedError(new Error(`Task failed to start within timeout (30s). Task ID: ${task.id}, Status: ${task.status}`), { operation: "Launch monitored background task", args, agent: agentToUse, category: args.category, }) } const bgTaskMeta = { title: args.description, metadata: { prompt: args.prompt, agent: agentToUse, category: args.category, ...(args.requested_subagent_type !== undefined ? { requested_subagent_type: args.requested_subagent_type } : {}), load_skills: args.load_skills, description: args.description, run_in_background: args.run_in_background, taskId: sessionID, backgroundTaskId: task.id, sessionId: sessionID, command: args.command, model: resolveMetadataModel(categoryModel, parentContext.model), }, } await publishToolMetadata(ctx, bgTaskMeta) const taskMetadataBlock = buildTaskMetadataBlock({ sessionId: sessionID, backgroundTaskId: task.id, agent: agentToUse, category: args.category, }) const startTime = new Date() const timingCfg = getTimingConfig() const pollStart = Date.now() let lastMsgCount = 0 let stablePolls = 0 let terminalStatus: { status: string; error?: string } | undefined let completedDuringMonitoring = false while (Date.now() - pollStart < (syncPollTimeoutMs ?? DEFAULT_SYNC_POLL_TIMEOUT_MS)) { if (ctx.abort?.aborted) { cleanupReason = "Parent aborted while monitoring unstable background task" return `Task aborted (was running in background mode).\n\nSession ID: ${sessionID}` } await new Promise(resolve => setTimeout(resolve, timingCfg.POLL_INTERVAL_MS)) const currentTask = manager.getTask(task.id) if (currentTask && (currentTask.status === "interrupt" || currentTask.status === "error" || currentTask.status === "cancelled")) { terminalStatus = { status: currentTask.status, error: currentTask.error } break } const statusResult = await client.session.status() const allStatuses = normalizeSDKResponse(statusResult, {} as Record) const sessionStatus = allStatuses[sessionID] if (sessionStatus && sessionStatus.type !== "idle") { stablePolls = 0 lastMsgCount = 0 continue } if (Date.now() - pollStart < timingCfg.MIN_STABILITY_TIME_MS) continue const messagesCheck = await client.session.messages({ path: { id: sessionID } }) const msgs = normalizeSDKResponse(messagesCheck, [] as Array, { preferResponseOnMissingData: true, }) const currentMsgCount = msgs.length if (currentMsgCount === lastMsgCount) { stablePolls++ if (stablePolls >= timingCfg.STABILITY_POLLS_REQUIRED) { completedDuringMonitoring = true break } } else { stablePolls = 0 lastMsgCount = currentMsgCount } } if (terminalStatus) { const duration = formatDuration(startTime) return `SUPERVISED TASK FAILED (${terminalStatus.status}) Task was interrupted/failed while running in monitored background mode. ${terminalStatus.error ? `Error: ${terminalStatus.error}` : ""} Duration: ${duration} Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""} Model: ${actualModel} The task session may contain partial results. ${taskMetadataBlock}` } if (!completedDuringMonitoring) { cleanupReason = "Monitored unstable background task exceeded timeout budget" const duration = formatDuration(startTime) const timeoutBudgetMs = syncPollTimeoutMs ?? DEFAULT_SYNC_POLL_TIMEOUT_MS return `SUPERVISED TASK TIMED OUT Task did not reach a stable completion signal within the monitored timeout budget. Timeout budget: ${timeoutBudgetMs}ms Duration: ${duration} Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""} Model: ${actualModel} The task session may still contain partial results. ${taskMetadataBlock}` } const messagesResult = await client.session.messages({ path: { id: sessionID } }) const messages = normalizeSDKResponse(messagesResult, [] as SessionMessage[], { preferResponseOnMissingData: true, }) const assistantMessages = messages .filter((m) => m.info?.role === "assistant") .sort((a, b) => (b.info?.time?.created ?? 0) - (a.info?.time?.created ?? 0)) if (assistantMessages.length === 0) { return `No assistant response found (task ran in background mode).\n\nSession ID: ${sessionID}` } let textContent = "" for (const msg of assistantMessages) { const textParts = msg.parts?.filter((p) => p.type === "text" || p.type === "reasoning") ?? [] const content = textParts.map((p) => p.text ?? "").filter(Boolean).join("\n") if (content) { textContent = content break } } const duration = formatDuration(startTime) return `SUPERVISED TASK COMPLETED SUCCESSFULLY IMPORTANT: This model (${actualModel}) is marked as unstable/experimental. Your run_in_background=false was automatically converted to background mode for reliability monitoring. Duration: ${duration} Agent: ${agentToUse}${args.category ? ` (category: ${args.category})` : ""} MONITORING INSTRUCTIONS: - The task was monitored and completed successfully - If you observe this agent behaving erratically in future calls, actively monitor its progress - Use background_cancel(task_id="...") to abort if the agent seems stuck or producing garbage output - Do NOT retry automatically if you see this message - the task already succeeded --- RESULT: ${textContent || "(No text output)"} ${taskMetadataBlock}` } catch (error) { if (!cleanupReason) { cleanupReason = "exception" } return formatDetailedError(error, { operation: "Launch monitored background task", args, agent: agentToUse, category: args.category, }) } finally { if (cleanupReason) { await cancelUnstableAgentTask(manager, launchedTaskID, cleanupReason) } } }