// module-predictor.js — Predictive resource allocation engine // Analyzes historical telemetry + memory graph data to predict task duration/turns // Commands: predict, predictor-status, predictor-config class PredictorModule { constructor() { this.commands = {}; this.terminal = null; this._dataPoints = []; this._predictions = 0; this._accuracy = { correct: 0, total: 0 }; this._CONFIDENCE_THRESHOLD = 0.5; this._MIN_DATA_POINTS = 5; this._loaded = false; } getHelpInfo() { return { 'predict': 'predict — Predict task duration, tool turns, and confidence', 'predictor-status': 'predictor-status — Model accuracy, data points, prediction count', 'predictor-config': 'predictor-config — Tune: confidence-threshold, min-data-points', }; } registerCommands() { this.commands['predict'] = (args) => this.predict(args.join(' ')); this.commands['predictor-status'] = () => this.showStatus(); this.commands['predictor-config'] = (args) => this.setConfig(args); } init(terminal) { this.terminal = terminal; this._loadFromVFS(); this._loaded = true; this.terminal.print('📊 Predictor loaded — analyzing task patterns', 'info'); } // ─── Prediction Engine ─── async predict(task) { if (!task) return this.terminal.print('Usage: predict ', 'error'); const startTime = performance.now(); // Extract features const features = this._extractFeatures(task); // Find similar historical tasks const similar = this._findSimilar(features); // Generate prediction const prediction = this._computePrediction(similar, features); // Record this prediction for accuracy tracking const duration = ((performance.now() - startTime) / 1000).toFixed(2); this._predictions++; this._dataPoints.push({ features, predictedDuration: prediction.duration, predictedTurns: prediction.toolTurns, confidence: prediction.confidence, similarCount: similar.length, timestamp: new Date().toISOString() }); this._debounceSave(); // Display result this.terminal.print('📈 Task Prediction', 'header'); this.terminal.print(` Task: ${task.substring(0, 50)}${task.length > 50 ? '...' : ''}`, 'info'); if (prediction.confidence >= this._CONFIDENCE_THRESHOLD) { this.terminal.print(` ⏱ Estimated duration: ${prediction.duration}s`, 'info'); this.terminal.print(` 🔧 Estimated tool turns: ${prediction.toolTurns}`, 'info'); this.terminal.print(` 📊 Confidence: ${(prediction.confidence * 100).toFixed(0)}% (based on ${similar.length} similar tasks)`, 'success'); } else if (similar.length > 0) { this.terminal.print(` ⏱ Estimated duration: ${prediction.duration}s (low confidence)`, 'warning'); this.terminal.print(` 🔧 Tool turns: ~${prediction.toolTurns}`, 'warning'); this.terminal.print(` 📊 Confidence: ${(prediction.confidence * 100).toFixed(0)}% — only ${similar.length} similar data point(s)`, 'warning'); this.terminal.print(' Tip: More task data will improve predictions.', 'info'); } else { this.terminal.print(' ❓ No historical data to base prediction on.', 'warning'); this.terminal.print(' Tip: Run `orchestrate` with various tasks to build a baseline.', 'info'); // Provide hardcoded defaults this.terminal.print(' Default estimate: ~30s duration, ~5 tool turns', 'info'); } // If got memory graph, offer recommendations if (prediction.confidence >= this._CONFIDENCE_THRESHOLD && prediction.duration > 30) { this.terminal.print(` 💡 Recommendation: Set TTL to ${Math.ceil(prediction.duration * 1.5)}s for predictable completion`, 'info'); } return prediction; } _extractFeatures(task) { const lower = task.toLowerCase(); return { length: task.length, wordCount: task.split(/\s+/).length, hasSearch: /\b(search|find|look)\b/.test(lower), hasWrite: /\b(write|create|save|edit)\b/.test(lower), hasDelete: /\b(delete|remove|rm)\b/.test(lower), hasInstall: /\b(install|wpm|package)\b/.test(lower), hasRead: /\b(read|cat|view|list)\b/.test(lower), hasWeb: /\b(fetch|url|http|web)\b/.test(lower), hasOrchestrate: /\b(orchestrat|delegate|agent)\b/.test(lower), hasModule: /\b(module|edit_module|view_module)\b/.test(lower), hasSync: /\b(sync|recall|remember|previous session|state sync)\b/.test(lower), complexity: Math.min(task.length / 50, 10), // 0-10 scale keywords: [...new Set(lower.split(/\s+/).filter(w => w.length > 3))].slice(0, 10) }; } _findSimilar(features) { return this._dataPoints.filter(dp => { // Skip predictions (only use actual executions with observed results) if (!dp.observedDuration) return false; const f = dp.features; if (!f) return false; // Feature similarity scoring let score = 0; const matches = [ features.hasSearch === f.hasSearch, features.hasWrite === f.hasWrite, features.hasDelete === f.hasDelete, features.hasInstall === f.hasInstall, features.hasRead === f.hasRead, features.hasWeb === f.hasWeb, features.hasOrchestrate === f.hasOrchestrate, features.hasModule === f.hasModule, features.hasSync === f.hasSync, ]; score = matches.filter(Boolean).length / matches.length; // Keyword overlap bonus const kOverlap = features.keywords.filter(k => f.keywords?.includes(k)).length; const kScore = kOverlap / Math.max(features.keywords.length, 1); score = score * 0.6 + kScore * 0.4; return score > 0.3; }); } _computePrediction(similar, features) { if (similar.length === 0) { // Hardcoded defaults let baseDuration = 15; let baseTurns = 3; if (features.hasSearch || features.hasWeb) { baseDuration = 25; baseTurns = 5; } if (features.hasWrite) { baseDuration = 20; baseTurns = 4; } if (features.hasOrchestrate) { baseDuration = 40; baseTurns = 8; } if (features.hasSync) { baseDuration = 30; baseTurns = 6; } if (features.complexity > 5) baseDuration *= 1.5; return { duration: baseDuration, toolTurns: baseTurns, confidence: 0.1, similarCount: 0 }; } const avgDuration = similar.reduce((s, dp) => s + (dp.observedDuration || 30), 0) / similar.length; const avgTurns = similar.reduce((s, dp) => s + (dp.observedTurns || 3), 0) / similar.length; const confidence = Math.min(similar.length / 20, 1) * 0.8 + 0.1; // Apply task complexity multiplier const complexityMultiplier = 1 + (features.complexity - 3) * 0.1; const adjustedDuration = Math.round(avgDuration * Math.max(complexityMultiplier, 0.5)); return { duration: adjustedDuration, toolTurns: Math.round(avgTurns), confidence: Math.min(confidence, 0.95), similarCount: similar.length }; } // ─── Learning from Results ─── recordResult(features, duration, toolTurns, success) { this._dataPoints.push({ features, observedDuration: duration, observedTurns: toolTurns, success, timestamp: new Date().toISOString() }); this._accuracy.total++; // Compare with last prediction for this feature set const lastPrediction = [...this._dataPoints] .reverse() .find(dp => dp.predictedDuration && dp.features?.hasSearch === features.hasSearch); if (lastPrediction) { const error = Math.abs(lastPrediction.predictedDuration - duration) / duration; if (error < 0.3) this._accuracy.correct++; } this._debounceSave(); } // ─── Commands ─── showStatus() { const actualExecutions = this._dataPoints.filter(dp => dp.observedDuration).length; const accPct = this._accuracy.total > 0 ? ((this._accuracy.correct / this._accuracy.total) * 100).toFixed(0) : 'N/A'; this.terminal.print('📊 Predictor Status', 'header'); this.terminal.print(` Total data points: ${this._dataPoints.length}`, 'info'); this.terminal.print(` Actual executions: ${actualExecutions}`, 'info'); this.terminal.print(` Predictions made: ${this._predictions}`, 'info'); this.terminal.print(` Accuracy: ${accPct}% (${this._accuracy.correct}/${this._accuracy.total})`, 'info'); this.terminal.print(` Confidence threshold: ${this._CONFIDENCE_THRESHOLD}`, 'info'); this.terminal.print(` Min data points: ${this._MIN_DATA_POINTS}`, 'info'); } setConfig(args) { if (args.length < 2) return this.terminal.print('Usage: predictor-config ', 'error'); const key = args[0]; const val = args[1]; const configMap = { 'confidence-threshold': () => { const n = parseFloat(val); if (isNaN(n) || n < 0 || n > 1) return 'Must be 0.0-1.0'; this._CONFIDENCE_THRESHOLD = n; return `confidence-threshold set to ${n}`; }, 'min-data-points': () => { const n = parseInt(val); if (isNaN(n) || n < 1) return 'Must be >= 1'; this._MIN_DATA_POINTS = n; return `min-data-points set to ${n}`; }, }; if (!configMap[key]) return this.terminal.print(`Unknown: ${key}. Valid: ${Object.keys(configMap).join(', ')}`, 'error'); const msg = configMap[key](); this.terminal.print(`✅ ${msg}`, 'success'); } // ─── Persistence ─── _loadFromVFS() { try { const fs = this.terminal.fs; if (!fs) return; const data = fs.fsReadJSON('/system/metrics/predictor.json'); if (data) { this._dataPoints = data.dataPoints || []; this._predictions = data.predictions || 0; this._accuracy = data.accuracy || { correct: 0, total: 0 }; } } catch (e) { console.warn('[Predictor] Load error (fresh start):', e.message); } } _saveNow() { try { const fs = this.terminal.fs; if (!fs) return; if (!fs.readDir('/system/metrics')) fs.mkdirRecursive('/system/metrics'); fs.fsWriteJSON('/system/metrics/predictor.json', { dataPoints: this._dataPoints.slice(-500), // keep last 500 predictions: this._predictions, accuracy: this._accuracy }); } catch (e) { console.error('[Predictor] Save error:', e.message); } } _debounceSave() { clearTimeout(this._saveTimer); this._saveTimer = setTimeout(() => this._saveNow(), 3000); } } // Required for module system if (typeof window !== 'undefined') { window.PredictorModule = PredictorModule; }