/** * callBudgetForecastBlock.ts — S-FORECAST: pre-turn budget forecast * * Prima di qualsiasi acquireExtraCall, analizza il task classificato e * RISERVA ogni slot (HIGH/LOW) per la capability più utile in quel contesto. * * Problema risolto (on-demand race condition): * reflection arriva all'iter 4 → prende LOW slot → verify alla fine non trova * slot → output di codice mai verificato. Non era un problema di logica, era * un problema di TIMING: chi arrivava prima vinceva. * * Con forecast: LOW è pre-riservato per verify su task di codice → reflection * è bloccata dalla riserva, non dal timing fortuito. Garanzia strutturale. * * Design: * - Zero LLM calls, < 0.1ms, deterministic, pure function * - BudgetForecast importato da callBudgetCoordinator (nessun circular import) * - null = nessuna restrizione (backward-compatible con on-demand) * - Stringa specifica = solo quella capability può acquisire lo slot * * Regole (priorità decrescente): * 1. isDirectAnswer → HIGH: null, LOW: 'verify' fast path * 2. code + complex/err → HIGH: 'thinking', LOW: 'verify' qualità codice * 3. code semplice → HIGH: null, LOW: 'verify' verify essenziale * 4. reasoning=high+text → HIGH: 'debate', LOW: 'reflection' testo complesso * 5. medium + errors → HIGH: 'thinking', LOW: 'verify' recovery boost * 6. default → HIGH: null, LOW: null no restriction * * @module callBudgetForecastBlock */ import type { BudgetForecast } from './callBudgetCoordinator'; export interface BudgetForecastInput { preCls: { type?: string; isDirectAnswer?: boolean; complexity?: number } | null; isCodeTask: boolean; reasoningDepth: 'low' | 'medium' | 'high'; hasRecentErrors: boolean; } /** * Calcola la previsione ottimale di allocazione budget per questo turno. * Chiamare DOPO la classificazione del task e PRIMA di runPreLoopFinalizer. * Risultato passato a callBudget.setForecast() per attivare la riserva. */ export function forecastBudget(input: BudgetForecastInput): BudgetForecast { const { preCls, isCodeTask, reasoningDepth, hasRecentErrors } = input; const _complexity = preCls?.complexity ?? 5; const _isDirectAns = preCls?.isDirectAnswer ?? false; // 1. Risposta diretta → nessun HIGH necessario, LOW riservato per verify if (_isDirectAns) { return { highReserved: null, lowReserved: 'verify', rationale: 'isDirectAnswer → LOW:verify (fast path, solo check completezza)', }; } // 2. Codice complesso o con errori → thinking garantito + verify garantito if (isCodeTask && (_complexity >= 7 || hasRecentErrors)) { return { highReserved: 'thinking', lowReserved: 'verify', rationale: `code+${_complexity >= 7 ? 'complexity' + _complexity : 'errors'} → HIGH:thinking LOW:verify`, }; } // 3. Codice semplice → solo verify garantito (thinking non necessario) if (isCodeTask) { return { highReserved: null, lowReserved: 'verify', rationale: 'code+simple → LOW:verify (nessun ragionamento avanzato; verify essenziale)', }; } // 4. Ragionamento alto + testo → debate + reflection (multi-prospettiva + validazione) if (reasoningDepth === 'high') { return { highReserved: 'debate', lowReserved: 'reflection', rationale: 'reasoning=high+text → HIGH:debate LOW:reflection', }; } // 5. Ragionamento medio con errori → thinking per recovery + verify output if (hasRecentErrors && reasoningDepth !== 'low') { return { highReserved: 'thinking', lowReserved: 'verify', rationale: 'medium+errors → HIGH:thinking LOW:verify (recovery boost)', }; } // 6. Default — nessuna restrizione (comportamento backward-compatible on-demand) return { highReserved: null, lowReserved: null, rationale: 'default → no restriction (on-demand fallback)', }; }