UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

844 lines (666 loc) 23.3 kB
# PID Control Layer Documentation ## Overview The PID Control layer provides adaptive loop management for External Ralph through a control theory-inspired feedback system. It continuously monitors loop progress and dynamically adjusts execution parameters to optimize convergence, prevent stuck states, and maintain system stability. ### Why PID Control for Agent Loops? Traditional software loops execute with fixed parameters regardless of runtime conditions. The PID Control layer brings adaptive intelligence by: - **Responding to current error** (Proportional) - How far from completion - **Accounting for persistent issues** (Integral) - Accumulated learnings and repeated blockers - **Predicting future trends** (Derivative) - Rate of progress change This creates loops that self-correct, adapt to task complexity, and avoid common failure modes like oscillation and stagnation. ### Control Theory Basics A PID controller calculates a control output using three components: ``` control_output = Kp * P + Ki * I + Kd * D Where: P = Proportional error (current completion gap) I = Integral accumulation (persistent issues over time) D = Derivative rate (velocity of progress) Kp, Ki, Kd = Gain coefficients (tunable weights) ``` Each component addresses different temporal aspects: | Component | Time Focus | Purpose | Ralph Mapping | |-----------|------------|---------|---------------| | **Proportional** | Present | React to current error | Completion gap (1 - progress) | | **Integral** | Past | Correct persistent offset | Accumulated learnings, repeated blockers | | **Derivative** | Future | Dampen oscillation | Rate of progress change (velocity) | ## Architecture ``` ┌─────────────────────────────────────────────────────────┐ PIDController Integrates all components and provides control decisions for orchestrator └────┬──────────────────────────────────────────────┬─────┘ ├──────────────┬──────────────┬────────────────┤ ┌────▼──────┐ ┌────▼──────┐ ┌────▼────────┐ ┌─────▼──────┐ Metrics Gain Control Process Collector Scheduler Alarms Monitor └───────────┘ └───────────┘ └─────────────┘ └────────────┘ Flow: 1. MetricsCollector extracts P/I/D from iteration data 2. GainScheduler adapts Kp/Ki/Kd based on task/phase 3. PIDController computes control signal 4. ControlAlarms detect pathological behaviors 5. Control decision affects next iteration ``` ## Component 1: PIDController **Purpose**: Core control logic that integrates metrics collection, gain scheduling, and alarm monitoring to provide intelligent control decisions. ### Initialization ```javascript import { PIDController } from './pid-controller.mjs'; const controller = new PIDController({ windowSize: 5, // Metrics moving window integralDecay: 0.9, // Integral decay factor noiseThreshold: 0.05, // Deadband for noise filtering adaptiveGains: true, // Enable gain scheduling autoIntervene: false, // Auto-apply interventions thresholds: {}, // Custom alarm thresholds initialProfile: 'standard', // Initial gain profile }); // Initialize for a specific task controller.initialize({ objective: 'Fix authentication bug', completionCriteria: 'Tests pass', maxIterations: 10, estimatedComplexity: 7, // 1-10 scale securitySensitive: true, breakingChanges: false, }); ``` ### Processing Iterations ```javascript // After each iteration const decision = controller.process(iteration, state); console.log(decision); // { // action: 'continue' | 'adjust' | 'pause' | 'abort', // controlSignal: 0.45, // urgency: 'normal' | 'elevated' | 'high' | 'critical', // recommendations: { // adjustBudget: false, // changeStrategy: false, // escalate: false, // message: 'Task on track...' // }, // alarmSummary: { // activeCount: 1, // bySeverity: { warning: 1, ... } // }, // metrics: { // proportional: 0.3, // integral: 0.5, // derivative: -0.05, // trend: 'improving', // velocity: 0.08 // }, // gains: { kp: 0.5, ki: 0.15, kd: 0.25 } // } ``` ### Control Decisions | Action | Meaning | Trigger | |--------|---------|---------| | `continue` | Proceed normally | No critical alarms, progressing | | `adjust` | Modify approach | Warning alarms, slow progress | | `pause` | Request human review | Critical alarms, high iteration usage | | `abort` | Stop immediately | Emergency alarms, unrecoverable state | ### Prompt Adjustments Get context to inject into Claude prompts: ```javascript const adjustments = controller.getPromptAdjustments(); console.log(adjustments); // { // adjustments: [ // 'Focus on completing the most critical remaining work', // 'Address recurring issues: null check failures' // ], // context: 'Urgency level: high. Issues appearing multiple times: 3', // metrics: { ... }, // gains: { ... } // } ``` ### State Persistence ```javascript // Export state for crash recovery const state = controller.exportState(); saveToFile('.aiwg/ralph/pid-state.json', state); // Restore from saved state const savedState = loadFromFile('.aiwg/ralph/pid-state.json'); controller.importState(savedState); ``` ## Component 2: MetricsCollector **Purpose**: Extracts Proportional, Integral, and Derivative metrics from agent loop iteration data. ### Metric Definitions #### Proportional (P): Current Error Represents the **completion gap** at this moment: ```javascript P = (1.0 - completionPercentage) + qualityPenalty + errorPenalty where: qualityPenalty = (1.0 - qualityScore) * 0.2 errorPenalty = min(errorCount * 0.05, 0.3) ``` **Range**: 0.0 (complete) to 1.0+ (no progress, with quality/error penalties) #### Integral (I): Accumulated Error Represents **persistent issues** over time: ```javascript I_new = I_old * decayFactor + P + repeatedBlockerPenalty - learningsCredit where: decayFactor = 0.9 (prevents windup) repeatedBlockerPenalty = 0.1 * occurrenceCount learningsCredit = learningsCount * 0.05 ``` **Range**: -1.0 (many learnings) to 5.0 (max windup limit) #### Derivative (D): Rate of Change Represents **velocity** of progress: ```javascript D = weightedAverage(recentErrorChanges) where recent changes are weighted: - More recent = higher weight - Window size = 5 iterations (default) ``` **Range**: -1.0 (rapid improvement) to +1.0 (rapid regression) ### Usage ```javascript import { MetricsCollector } from './metrics-collector.mjs'; const collector = new MetricsCollector({ windowSize: 5, // Derivative window integralDecay: 0.9, // Anti-windup decay noiseThreshold: 0.05, // Ignore changes < 5% }); // Extract metrics from iteration const pidMetrics = collector.collect(iteration); console.log(pidMetrics); // { // proportional: 0.45, // integral: 1.2, // derivative: -0.08, // timestamp: 1706198400000, // iterationNumber: 3, // raw: { // completionPercentage: 0.55, // qualityScore: 0.8, // errorCount: 2, // testsPassing: 8, // testsFailing: 2, // learnings: ['Fixed auth token expiry'], // blockers: ['Database connection timeout'] // } // } ``` ### Metric Formulas **Proportional Calculation**: ```javascript calculateProportional(metrics) { const completionGap = 1.0 - metrics.completionPercentage; const qualityPenalty = (1.0 - metrics.qualityScore) * 0.2; const errorPenalty = Math.min(metrics.errorCount * 0.05, 0.3); const rawP = completionGap + qualityPenalty + errorPenalty; return applyDeadband(Math.min(rawP, 1.0)); } ``` **Integral Calculation**: ```javascript calculateIntegral(metrics, proportional) { // Decay previous value integralAccumulator *= 0.9; // Add current error integralAccumulator += proportional; // Add repeated blocker penalty for (const blocker of metrics.blockers) { const count = issueFrequency.get(blocker) || 0; if (count > 1) { integralAccumulator += 0.1 * count; } } // Subtract learnings credit const learningsCredit = metrics.learnings.length * 0.05; return applyAntiWindup(integralAccumulator - learningsCredit); } ``` **Derivative Calculation**: ```javascript calculateDerivative(metrics, proportional) { const recent = history.slice(-windowSize); let sumWeightedChange = 0; let sumWeights = 0; for (let i = 1; i < recent.length; i++) { const weight = i / recent.length; const change = recent[i].proportional - recent[i-1].proportional; sumWeightedChange += change * weight; sumWeights += weight; } const derivative = sumWeightedChange / sumWeights; return applyDeadband(derivative); } ``` ### Noise Filtering Deadband filter ignores small changes: ```javascript applyDeadband(value) { if (Math.abs(value) < noiseThreshold) { return 0; } return value; } ``` ### Anti-Windup Bounds prevent integral saturation: ```javascript applyAntiWindup(integral) { const maxIntegral = 5.0; const minIntegral = -1.0; return Math.max(minIntegral, Math.min(integral, maxIntegral)); } ``` ### Trend Analysis ```javascript const trend = collector.getTrend(); console.log(trend); // { // trend: 'improving' | 'stable' | 'regressing' | 'oscillating', // velocity: 0.08, // iterationsAnalyzed: 5, // repeatedIssues: [ // { issue: 'null check failure', count: 3 }, // { issue: 'timeout', count: 2 } // ] // } ``` ## Component 3: GainScheduler **Purpose**: Adaptive gain tuning based on task complexity, loop phase, and observed behavior patterns. ### Gain Profiles Five predefined profiles optimized for different scenarios: | Profile | Kp | Ki | Kd | When to Use | |---------|----|----|----|----| | **Conservative** | 0.3 | 0.05 | 0.4 | High-risk, security-sensitive, breaking changes | | **Standard** | 0.5 | 0.15 | 0.25 | Typical development tasks (default) | | **Aggressive** | 0.8 | 0.25 | 0.1 | Simple tasks (docs, config, minor fixes) | | **Recovery** | 1.0 | 0.4 | -0.1 | Stuck or regressing (escape local minimum) | | **Cautious** | 0.2 | 0.02 | 0.5 | Near completion (fine-tuning phase) | **Profile Characteristics**: - **Conservative**: High damping (Kd=0.4), low response Stable but slow - **Standard**: Balanced Most tasks - **Aggressive**: Low damping (Kd=0.1), high response Fast but riskier - **Recovery**: Negative Kd Accelerate change when stuck - **Cautious**: Very high damping (Kd=0.5) Precise landing ### Usage ```javascript import { GainScheduler, GAIN_PROFILES } from './gain-scheduler.mjs'; const scheduler = new GainScheduler({ initialProfile: GAIN_PROFILES.standard, adaptiveEnabled: true, transitionSmoothing: 0.3, }); // Assess task complexity scheduler.assessTaskComplexity({ estimatedIterations: 8, filesAffected: 15, hasTests: true, securitySensitive: true, breakingChanges: false, domainComplexity: 'high', }); ``` ### Adaptive Gain Updates Gains automatically adjust based on system state: ```javascript const gains = scheduler.update({ proportional: 0.45, integral: 1.2, derivative: -0.08, trend: 'improving', iterationNumber: 5, maxIterations: 10, }); console.log(gains); // { // name: 'cautious', // kp: 0.2, // ki: 0.02, // kd: 0.5, // description: 'Near completion: fine-tuning phase' // } ``` ### Transition Conditions | Condition | New Profile | Reason | |-----------|-------------|--------| | Stuck (P > 0.7, \|D\| < 0.02, iter > 3) | Recovery | Escape stagnation | | Near completion (P < 0.15, progress > 50%) | Cautious | Precision landing | | Oscillating (alternating improvements/regressions) | Damped | Reduce oscillation | | Regressing (D > 0.1) | Conservative | Stabilize | | Integral windup (I > 3.0) | Recovery | Break through blockers | ### Smooth Transitions Profiles transition gradually to avoid discontinuities: ```javascript smoothTransition(current, target) { const alpha = 0.3; // Smoothing factor return { kp: current.kp + alpha * (target.kp - current.kp), ki: current.ki + alpha * (target.ki - current.ki), kd: current.kd + alpha * (target.kd - current.kd), }; } ``` ### Control Output Calculation ```javascript const controlOutput = scheduler.calculateControlOutput({ proportional: 0.45, integral: 1.2, derivative: -0.08, }); console.log(controlOutput); // { // controlSignal: 0.53, // pTerm: 0.225, // 0.5 * 0.45 // iTerm: 0.18, // 0.15 * 1.2 // dTerm: -0.02, // 0.25 * -0.08 // recommendations: { // urgency: 'elevated', // adjustBudget: true, // changeStrategy: false, // escalate: false, // message: 'Task behind schedule - may need more resources' // } // } ``` ### Interpretation Control signal influences urgency and recommendations: | Control Signal | Urgency | Meaning | |----------------|---------|---------| | > 0.8 | Critical | Significantly behind, strategy change needed | | 0.5 - 0.8 | High | Behind schedule, may need more resources | | 0.3 - 0.5 | Elevated | Progressing slowly, monitor closely | | < 0.3 | Normal/Low | On track or completing | ## Component 4: ControlAlarms **Purpose**: Detect pathological loop behaviors and trigger appropriate interventions. ### Alarm Types | Alarm | Trigger | Severity | Intervention | |-------|---------|----------|--------------| | **Stuck Loop** | No progress for N iterations | Warning/Critical | Change approach, request human | | **Oscillation** | Alternating improvement/regression | Warning | Increase damping (Kd) | | **Regression** | Consistent negative progress | Warning/Critical | Revert changes, switch to recovery | | **Resource Burn** | High iteration usage | Critical/Emergency | Increase budget or abort | | **Quality Degradation** | Declining quality scores | Warning/Critical | Review changes, focus on quality | | **Integral Windup** | Excessive accumulation | Warning | Address persistent blockers | | **Derivative Spike** | Sudden velocity change | Info/Warning | Investigate cause | ### Usage ```javascript import { ControlAlarms, DEFAULT_THRESHOLDS } from './control-alarms.mjs'; const alarms = new ControlAlarms({ thresholds: { stuckIterations: 3, oscillationCount: 2, regressionRate: 0.1, maxIterationsPercent: 0.8, qualityDropThreshold: 0.15, integralWindupLimit: 4.0, derivativeSpike: 0.2, minProgressRate: 0.02, }, onAlarm: (alarm) => { console.log(`[ALARM] ${alarm.severity}: ${alarm.message}`); }, autoIntervene: false, }); // Check for alarms const newAlarms = alarms.check(metrics, systemState); if (newAlarms.length > 0) { console.log(`Raised ${newAlarms.length} new alarms`); } ``` ### Alarm Object ```javascript { id: 'alarm-42-1706198400000', type: 'stuck_loop', severity: 'critical', message: 'Loop stuck for 3 iterations with error=0.65', timestamp: 1706198400000, context: { iterationsStuck: 3, errorLevel: 0.65, errorChange: 0.02 }, acknowledged: false, interventions: [ 'Change approach or strategy', 'Break task into smaller sub-tasks', 'Request human guidance', 'Increase iteration budget' ] } ``` ### Severity Levels | Severity | Impact | Action Required | |----------|--------|-----------------| | `info` | Informational | Monitor | | `warning` | Suboptimal behavior | Adjust approach | | `critical` | Serious issue | Human review recommended | | `emergency` | Unrecoverable | Immediate abort | ### Intervention Levels ```javascript import { INTERVENTION_LEVELS } from './control-alarms.mjs'; const level = alarms.suggestIntervention(alarm); // INTERVENTION_LEVELS: // - NONE: No intervention // - ADJUST: Adjust parameters // - NUDGE: Suggest new approach // - PAUSE: Request human review // - ABORT: Stop immediately ``` ### Alarm Management ```javascript // Get all active alarms const active = alarms.getActiveAlarms(); // Get highest severity const highest = alarms.getHighestSeverityAlarm(); // Acknowledge an alarm alarms.acknowledgeAlarm('alarm-42-1706198400000'); // Clear specific alarm type alarms.clearAlarm('stuck_loop'); // Clear all alarms alarms.clearAllAlarms(); // Get summary const summary = alarms.getSummary(); // { // activeCount: 2, // bySeverity: { // emergency: 0, // critical: 1, // warning: 1, // info: 0 // }, // types: ['stuck_loop', 'quality_degradation'], // highestSeverity: 'critical', // totalHistorical: 15 // } ``` ### Pattern Detection **Stuck Loop Detection**: ```javascript // Triggers when: // - Error hasn't changed significantly for N iterations // - Derivative near zero (no velocity) // Severity: Warning if error < 0.5, Critical if error > 0.5 ``` **Oscillation Detection**: ```javascript // Triggers when: // - Direction changes N times in recent window // - Each change magnitude > threshold // Severity: Warning (always) ``` **Regression Detection**: ```javascript // Triggers when: // - Derivative > regressionRate (error increasing) // Severity: Warning if D > 0.1, Critical if D > 0.2 ``` **Resource Burn Detection**: ```javascript // Triggers when: // - Iteration usage > maxIterationsPercent // Severity: Critical at 80%, Emergency at 95% ``` ## Integration with Orchestrator The PID Control layer integrates into the orchestrator's main loop: ```javascript // Initialize PID controller at loop start if (config.enablePIDControl) { this.pidController = new PIDController({ ... }); this.pidController.initialize({ objective: config.objective, completionCriteria: config.completionCriteria, maxIterations: config.maxIterations, estimatedComplexity: 7, }); } // During iteration const decision = this.pidController.process(iteration, { currentIteration: 5, maxIterations: 10, }); // Apply control decisions if (decision.action === 'pause') { // Request human review await escalationHandler.escalate({ level: 'high', reason: decision.recommendations.message, }); } // Adjust prompts based on control signals const promptAdjustments = this.pidController.getPromptAdjustments(); prompt = injectAdjustments(prompt, promptAdjustments); ``` ## Configuration Examples ### Simple Task (Documentation) ```javascript const controller = new PIDController({ initialProfile: GAIN_PROFILES.aggressive, adaptiveGains: false, // Fixed gains }); controller.initialize({ objective: 'Update API documentation', completionCriteria: 'All endpoints documented', maxIterations: 5, estimatedComplexity: 2, }); ``` ### Security-Critical Task ```javascript const controller = new PIDController({ initialProfile: GAIN_PROFILES.conservative, adaptiveGains: true, thresholds: { stuckIterations: 2, // Detect sooner regressionRate: 0.05, // Lower tolerance }, }); controller.initialize({ objective: 'Fix authentication vulnerability', completionCriteria: 'Security tests pass', maxIterations: 8, estimatedComplexity: 9, securitySensitive: true, }); ``` ### Long-Running Complex Task ```javascript const controller = new PIDController({ windowSize: 7, // Longer window integralDecay: 0.85, // Slower decay adaptiveGains: true, }); controller.initialize({ objective: 'Refactor authentication system', completionCriteria: 'All tests pass, no regressions', maxIterations: 15, estimatedComplexity: 10, breakingChanges: true, }); ``` ## Troubleshooting ### Problem: Loop Oscillating **Symptoms**: Progress alternates between improvement and regression. **Solution**: ```javascript // Increase damping (Kd) scheduler.setProfile({ ...GAIN_PROFILES.standard, kd: 0.4, // Higher damping }); ``` ### Problem: Loop Stuck **Symptoms**: No progress for multiple iterations. **Solution**: ```javascript // Switch to recovery profile scheduler.setProfile(GAIN_PROFILES.recovery); // Or increase urgency const decision = controller.process(iteration, state); if (decision.metrics.proportional > 0.7) { // Manual intervention } ``` ### Problem: Integral Windup **Symptoms**: Integral value continuously growing, excessive accumulated error. **Solution**: ```javascript // Increase decay rate const collector = new MetricsCollector({ integralDecay: 0.8, // Faster decay (default: 0.9) }); // Or address repeated blockers directly const repeatedIssues = collector.getRepeatedIssues(); console.log('Fix these first:', repeatedIssues); ``` ### Problem: Too Sensitive to Noise **Symptoms**: Control signals fluctuate wildly from small changes. **Solution**: ```javascript // Increase noise threshold const collector = new MetricsCollector({ noiseThreshold: 0.10, // Ignore changes < 10% }); ``` ### Problem: Too Many False Alarms **Symptoms**: Alarms triggering too frequently for normal conditions. **Solution**: ```javascript // Adjust thresholds const alarms = new ControlAlarms({ thresholds: { stuckIterations: 5, // Tolerate longer oscillationCount: 3, // More oscillations minProgressRate: 0.01, // Lower progress requirement }, }); ``` ## Performance Characteristics | Metric | Value | Notes | |--------|-------|-------| | Overhead per iteration | < 10ms | Lightweight calculations | | Memory usage | ~1MB | Bounded history windows | | State persistence | ~5KB | JSON serialization | | Response time | Immediate | No async operations | ## Best Practices 1. **Start with standard profile** - Most tasks work well with default settings 2. **Enable adaptive gains** - Let the system adjust automatically 3. **Monitor alarm patterns** - Recurring alarms indicate systemic issues 4. **Use conservative for critical paths** - Security and breaking changes 5. **Check control signals** - High signals indicate struggling loops 6. **Preserve state** - Export/import for crash recovery 7. **Review metrics history** - Understand loop behavior over time 8. **Tune thresholds cautiously** - Defaults are research-backed 9. **Watch integral accumulation** - Sign of persistent blockers 10. **Use recovery mode sparingly** - Only when genuinely stuck ## References - Issue #23: PID-inspired Control Feedback Loop - REF-015: Self-Refine (Madaan et al., 2023) - REF-021: Reflexion (Shinn et al., 2023) - Classic PID Control Theory (Åström & Hägglund) ## See Also - `README-state-assessment.md` - State assessment and prompt generation - `README-memory.md` - Memory and learning systems - `README-snapshot.md` - Snapshot and checkpoint management - Orchestrator integration in `orchestrator.mjs`