Skip to main content
Glama
framework-selector.ts25.6 kB
/** * Framework Selector * * Dynamically selects appropriate systematic thinking frameworks based on * problem classification. Supports single framework selection and hybrid * framework combinations for complex problems. */ import { randomUUID } from "node:crypto"; import type { FrameworkLearningSystem } from "./framework-learning.js"; import type { FrameworkRegistry } from "./framework-registry.js"; import type { ProblemClassifier } from "./problem-classifier.js"; import type { Context, FrameworkAlternative, FrameworkSelection, Problem, ProblemClassification, ThinkingFramework, } from "./types.js"; /** * Framework score with reasoning */ interface FrameworkScore { framework: ThinkingFramework; score: number; reason: string; /** Strengths of this framework for the problem */ strengths: string[]; /** Weaknesses of this framework for the problem */ weaknesses: string[]; } /** * Framework Selector * * Selects optimal systematic thinking framework(s) based on problem * classification across complexity, uncertainty, stakes, and time pressure. * Optionally integrates with learning system for adaptive selection. */ export class FrameworkSelector { constructor( private readonly classifier: ProblemClassifier, private readonly registry: FrameworkRegistry, private readonly learningSystem?: FrameworkLearningSystem ) {} /** * Select the most appropriate framework(s) for a problem * * @param problem - Problem to solve * @param _context - Execution context (reserved for future use) * @returns Framework selection with primary, alternatives, and hybrid info */ selectFramework(problem: Problem, _context: Context): FrameworkSelection { // Validate inputs if (!problem) { throw new Error("Problem cannot be null or undefined"); } // Generate selection ID if learning system is provided const selectionId = this.learningSystem ? randomUUID() : undefined; // Classify the problem const classification = this.classifier.classifyProblem(problem); // Get adaptive weights from learning system if available const adaptiveWeights = this.getAdaptiveWeights(classification); // Get user preferences from learning system if available const userPreferences = this.getUserPreferences(); // Score all frameworks with adaptive weights and preferences const scores = this.scoreAllFrameworks(classification, adaptiveWeights, userPreferences); // Sort by score (highest first) scores.sort((a, b) => b.score - a.score); // Check for hybrid opportunity const hybridInfo = this.identifyHybridOpportunity(scores, classification); if (hybridInfo.isHybrid) { return this.createHybridSelection(scores, hybridInfo, classification, selectionId); } // Single framework selection return this.createSingleSelection(scores, classification, selectionId); } /** * Get adaptive weights from learning system */ private getAdaptiveWeights(classification: ProblemClassification): { complexity: number; uncertainty: number; stakes: number; timePressure: number; } { if (!this.learningSystem) { // Default weights return { complexity: 0.3, uncertainty: 0.3, stakes: 0.25, timePressure: 0.15, }; } try { // Try to get domain-specific weights const domain = `${classification.complexity}-${classification.uncertainty}-${classification.stakes}-${classification.timePressure}`; const weights = this.learningSystem.getAdaptiveWeights(domain); return { complexity: weights.complexity, uncertainty: weights.uncertainty, stakes: weights.stakes, timePressure: weights.timePressure, }; } catch { // Fall back to default weights on error return { complexity: 0.3, uncertainty: 0.3, stakes: 0.25, timePressure: 0.15, }; } } /** * Get user preferences from learning system */ private getUserPreferences(): Map<string, number> { if (!this.learningSystem) { return new Map(); } try { // Use default user ID for now return this.learningSystem.getUserPreferences("default"); } catch { // Fall back to empty preferences on error return new Map(); } } /** * Score all available frameworks for the problem */ private scoreAllFrameworks( classification: ProblemClassification, adaptiveWeights?: { complexity: number; uncertainty: number; stakes: number; timePressure: number; }, userPreferences?: Map<string, number> ): FrameworkScore[] { const frameworks = this.registry.getAllFrameworks(); const scores: FrameworkScore[] = []; // Use default weights if not provided const weights = adaptiveWeights ?? { complexity: 0.3, uncertainty: 0.3, stakes: 0.25, timePressure: 0.15, }; for (const framework of frameworks) { const score = this.calculateFrameworkScore(framework, classification, weights); // Apply user preference boost if available if (userPreferences?.has(framework.id)) { const preference = userPreferences.get(framework.id); if (preference !== undefined) { // Boost score by up to 20% based on preference (-1 to +1 range) score.score = score.score * (1 + preference * 0.2); // Clamp to valid range score.score = Math.max(0, Math.min(1, score.score)); } } scores.push(score); } return scores; } /** * Calculate score for a specific framework */ private calculateFrameworkScore( framework: ThinkingFramework, classification: ProblemClassification, weights?: { complexity: number; uncertainty: number; stakes: number; timePressure: number } ): FrameworkScore { let score = 0; const reasons: string[] = []; const strengths: string[] = []; const weaknesses: string[] = []; // Use provided weights or defaults const w = weights ?? { complexity: 0.3, uncertainty: 0.3, stakes: 0.25, timePressure: 0.15, }; // Complexity match (weighted) const complexityScore = this.scoreComplexityMatch(framework.id, classification.complexity); score += complexityScore.score * w.complexity; if (complexityScore.reason) reasons.push(complexityScore.reason); this.categorizeScoreResult(complexityScore, "complexity", strengths, weaknesses); // Uncertainty match (weighted) const uncertaintyScore = this.scoreUncertaintyMatch(framework.id, classification.uncertainty); score += uncertaintyScore.score * w.uncertainty; if (uncertaintyScore.reason) reasons.push(uncertaintyScore.reason); this.categorizeScoreResult(uncertaintyScore, "uncertainty", strengths, weaknesses); // Stakes match (weighted) const stakesScore = this.scoreStakesMatch(framework.id, classification.stakes); score += stakesScore.score * w.stakes; if (stakesScore.reason) reasons.push(stakesScore.reason); this.categorizeScoreResult(stakesScore, "stakes", strengths, weaknesses); // Time pressure match (weighted) const timePressureScore = this.scoreTimePressureMatch( framework.id, classification.timePressure ); score += timePressureScore.score * w.timePressure; if (timePressureScore.reason) reasons.push(timePressureScore.reason); this.categorizeScoreResult(timePressureScore, "time pressure", strengths, weaknesses); return { framework, score, reason: reasons.join(", "), strengths, weaknesses, }; } /** * Categorize a score result as strength or weakness */ private categorizeScoreResult( result: { score: number; reason: string }, dimension: string, strengths: string[], weaknesses: string[] ): void { if (!result.reason) return; // High scores (>= 0.7) are strengths, low scores (<= 0.4) are weaknesses if (result.score >= 0.7) { strengths.push(`${dimension}: ${result.reason}`); } else if (result.score <= 0.4) { weaknesses.push(`${dimension}: ${result.reason}`); } } /** * Score complexity match for framework * Returns normalized score (0-1) independent of weight */ private scoreComplexityMatch( frameworkId: string, complexity: string ): { score: number; reason: string } { const rules: Record<string, Record<string, { score: number; reason: string }>> = { "scientific-method": { simple: { score: 1.0, reason: "ideal for simple problems" }, moderate: { score: 0.5, reason: "works for moderate problems" }, complex: { score: 0.17, reason: "less suitable for complex problems" }, }, "design-thinking": { simple: { score: 0.33, reason: "may be overkill for simple problems" }, moderate: { score: 0.83, reason: "good for moderate complexity" }, complex: { score: 0.67, reason: "handles complex problems" }, }, "systems-thinking": { simple: { score: 0.17, reason: "overkill for simple problems" }, moderate: { score: 0.67, reason: "useful for moderate complexity" }, complex: { score: 1.0, reason: "ideal for complex systems" }, }, "critical-thinking": { simple: { score: 0.67, reason: "works for simple evaluation" }, moderate: { score: 0.83, reason: "good for moderate evaluation" }, complex: { score: 0.67, reason: "handles complex evaluation" }, }, "root-cause-analysis": { simple: { score: 0.83, reason: "good for simple diagnostics" }, moderate: { score: 1.0, reason: "ideal for moderate diagnostics" }, complex: { score: 0.67, reason: "works for complex diagnostics" }, }, }; return rules[frameworkId]?.[complexity] ?? { score: 0.33, reason: "" }; } /** * Score uncertainty match for framework * Returns normalized score (0-1) independent of weight */ private scoreUncertaintyMatch( frameworkId: string, uncertainty: string ): { score: number; reason: string } { const rules: Record<string, Record<string, { score: number; reason: string }>> = { "scientific-method": { low: { score: 1.0, reason: "ideal for low uncertainty" }, medium: { score: 0.6, reason: "works with medium uncertainty" }, high: { score: 0.2, reason: "struggles with high uncertainty" }, }, "design-thinking": { low: { score: 0.4, reason: "less needed for low uncertainty" }, medium: { score: 0.8, reason: "good for medium uncertainty" }, high: { score: 1.0, reason: "ideal for high uncertainty" }, }, "systems-thinking": { low: { score: 0.6, reason: "works with low uncertainty" }, medium: { score: 0.8, reason: "good for medium uncertainty" }, high: { score: 0.8, reason: "handles high uncertainty" }, }, "critical-thinking": { low: { score: 0.8, reason: "good for clear evaluation" }, medium: { score: 1.0, reason: "ideal for uncertain evaluation" }, high: { score: 0.6, reason: "works with high uncertainty" }, }, "root-cause-analysis": { low: { score: 0.8, reason: "good for clear diagnostics" }, medium: { score: 1.0, reason: "ideal for uncertain diagnostics" }, high: { score: 0.8, reason: "works with high uncertainty" }, }, }; return rules[frameworkId]?.[uncertainty] ?? { score: 0.4, reason: "" }; } /** * Score stakes match for framework * Returns normalized score (0-1) independent of weight */ private scoreStakesMatch(frameworkId: string, stakes: string): { score: number; reason: string } { const rules: Record<string, Record<string, { score: number; reason: string }>> = { "scientific-method": { routine: { score: 1.0, reason: "good for routine problems" }, important: { score: 0.8, reason: "works for important problems" }, critical: { score: 0.6, reason: "works for critical problems" }, }, "design-thinking": { routine: { score: 0.6, reason: "works for routine problems" }, important: { score: 1.0, reason: "ideal for important problems" }, critical: { score: 0.8, reason: "good for critical problems" }, }, "systems-thinking": { routine: { score: 0.4, reason: "may be overkill for routine" }, important: { score: 0.8, reason: "good for important problems" }, critical: { score: 1.0, reason: "ideal for critical problems" }, }, "critical-thinking": { routine: { score: 0.8, reason: "good for routine evaluation" }, important: { score: 1.0, reason: "ideal for important evaluation" }, critical: { score: 1.0, reason: "critical for high-stakes decisions" }, }, "root-cause-analysis": { routine: { score: 0.6, reason: "works for routine diagnostics" }, important: { score: 0.8, reason: "good for important diagnostics" }, critical: { score: 1.0, reason: "critical for high-stakes issues" }, }, }; return rules[frameworkId]?.[stakes] ?? { score: 0.4, reason: "" }; } /** * Score time pressure match for framework * Returns normalized score (0-1) independent of weight */ private scoreTimePressureMatch( frameworkId: string, timePressure: string ): { score: number; reason: string } { const rules: Record<string, Record<string, { score: number; reason: string }>> = { "scientific-method": { none: { score: 1.0, reason: "thorough when time allows" }, moderate: { score: 0.75, reason: "works with moderate pressure" }, high: { score: 0.25, reason: "too slow for high pressure" }, }, "design-thinking": { none: { score: 1.0, reason: "thorough when time allows" }, moderate: { score: 0.75, reason: "works with moderate pressure" }, high: { score: 0.5, reason: "can be slow under pressure" }, }, "systems-thinking": { none: { score: 1.0, reason: "thorough when time allows" }, moderate: { score: 0.75, reason: "works with moderate pressure" }, high: { score: 0.5, reason: "can be slow under pressure" }, }, "critical-thinking": { none: { score: 0.75, reason: "works without pressure" }, moderate: { score: 1.0, reason: "good under moderate pressure" }, high: { score: 1.0, reason: "efficient under high pressure" }, }, "root-cause-analysis": { none: { score: 0.75, reason: "thorough when time allows" }, moderate: { score: 1.0, reason: "efficient under moderate pressure" }, high: { score: 1.0, reason: "focused under high pressure" }, }, }; return rules[frameworkId]?.[timePressure] ?? { score: 0.5, reason: "" }; } /** * Identify if hybrid framework approach is appropriate */ private identifyHybridOpportunity( scores: FrameworkScore[], classification: ProblemClassification ): { isHybrid: boolean; count: number; reason: string } { const { count: closeScoreCount, hasClose } = this.hasCloseScores(scores, 0.15); const needsMultiplePerspectives = this.requiresMultiplePerspectives(classification); const needsBalancedApproach = this.requiresBalancedApproach(classification); const isHybrid = hasClose || needsMultiplePerspectives || needsBalancedApproach; const reason = this.buildHybridReason( classification, closeScoreCount, needsMultiplePerspectives, needsBalancedApproach ); return { isHybrid, count: Math.min(3, Math.max(2, closeScoreCount)), reason: isHybrid ? `Hybrid approach: ${reason}` : "", }; } /** * Check if top frameworks have close scores */ private hasCloseScores( scores: FrameworkScore[], threshold: number ): { count: number; hasClose: boolean } { const topScores = scores.slice(0, 3); const highestScore = topScores[0].score; let count = 1; // Always include the top framework for (let i = 1; i < topScores.length; i++) { if (highestScore - topScores[i].score <= threshold) { count++; } } return { count, hasClose: count >= 2 }; } /** * Check if problem requires multiple perspectives */ private requiresMultiplePerspectives(classification: ProblemClassification): boolean { return classification.complexity === "complex" && classification.uncertainty === "high"; } /** * Check if problem requires balanced approach */ private requiresBalancedApproach(classification: ProblemClassification): boolean { return classification.stakes === "critical" && classification.timePressure === "high"; } /** * Build reason string for hybrid approach */ private buildHybridReason( classification: ProblemClassification, closeScoreCount: number, needsMultiplePerspectives: boolean, needsBalancedApproach: boolean ): string { const reasons: string[] = []; if (needsMultiplePerspectives) { reasons.push("high complexity and high uncertainty require multiple perspectives"); } if (needsBalancedApproach) { reasons.push("critical stakes and time pressure require balanced approach"); } if (!needsBalancedApproach) { this.addStakesAndTimePressureReasons(classification, reasons); } if (closeScoreCount >= 2 && reasons.length === 0) { reasons.push(`${closeScoreCount} frameworks have similar scores`); } if (reasons.length === 0) { reasons.push("multiple frameworks are suitable"); } return reasons.join("; "); } /** * Add stakes and time pressure reasons */ private addStakesAndTimePressureReasons( classification: ProblemClassification, reasons: string[] ): void { const isCriticalOrImportantWithPressure = classification.stakes === "critical" || (classification.stakes === "important" && classification.timePressure === "high"); if (isCriticalOrImportantWithPressure) { reasons.push("critical stakes and time pressure require balanced approach"); } else if (classification.stakes === "important") { reasons.push("important stakes require careful consideration"); } const shouldAddTimePressure = classification.timePressure === "high" && classification.stakes !== "important" && classification.stakes !== "critical"; if (shouldAddTimePressure) { reasons.push("high time pressure requires efficient approach"); } } /** * Create hybrid framework selection */ private createHybridSelection( scores: FrameworkScore[], hybridInfo: { isHybrid: boolean; count: number; reason: string }, classification: ProblemClassification, selectionId?: string ): FrameworkSelection { // Select top N frameworks for hybrid (2-3) const hybridFrameworks = scores.slice(0, hybridInfo.count).map((s) => s.framework); // Calculate hybrid confidence with calibration // Hybrid selections have slightly lower confidence due to ambiguity const avgScore = scores.slice(0, hybridInfo.count).reduce((sum, s) => sum + s.score, 0) / hybridInfo.count; // Apply calibration factors for hybrid const classificationFactor = classification.confidence * 0.2; const scoreFactor = avgScore * 0.6; // Hybrid selections get a small penalty for ambiguity (0.85 multiplier) let calibratedConfidence = (classificationFactor + scoreFactor + 0.2) * 0.85; // Ensure confidence is in valid range [0.4, 0.85] for hybrid // Hybrid is never as confident as single selection calibratedConfidence = Math.max(0.4, Math.min(0.85, calibratedConfidence)); // Primary framework is still the highest scoring one const primaryFramework = scores[0].framework; const primaryScore = scores[0]; // Alternatives with trade-off explanations const alternatives: FrameworkAlternative[] = scores.slice(hybridInfo.count).map((s) => ({ framework: s.framework, confidence: s.score, reason: this.generateTradeOffExplanation(primaryScore, s), })); return { primaryFramework, alternatives, confidence: calibratedConfidence, reason: hybridInfo.reason, isHybrid: true, hybridFrameworks, timestamp: new Date(), selectionId, }; } /** * Create single framework selection */ private createSingleSelection( scores: FrameworkScore[], classification: ProblemClassification, selectionId?: string ): FrameworkSelection { const primaryFramework = scores[0].framework; const primaryScore = scores[0]; // Calculate calibrated confidence based on multiple factors const calibratedConfidence = this.calculateCalibratedConfidence(scores, classification); // Alternatives with trade-off explanations const alternatives: FrameworkAlternative[] = scores.slice(1).map((s) => ({ framework: s.framework, confidence: s.score, reason: this.generateTradeOffExplanation(primaryScore, s), })); return { primaryFramework, alternatives, confidence: calibratedConfidence, reason: `Selected ${primaryFramework.name}: ${scores[0].reason}`, isHybrid: false, timestamp: new Date(), selectionId, }; } /** * Calculate calibrated confidence based on multiple factors * * Confidence varies based on: * 1. Score gap between top frameworks (larger gap = higher confidence) * 2. Problem classification confidence * 3. How well the framework matches the problem characteristics */ private calculateCalibratedConfidence( scores: FrameworkScore[], classification: ProblemClassification ): number { const topScore = scores[0].score; const secondScore = scores.length > 1 ? scores[1].score : 0; // Factor 1: Score gap (0-0.3 contribution) // Larger gap between top two frameworks = higher confidence const scoreGap = topScore - secondScore; const gapFactor = Math.min(0.3, scoreGap * 1.5); // Factor 2: Classification confidence (0-0.3 contribution) // Higher classification confidence = higher selection confidence const classificationFactor = classification.confidence * 0.3; // Factor 3: Absolute score quality (0-0.4 contribution) // Higher absolute score = higher confidence const scoreFactor = topScore * 0.4; // Combine factors let confidence = gapFactor + classificationFactor + scoreFactor; // Apply penalty for ambiguous situations // If top 3 frameworks are very close, reduce confidence if (scores.length >= 3) { const thirdScore = scores[2].score; if (topScore - thirdScore < 0.1) { confidence *= 0.85; // 15% penalty for ambiguity } } // Ensure confidence is in valid range [0.3, 0.95] // Never 1.0 (always some uncertainty) and never too low for a selection return Math.max(0.3, Math.min(0.95, confidence)); } /** * Generate trade-off explanation comparing alternative to primary framework */ private generateTradeOffExplanation( primary: FrameworkScore, alternative: FrameworkScore ): string { const parts: string[] = []; // Start with the alternative's basic reason if (alternative.reason) { parts.push(alternative.reason); } // Add trade-off comparison const tradeOffs = this.identifyTradeOffs(primary, alternative); if (tradeOffs.length > 0) { parts.push(`Trade-offs vs ${primary.framework.name}: ${tradeOffs.join("; ")}`); } // Add when this alternative might be preferred const preferredWhen = this.identifyPreferredScenarios(alternative); if (preferredWhen) { parts.push(`Preferred when: ${preferredWhen}`); } return parts.join(". "); } /** * Identify trade-offs between primary and alternative framework */ private identifyTradeOffs(primary: FrameworkScore, alternative: FrameworkScore): string[] { const tradeOffs: string[] = []; // Find areas where alternative is stronger for (const altStrength of alternative.strengths) { const dimension = altStrength.split(":")[0]; const primaryHasWeakness = primary.weaknesses.some((w) => w.startsWith(dimension)); if (primaryHasWeakness) { tradeOffs.push(`stronger in ${dimension.toLowerCase()}`); } } // Find areas where alternative is weaker for (const altWeakness of alternative.weaknesses) { const dimension = altWeakness.split(":")[0]; const primaryHasStrength = primary.strengths.some((s) => s.startsWith(dimension)); if (primaryHasStrength) { tradeOffs.push(`weaker in ${dimension.toLowerCase()}`); } } return tradeOffs; } /** * Identify scenarios where alternative framework might be preferred */ private identifyPreferredScenarios(alternative: FrameworkScore): string | null { const frameworkId = alternative.framework.id; // Framework-specific preferred scenarios const scenarios: Record<string, string> = { "scientific-method": "problem has clear hypothesis to test", "design-thinking": "user experience or creative solution is priority", "systems-thinking": "problem involves complex interdependencies", "critical-thinking": "evaluating options or making decisions", "root-cause-analysis": "diagnosing failures or defects", }; return scenarios[frameworkId] ?? null; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server