/** * RetryPlanner - Decide retry strategy for failed tasks * * Implements DD §7.4. * * @module packages/runtime/src/scheduler/RetryPlanner */ export type RetryDecision = 'retry' | 'retry_serial' | 'debug' | 'skip' | 'block' | 'cancel' export interface RetryInput { task_id: string attempt_count: number failure_signature: string failure_summary: string previous_signatures: string[] max_retries: number is_env_error: boolean is_arch_error: boolean } export interface RetryResult { decision: RetryDecision reason: string escalate_to: 'architecture_designer' | 'main_agent' | 'user' | null delay_ms?: number } export class RetryPlanner { private default_max_retries: number constructor(default_max_retries: number = 3) { this.default_max_retries = default_max_retries } /** * Decide retry strategy based on failure analysis. */ decide(input: RetryInput): RetryResult { const max_retries = input.max_retries || this.default_max_retries // Env impossibility → block immediately if (input.is_env_error) { return { decision: 'block', reason: 'Environment error — cannot retry until env is fixed', escalate_to: 'user' } } // Architecture/interface mismatch → route to ArchitectureDesigner if (input.is_arch_error) { return { decision: 'block', reason: 'Architecture mismatch — routing to ArchitectureDesigner', escalate_to: 'architecture_designer' } } // Same failure signature → escalate faster const same_signature_count = input.previous_signatures.filter(s => s === input.failure_signature).length if (same_signature_count >= 2) { return { decision: 'debug', reason: `Same failure signature (${input.failure_signature}) repeated ${same_signature_count + 1} times`, escalate_to: 'main_agent' } } // Max retries exceeded if (input.attempt_count >= max_retries) { return { decision: 'cancel', reason: `Max retries (${max_retries}) exceeded`, escalate_to: 'main_agent' } } // Serial retry for same-area conflicts if (same_signature_count >= 1) { return { decision: 'retry_serial', reason: `Retrying with serialized execution (conflict detected)`, escalate_to: null, delay_ms: 5000 } } // Default retry return { decision: 'retry', reason: `Retry attempt ${input.attempt_count + 1}/${max_retries}`, escalate_to: null, delay_ms: Math.min(1000 * Math.pow(2, input.attempt_count), 30000) // Exponential backoff } } }