aiwg
Version:
Cognitive architecture for AI-augmented software development with structured memory, ensemble validation, and closed-loop correction. FAIR-aligned artifacts, 84% cost reduction via human-in-the-loop, standards adopted by 100+ organizations.
467 lines (415 loc) • 13.3 kB
JavaScript
/**
* Gain Scheduler for PID Control System
*
* Provides adaptive gain profiles based on task complexity,
* iteration phase, and observed behavior patterns.
*
* @implements Issue #23 - PID-inspired Control Feedback Loop
* @references REF-015 Self-Refine, REF-021 Reflexion
*
* Gain Profiles:
* - Conservative: For high-risk tasks, slow but stable
* - Standard: Balanced approach for typical tasks
* - Aggressive: For simple tasks, fast convergence
* - Recovery: When stuck or regressing, changes strategy
*/
/**
* @typedef {Object} GainProfile
* @property {string} name - Profile name
* @property {number} kp - Proportional gain
* @property {number} ki - Integral gain
* @property {number} kd - Derivative gain
* @property {string} description - When to use this profile
*/
/**
* @typedef {Object} TaskComplexityFactors
* @property {number} estimatedIterations - Expected iterations to complete
* @property {number} filesAffected - Number of files likely to be modified
* @property {boolean} hasTests - Whether tests exist/are expected
* @property {boolean} securitySensitive - Security-critical task
* @property {boolean} breakingChanges - May cause breaking changes
* @property {string} domainComplexity - 'low' | 'medium' | 'high'
*/
/**
* Predefined gain profiles
*/
export const GAIN_PROFILES = {
/**
* Conservative: For high-risk, security-sensitive, or breaking changes
* - Low Kp: Small corrections to avoid overshooting
* - Low Ki: Slow accumulation prevents drastic changes
* - High Kd: Strong damping to prevent oscillation
*/
conservative: {
name: 'conservative',
kp: 0.3,
ki: 0.05,
kd: 0.4,
description: 'High-risk tasks: security, breaking changes, compliance',
},
/**
* Standard: Balanced approach for typical development tasks
* - Moderate Kp: Reasonable response to error
* - Moderate Ki: Steady progress accumulation
* - Moderate Kd: Some damping for stability
*/
standard: {
name: 'standard',
kp: 0.5,
ki: 0.15,
kd: 0.25,
description: 'Typical development tasks with moderate complexity',
},
/**
* Aggressive: For simple tasks where fast convergence is desired
* - High Kp: Strong response to completion gap
* - High Ki: Quick accumulation for persistent issues
* - Low Kd: Less damping allows faster movement
*/
aggressive: {
name: 'aggressive',
kp: 0.8,
ki: 0.25,
kd: 0.1,
description: 'Simple tasks: documentation, config, straightforward fixes',
},
/**
* Recovery: When system is stuck or regressing
* - Very High Kp: Strong push to escape local minimum
* - Very High Ki: Break through persistent blockers
* - Negative Kd: Actually accelerate change when stuck
*/
recovery: {
name: 'recovery',
kp: 1.0,
ki: 0.4,
kd: -0.1,
description: 'Stuck or regressing: needs strategy change',
},
/**
* Cautious: For tasks near completion
* - Low Kp: Gentle corrections
* - Very Low Ki: Prevent overshoot from accumulation
* - Very High Kd: Strong damping for precise landing
*/
cautious: {
name: 'cautious',
kp: 0.2,
ki: 0.02,
kd: 0.5,
description: 'Near completion: fine-tuning phase',
},
};
export class GainScheduler {
/**
* @param {Object} options
* @param {GainProfile} [options.initialProfile] - Starting profile
* @param {boolean} [options.adaptiveEnabled=true] - Enable adaptive scheduling
* @param {number} [options.transitionSmoothing=0.3] - Smoothing factor for transitions
*/
constructor(options = {}) {
this.currentProfile = options.initialProfile || GAIN_PROFILES.standard;
this.adaptiveEnabled = options.adaptiveEnabled !== false;
this.transitionSmoothing = options.transitionSmoothing || 0.3;
// Track profile history for analysis
this.profileHistory = [];
// Current interpolated gains (for smooth transitions)
this.effectiveGains = { ...this.currentProfile };
// Task complexity assessment
this.taskComplexity = null;
}
/**
* Assess task complexity and select initial profile
* @param {TaskComplexityFactors} factors
* @returns {GainProfile}
*/
assessTaskComplexity(factors) {
this.taskComplexity = factors;
let complexityScore = 0;
// Estimated iterations (more = more complex)
if (factors.estimatedIterations > 10) {
complexityScore += 2;
} else if (factors.estimatedIterations > 5) {
complexityScore += 1;
}
// Files affected
if (factors.filesAffected > 20) {
complexityScore += 2;
} else if (factors.filesAffected > 5) {
complexityScore += 1;
}
// Risk factors
if (factors.securitySensitive) {
complexityScore += 3;
}
if (factors.breakingChanges) {
complexityScore += 2;
}
// Domain complexity
if (factors.domainComplexity === 'high') {
complexityScore += 2;
} else if (factors.domainComplexity === 'medium') {
complexityScore += 1;
}
// Select profile based on score
let profile;
if (complexityScore >= 6 || factors.securitySensitive) {
profile = GAIN_PROFILES.conservative;
} else if (complexityScore >= 3) {
profile = GAIN_PROFILES.standard;
} else {
profile = GAIN_PROFILES.aggressive;
}
this.setProfile(profile, 'initial_assessment');
return profile;
}
/**
* Set the current gain profile
* @param {GainProfile} profile
* @param {string} [reason] - Why the profile changed
*/
setProfile(profile, reason = 'manual') {
this.currentProfile = profile;
// Also update effectiveGains for immediate effect
this.effectiveGains = { ...profile };
this.profileHistory.push({
profile: profile.name,
reason,
timestamp: Date.now(),
});
// Keep history bounded
if (this.profileHistory.length > 50) {
this.profileHistory = this.profileHistory.slice(-50);
}
}
/**
* Get current effective gains (with smoothing applied)
* @returns {GainProfile}
*/
getEffectiveGains() {
return this.effectiveGains;
}
/**
* Update gains based on current system state
* @param {Object} state
* @param {number} state.proportional - Current P error
* @param {number} state.integral - Current I accumulation
* @param {number} state.derivative - Current D rate
* @param {string} state.trend - 'improving' | 'stable' | 'regressing' | 'oscillating'
* @param {number} state.iterationNumber - Current iteration
* @param {number} state.maxIterations - Maximum iterations
* @returns {GainProfile}
*/
update(state) {
if (!this.adaptiveEnabled) {
this.effectiveGains = { ...this.currentProfile };
return this.currentProfile;
}
// Phase-based adjustment
const progress = state.iterationNumber / state.maxIterations;
let targetProfile = this.currentProfile;
// Check for recovery condition
if (this.needsRecovery(state)) {
targetProfile = GAIN_PROFILES.recovery;
this.setProfile(targetProfile, 'recovery_triggered');
}
// Check if near completion
else if (state.proportional < 0.15 && progress > 0.5) {
targetProfile = GAIN_PROFILES.cautious;
if (this.currentProfile.name !== 'cautious') {
this.setProfile(targetProfile, 'near_completion');
}
}
// Check for oscillation
else if (state.trend === 'oscillating') {
// Increase damping
targetProfile = this.createDampedProfile();
this.setProfile(targetProfile, 'oscillation_damping');
}
// Check for regression
else if (state.trend === 'regressing' && state.derivative > 0.1) {
targetProfile = GAIN_PROFILES.conservative;
if (this.currentProfile.name !== 'conservative') {
this.setProfile(targetProfile, 'regression_detected');
}
}
// Apply smooth transition to target profile
this.effectiveGains = this.smoothTransition(
this.effectiveGains,
targetProfile
);
return this.effectiveGains;
}
/**
* Check if system needs recovery mode
* @param {Object} state
* @returns {boolean}
*/
needsRecovery(state) {
// Stuck: high error with no progress for multiple iterations
const stuck = state.proportional > 0.7 &&
Math.abs(state.derivative) < 0.02 &&
state.iterationNumber > 3;
// Regressing significantly
const regressing = state.derivative > 0.15;
// Integral windup (persistent errors)
const windingUp = state.integral > 3.0;
return stuck || regressing || windingUp;
}
/**
* Create a damped profile to reduce oscillation
* @returns {GainProfile}
*/
createDampedProfile() {
return {
name: 'damped',
kp: this.currentProfile.kp * 0.7,
ki: this.currentProfile.ki * 0.5,
kd: Math.min(this.currentProfile.kd * 1.5, 0.6),
description: 'Dynamically damped to reduce oscillation',
};
}
/**
* Smooth transition between profiles
* @param {GainProfile} current
* @param {GainProfile} target
* @returns {GainProfile}
*/
smoothTransition(current, target) {
const alpha = this.transitionSmoothing;
return {
name: target.name,
kp: current.kp + alpha * (target.kp - current.kp),
ki: current.ki + alpha * (target.ki - current.ki),
kd: current.kd + alpha * (target.kd - current.kd),
description: target.description,
};
}
/**
* Calculate control output from PID metrics
* @param {Object} metrics - PID metrics from collector
* @returns {Object} - Control signals
*/
calculateControlOutput(metrics) {
const gains = this.effectiveGains;
// PID control equation
const pTerm = gains.kp * metrics.proportional;
const iTerm = gains.ki * metrics.integral;
const dTerm = gains.kd * metrics.derivative;
const controlSignal = pTerm + iTerm + dTerm;
// Interpret control signal as adjustment recommendations
return {
controlSignal,
pTerm,
iTerm,
dTerm,
recommendations: this.interpretControlSignal(controlSignal, metrics),
};
}
/**
* Interpret control signal into actionable recommendations
* @param {number} signal
* @param {Object} metrics
* @returns {Object}
*/
interpretControlSignal(signal, metrics) {
const recommendations = {
urgency: 'normal',
adjustBudget: false,
adjustTimeout: false,
changeStrategy: false,
escalate: false,
message: '',
};
// High control signal = urgent action needed
if (signal > 0.8) {
recommendations.urgency = 'critical';
recommendations.changeStrategy = true;
recommendations.message = 'Task significantly behind - consider strategy change';
} else if (signal > 0.5) {
recommendations.urgency = 'high';
recommendations.adjustBudget = true;
recommendations.message = 'Task behind schedule - may need more resources';
} else if (signal > 0.3) {
recommendations.urgency = 'elevated';
recommendations.message = 'Task progressing slowly - monitor closely';
} else if (signal < 0.1) {
recommendations.urgency = 'low';
recommendations.message = 'Task on track - continue current approach';
}
// Specific recommendations based on PID components
if (metrics.integral > 2.0) {
recommendations.escalate = true;
recommendations.message += '; Persistent issues detected - may need human review';
}
if (metrics.derivative > 0.1) {
recommendations.adjustTimeout = true;
recommendations.message += '; Progress slowing - consider extending timeout';
}
return recommendations;
}
/**
* Get a profile by name
* @param {string} name
* @returns {GainProfile|null}
*/
getProfileByName(name) {
return GAIN_PROFILES[name] || null;
}
/**
* Get all available profiles
* @returns {Object<string, GainProfile>}
*/
getAllProfiles() {
return { ...GAIN_PROFILES };
}
/**
* Get profile history
* @returns {Array}
*/
getProfileHistory() {
return [...this.profileHistory];
}
/**
* Reset scheduler state
*/
reset() {
this.currentProfile = GAIN_PROFILES.standard;
this.effectiveGains = { ...this.currentProfile };
this.profileHistory = [];
this.taskComplexity = null;
}
/**
* Export state for persistence
* @returns {Object}
*/
exportState() {
return {
currentProfile: this.currentProfile.name,
effectiveGains: this.effectiveGains,
profileHistory: this.profileHistory,
taskComplexity: this.taskComplexity,
adaptiveEnabled: this.adaptiveEnabled,
transitionSmoothing: this.transitionSmoothing,
};
}
/**
* Import state from persistence
* @param {Object} state
*/
importState(state) {
if (state.currentProfile) {
this.currentProfile = GAIN_PROFILES[state.currentProfile] || GAIN_PROFILES.standard;
}
if (state.effectiveGains) {
this.effectiveGains = state.effectiveGains;
}
if (state.profileHistory) {
this.profileHistory = state.profileHistory;
}
if (state.taskComplexity) {
this.taskComplexity = state.taskComplexity;
}
}
}
export default GainScheduler;