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
JavaScript
/**
* 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;