UNPKG

sf-agent-framework

Version:

AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction

572 lines (497 loc) • 16.8 kB
/** * Scale-Adaptive Workflow Engine * * Purpose: Dynamically adjust workflow execution based on: * - Project complexity * - Available resources (tokens, time, budget) * - Team size and expertise * - Quality requirements * * Key Features: * - Automatic scaling (can escalate or de-escalate) * - Resource-aware execution (stays within budgets) * - Quality-driven decisions (adjust depth based on criticality) * - Intelligent agent selection (right agent for the job) * - Real-time adaptation (responds to changing conditions) * * @module ScaleAdaptiveEngine * @version 1.0.0 * @date 2025-11-25 */ const fs = require('fs-extra'); const path = require('path'); const yaml = require('js-yaml'); class ScaleAdaptiveEngine { constructor(rootDir = process.cwd()) { this.rootDir = rootDir; this.configPath = path.join(rootDir, 'sf-core', 'config', 'workflow-tracks.yaml'); this.sessionsDir = path.join(rootDir, '.sf-agent', 'workflow-sessions'); // Scaling thresholds this.thresholds = { // Complexity-based scaling complexityEscalation: 65, // Escalate to next track at 65% complexityDeEscalation: 35, // De-escalate at 35% // Resource-based scaling tokenBudgetWarning: 0.75, // Warn at 75% budget used tokenBudgetCritical: 0.9, // Critical at 90% timeBudgetWarning: 0.8, // Warn at 80% time used // Quality-based scaling criticalQuality: 95, // 95%+ quality for critical systems standardQuality: 85, // 85%+ quality for standard work acceptableQuality: 75, // 75%+ for non-critical work }; // Current execution state this.state = { currentTrack: null, currentPhase: null, budgets: { tokens: { allocated: 0, used: 0 }, time: { allocated: 0, used: 0 }, cost: { allocated: 0, used: 0 }, }, metrics: { complexity: 0, quality: 0, progress: 0, }, adaptations: [], }; } /** * Initialize engine */ async initialize() { await fs.ensureDir(this.sessionsDir); // Load workflow tracks configuration this.tracks = await this.loadTracks(); console.log('āœ“ Scale-adaptive workflow engine initialized'); return true; } /** * Load workflow tracks */ async loadTracks() { const content = await fs.readFile(this.configPath, 'utf8'); return yaml.load(content); } /** * Start adaptive workflow */ async startWorkflow(initialTrack, projectInfo, budgets = {}) { console.log(`\nšŸš€ Starting scale-adaptive workflow: ${initialTrack}\n`); // Initialize state this.state.currentTrack = initialTrack; this.state.currentPhase = this.tracks.tracks[initialTrack].phases[0]; this.state.budgets = this.initializeBudgets(initialTrack, budgets); this.state.metrics.complexity = projectInfo.complexity || 50; // Create session const session = { id: this.generateSessionId(), track: initialTrack, projectInfo, budgets: this.state.budgets, startTime: new Date().toISOString(), phases: [], adaptations: [], status: 'active', }; // Save session await this.saveSession(session); console.log(`Session ID: ${session.id}`); console.log(`Initial Track: ${initialTrack}`); console.log(`Token Budget: ${this.state.budgets.tokens.allocated} tokens`); console.log(`Time Budget: ${this.state.budgets.time.allocated} minutes\n`); return session; } /** * Initialize budgets based on track */ initializeBudgets(track, customBudgets = {}) { const trackConfig = this.tracks.tracks[track]; // Default budgets by track const defaults = { quick: { tokens: 20000, // 20k tokens for quick time: 10, // 10 minutes cost: 0.5, // $0.50 }, balanced: { tokens: 80000, // 80k tokens for balanced time: 30, // 30 minutes cost: 2.0, // $2.00 }, enterprise: { tokens: 200000, // 200k tokens for enterprise time: 60, // 60 minutes cost: 10.0, // $10.00 }, }; const trackDefaults = defaults[track] || defaults.balanced; return { tokens: { allocated: customBudgets.tokens || trackDefaults.tokens, used: 0, }, time: { allocated: customBudgets.time || trackDefaults.time, used: 0, }, cost: { allocated: customBudgets.cost || trackDefaults.cost, used: 0, }, }; } /** * Execute phase with adaptation */ async executePhase(phaseConfig, session) { console.log(`\nšŸ“ Executing phase: ${phaseConfig.name || 'Unnamed'}`); const phaseStart = Date.now(); const phaseResult = { name: phaseConfig.name, startTime: new Date().toISOString(), agents: [], outputs: [], tokensUsed: 0, status: 'in-progress', }; try { // Pre-phase adaptation check const preCheck = await this.checkAdaptation(session, 'pre-phase'); if (preCheck.shouldAdapt) { await this.adapt(preCheck.reason, preCheck.action, session); } // Execute phase steps const steps = phaseConfig.steps || []; for (const step of steps) { // Check budget before each step if (this.isBudgetExceeded('tokens')) { throw new Error('Token budget exceeded'); } // Execute step (this would call actual agents) const stepResult = await this.executeStep(step, session); phaseResult.agents.push(stepResult.agent); phaseResult.outputs.push(...stepResult.outputs); phaseResult.tokensUsed += stepResult.tokensUsed; // Update budget this.updateBudget('tokens', stepResult.tokensUsed); } // Post-phase adaptation check const postCheck = await this.checkAdaptation(session, 'post-phase'); if (postCheck.shouldAdapt) { await this.adapt(postCheck.reason, postCheck.action, session); } // Calculate phase duration const duration = (Date.now() - phaseStart) / 1000 / 60; // minutes this.updateBudget('time', duration); phaseResult.endTime = new Date().toISOString(); phaseResult.duration = duration; phaseResult.status = 'completed'; console.log(` āœ“ Phase completed in ${duration.toFixed(2)} minutes`); console.log(` Tokens used: ${phaseResult.tokensUsed}`); return phaseResult; } catch (error) { phaseResult.status = 'failed'; phaseResult.error = error.message; console.error(` āœ— Phase failed: ${error.message}`); throw error; } } /** * Execute individual step */ async executeStep(step, session) { // This is a placeholder - actual implementation would call real agents console.log(` → Step: ${step.name || 'Unnamed'}`); // Simulate step execution const tokensUsed = Math.floor(Math.random() * 2000) + 500; // 500-2500 tokens return { agent: step.agent, outputs: step.outputs || [], tokensUsed, duration: Math.random() * 2 + 0.5, // 0.5-2.5 minutes status: 'completed', }; } /** * Check if adaptation is needed */ async checkAdaptation(session, checkPoint) { const checks = []; // Check 1: Budget pressure const tokenUsagePercent = this.getBudgetUsagePercent('tokens'); const timeUsagePercent = this.getBudgetUsagePercent('time'); if (tokenUsagePercent > this.thresholds.tokenBudgetCritical) { checks.push({ shouldAdapt: true, reason: 'critical-token-budget', action: 'reduce-scope', severity: 'critical', }); } else if (tokenUsagePercent > this.thresholds.tokenBudgetWarning) { checks.push({ shouldAdapt: true, reason: 'token-budget-warning', action: 'optimize-context', severity: 'warning', }); } // Check 2: Complexity mismatch const actualComplexity = this.state.metrics.complexity; const trackComplexity = this.getTrackComplexityRange(this.state.currentTrack); if (actualComplexity > trackComplexity.max * 0.9) { checks.push({ shouldAdapt: true, reason: 'complexity-too-high', action: 'escalate-track', severity: 'high', }); } else if (actualComplexity < trackComplexity.min * 1.1) { checks.push({ shouldAdapt: true, reason: 'complexity-too-low', action: 'deescalate-track', severity: 'low', }); } // Check 3: Quality requirements if (session.projectInfo.requiresHighQuality && this.state.currentTrack === 'quick') { checks.push({ shouldAdapt: true, reason: 'quality-requirements-not-met', action: 'escalate-track', severity: 'high', }); } // Return highest severity check const criticalCheck = checks.find((c) => c.severity === 'critical'); const highCheck = checks.find((c) => c.severity === 'high'); const warningCheck = checks.find((c) => c.severity === 'warning'); return criticalCheck || highCheck || warningCheck || { shouldAdapt: false }; } /** * Adapt workflow */ async adapt(reason, action, session) { console.log(`\nšŸ”„ Adapting workflow: ${reason} → ${action}`); const adaptation = { timestamp: new Date().toISOString(), reason, action, fromTrack: this.state.currentTrack, toTrack: null, changes: [], }; switch (action) { case 'escalate-track': adaptation.toTrack = this.getNextTrack(this.state.currentTrack, 'up'); if (adaptation.toTrack) { this.state.currentTrack = adaptation.toTrack; this.state.budgets = this.initializeBudgets(adaptation.toTrack); adaptation.changes.push('Track escalated'); adaptation.changes.push('Budget increased'); console.log(` āœ“ Escalated to: ${adaptation.toTrack}`); } break; case 'deescalate-track': adaptation.toTrack = this.getNextTrack(this.state.currentTrack, 'down'); if (adaptation.toTrack) { this.state.currentTrack = adaptation.toTrack; this.state.budgets = this.initializeBudgets(adaptation.toTrack); adaptation.changes.push('Track de-escalated'); adaptation.changes.push('Budget optimized'); console.log(` āœ“ De-escalated to: ${adaptation.toTrack}`); } break; case 'reduce-scope': adaptation.changes.push('Skipping optional phases'); adaptation.changes.push('Using lean agents only'); console.log(' āœ“ Scope reduced to stay within budget'); break; case 'optimize-context': adaptation.changes.push('Enabling document sharding'); adaptation.changes.push('Loading critical shards only'); console.log(' āœ“ Context optimization enabled'); break; default: console.log(` āš ļø Unknown action: ${action}`); } // Record adaptation this.state.adaptations.push(adaptation); session.adaptations.push(adaptation); await this.saveSession(session); return adaptation; } /** * Get next track (escalation or de-escalation) */ getNextTrack(currentTrack, direction) { const order = ['quick', 'balanced', 'enterprise']; const currentIndex = order.indexOf(currentTrack); if (direction === 'up') { return currentIndex < order.length - 1 ? order[currentIndex + 1] : null; } else { return currentIndex > 0 ? order[currentIndex - 1] : null; } } /** * Get track complexity range */ getTrackComplexityRange(track) { const ranges = { quick: { min: 0, max: 30 }, balanced: { min: 30, max: 70 }, enterprise: { min: 70, max: 100 }, }; return ranges[track] || ranges.balanced; } /** * Update budget */ updateBudget(type, amount) { if (this.state.budgets[type]) { this.state.budgets[type].used += amount; } } /** * Get budget usage percentage */ getBudgetUsagePercent(type) { if (!this.state.budgets[type]) return 0; const { allocated, used } = this.state.budgets[type]; return allocated > 0 ? (used / allocated) * 100 : 0; } /** * Check if budget exceeded */ isBudgetExceeded(type) { return this.getBudgetUsagePercent(type) >= 100; } /** * Get budget status */ getBudgetStatus() { return { tokens: { allocated: this.state.budgets.tokens.allocated, used: this.state.budgets.tokens.used, remaining: this.state.budgets.tokens.allocated - this.state.budgets.tokens.used, percentUsed: this.getBudgetUsagePercent('tokens'), }, time: { allocated: this.state.budgets.time.allocated, used: this.state.budgets.time.used, remaining: this.state.budgets.time.allocated - this.state.budgets.time.used, percentUsed: this.getBudgetUsagePercent('time'), }, cost: { allocated: this.state.budgets.cost.allocated, used: this.state.budgets.cost.used, remaining: this.state.budgets.cost.allocated - this.state.budgets.cost.used, percentUsed: this.getBudgetUsagePercent('cost'), }, }; } /** * Select optimal agents for current state */ async selectAgents(phaseConfig, session) { const agents = []; const trackConfig = this.tracks.tracks[this.state.currentTrack]; // Get required agents for this phase const requiredAgents = phaseConfig.agents?.required || trackConfig.agents?.required || []; const optionalAgents = phaseConfig.agents?.optional || trackConfig.agents?.optional || []; // Always include required agents agents.push(...requiredAgents); // Include optional agents based on budget const tokenBudgetRemaining = this.getBudgetUsagePercent('tokens') < 80; if (tokenBudgetRemaining) { // Can afford optional agents agents.push(...optionalAgents.slice(0, 2)); // Limit to 2 optional } return agents; } /** * Generate session ID */ generateSessionId() { const timestamp = Date.now(); const random = Math.floor(Math.random() * 1000); return `session_${timestamp}_${random}`; } /** * Save session */ async saveSession(session) { const sessionPath = path.join(this.sessionsDir, `${session.id}.json`); await fs.writeJson(sessionPath, session, { spaces: 2 }); } /** * Load session */ async loadSession(sessionId) { const sessionPath = path.join(this.sessionsDir, `${sessionId}.json`); return await fs.readJson(sessionPath); } /** * Get scaling recommendations */ async getScalingRecommendations(session) { const recommendations = []; // Analyze current state const tokenUsage = this.getBudgetUsagePercent('tokens'); const timeUsage = this.getBudgetUsagePercent('time'); const complexity = this.state.metrics.complexity; // Token budget recommendations if (tokenUsage > 80) { recommendations.push({ type: 'token-optimization', priority: 'high', action: 'Enable document sharding to reduce token usage', impact: 'Could save 60-90% of remaining token budget', }); } // Track recommendations const trackRange = this.getTrackComplexityRange(this.state.currentTrack); if (complexity > trackRange.max) { recommendations.push({ type: 'track-escalation', priority: 'high', action: `Consider escalating to ${this.getNextTrack(this.state.currentTrack, 'up')} track`, impact: 'Better suited for current complexity level', }); } else if (complexity < trackRange.min) { recommendations.push({ type: 'track-deescalation', priority: 'medium', action: `Consider de-escalating to ${this.getNextTrack(this.state.currentTrack, 'down')} track`, impact: 'More cost-effective for current complexity', }); } // Agent recommendations if (tokenUsage < 50 && this.state.currentTrack !== 'quick') { recommendations.push({ type: 'agent-expansion', priority: 'low', action: 'Can afford to include optional agents for better quality', impact: 'Improved output quality with available budget', }); } return recommendations; } /** * Get workflow metrics */ getMetrics() { return { currentTrack: this.state.currentTrack, currentPhase: this.state.currentPhase, budgets: this.getBudgetStatus(), metrics: this.state.metrics, adaptations: this.state.adaptations.length, adaptationHistory: this.state.adaptations, }; } } module.exports = ScaleAdaptiveEngine;