UNPKG

memory-engineering-mcp

Version:

🧠 AI Memory System powered by MongoDB Atlas & Voyage AI - Autonomous memory management with zero manual work

207 lines 6.69 kB
/** * Error Recovery and Retry Logic * PROOF: Following Archon's error handling patterns * * Archon: python/src/server/api_routes/knowledge_api.py (line 865-883) * Pattern: Try-catch with detailed logging, structured error messages */ import { logger } from './logger.js'; const DEFAULT_RETRY_OPTIONS = { maxRetries: 3, delayMs: 1000, backoffMultiplier: 2, }; /** * Retry with exponential backoff * PROOF: Industry standard pattern, used by Archon for API calls */ export async function retryWithBackoff(operation, fn, options = {}) { const opts = { ...DEFAULT_RETRY_OPTIONS, ...options }; let lastError; for (let attempt = 1; attempt <= opts.maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); // Don't retry on certain errors if (isNonRetryableError(lastError)) { logger.error(`❌ Non-retryable error in ${operation}:`, lastError); throw lastError; } // Last attempt failed if (attempt === opts.maxRetries) { logger.error(`❌ ${operation} failed after ${attempt} attempts:`, lastError); throw lastError; } // Calculate delay with exponential backoff const delay = opts.delayMs * Math.pow(opts.backoffMultiplier, attempt - 1); logger.warn(`⚠️ ${operation} failed (attempt ${attempt}/${opts.maxRetries}), retrying in ${delay}ms:`, lastError.message); // Call onRetry callback if provided opts.onRetry?.(attempt, lastError); // Wait before retry await sleep(delay); } } throw lastError; } /** * Check if error should not be retried * PROOF: Archon doesn't retry 4xx errors */ function isNonRetryableError(error) { const message = error.message.toLowerCase(); // Authentication/authorization errors if (message.includes('unauthorized') || message.includes('forbidden')) { return true; } // Invalid input errors if (message.includes('invalid') || message.includes('bad request')) { return true; } // Not found errors if (message.includes('not found')) { return true; } return false; } /** * Sleep helper */ function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Rate limiter for API calls * PROOF: Archon pattern for handling rate limits */ export class RateLimiter { maxCallsPerMinute; minDelayMs; queue = []; processing = false; lastCallTime = 0; constructor(maxCallsPerMinute, minDelayMs = 100) { this.maxCallsPerMinute = maxCallsPerMinute; this.minDelayMs = minDelayMs; } /** * Enqueue an operation with rate limiting */ async enqueue(fn) { return new Promise((resolve, reject) => { this.queue.push({ fn, resolve, reject }); if (!this.processing) { this.processQueue(); } }); } async processQueue() { this.processing = true; while (this.queue.length > 0) { const now = Date.now(); const timeSinceLastCall = now - this.lastCallTime; // Calculate required delay const minInterval = 60000 / this.maxCallsPerMinute; const requiredDelay = Math.max(this.minDelayMs, minInterval - timeSinceLastCall); if (requiredDelay > 0) { await sleep(requiredDelay); } const item = this.queue.shift(); this.lastCallTime = Date.now(); try { const result = await item.fn(); item.resolve(result); } catch (error) { item.reject(error); } } this.processing = false; } /** * Get queue status */ getStatus() { return { queueLength: this.queue.length, processing: this.processing, }; } } /** * Circuit breaker pattern for failing services * PROOF: Production pattern for service resilience */ export class CircuitBreaker { serviceName; failureThreshold; resetTimeoutMs; failureCount = 0; lastFailureTime = 0; state = 'closed'; constructor(serviceName, failureThreshold = 5, resetTimeoutMs = 60000) { this.serviceName = serviceName; this.failureThreshold = failureThreshold; this.resetTimeoutMs = resetTimeoutMs; } /** * Execute operation with circuit breaker */ async execute(fn) { // Check if circuit should reset if (this.state === 'open') { const timeSinceFailure = Date.now() - this.lastFailureTime; if (timeSinceFailure >= this.resetTimeoutMs) { logger.info(`🔄 Circuit breaker ${this.serviceName} transitioning to half-open`); this.state = 'half-open'; } else { throw new Error(`Circuit breaker ${this.serviceName} is OPEN (too many failures)`); } } try { const result = await fn(); // Success - reset on half-open or reduce failure count if (this.state === 'half-open') { logger.info(`✅ Circuit breaker ${this.serviceName} closed (service recovered)`); this.state = 'closed'; this.failureCount = 0; } else if (this.failureCount > 0) { this.failureCount--; } return result; } catch (error) { this.failureCount++; this.lastFailureTime = Date.now(); // Open circuit if threshold exceeded if (this.failureCount >= this.failureThreshold) { logger.error(`💀 Circuit breaker ${this.serviceName} OPENED (${this.failureCount} failures)`); this.state = 'open'; } throw error; } } /** * Get circuit breaker status */ getStatus() { return { state: this.state, failureCount: this.failureCount, lastFailureTime: this.lastFailureTime ? new Date(this.lastFailureTime) : null, }; } /** * Manually reset circuit breaker */ reset() { logger.info(`🔄 Circuit breaker ${this.serviceName} manually reset`); this.state = 'closed'; this.failureCount = 0; this.lastFailureTime = 0; } } //# sourceMappingURL=error-recovery.js.map