/** * Context Handoff Protocol * * Manages context transfer between agents for seamless delegation. * Ensures proper serialization, validation, and cleanup of handoffs. */ import { ContextHandoff, HandoffResult, ConversationTurn, DelegationContext } from '../core/types'; // ============================================================================ // Handoff Configuration // ============================================================================ interface HandoffConfig { defaultTimeout: number; defaultTimeLimit: number; maxHandoffs: number; cleanupInterval: number; handoffTTL: number; } const DEFAULT_CONFIG: HandoffConfig = { defaultTimeout: 30000, defaultTimeLimit: 60000, maxHandoffs: 100, cleanupInterval: 60000, handoffTTL: 300000 // 5 minutes }; // ============================================================================ // Context Handoff Manager Class // ============================================================================ export class ContextHandoffManager { private config: HandoffConfig; private activeHandoffs: Map = new Map(); private completedHandoffs: Map = new Map(); private cleanupTimer?: NodeJS.Timeout; private handoffCounter: number = 0; constructor(config: Partial = {}) { this.config = { ...DEFAULT_CONFIG, ...config }; this.startCleanupTimer(); } // ============================================================================ // Handoff Creation // ============================================================================ /** * Create a new context handoff */ createHandoff(options: { sourceAgent: string; targetAgent: string; request: any; files?: Record; conversationHistory?: ConversationTurn[]; workspace?: string; metadata?: Record; timeLimit?: number; scope?: 'narrow' | 'medium' | 'broad'; qualityLevel?: 'fast' | 'balanced' | 'thorough'; callbackEndpoint?: string; callbackTimeout?: number; }): ContextHandoff { const id = this.generateHandoffId(); const handoff: ContextHandoff = { id, sourceAgent: options.sourceAgent, targetAgent: options.targetAgent, request: options.request, context: { files: options.files || {}, conversationHistory: options.conversationHistory || [], workspace: options.workspace || '', metadata: options.metadata || {} }, constraints: { timeLimit: options.timeLimit || this.config.defaultTimeLimit, scope: options.scope || 'medium', qualityLevel: options.qualityLevel || 'balanced' }, callback: { endpoint: options.callbackEndpoint || '', timeout: options.callbackTimeout || this.config.defaultTimeout, retries: 3 }, createdAt: new Date(), expiresAt: new Date(Date.now() + this.config.handoffTTL) }; this.activeHandoffs.set(id, handoff); this.handoffCounter++; return handoff; } /** * Create handoff from delegation context */ createFromDelegation( delegationContext: DelegationContext, sourceAgent: string, targetAgent: string ): ContextHandoff { return this.createHandoff({ sourceAgent, targetAgent, request: delegationContext.originalRequest, metadata: { requestId: delegationContext.requestId, classification: delegationContext.classification, strategy: delegationContext.delegationDecision.strategy } }); } // ============================================================================ // Handoff Execution // ============================================================================ /** * Get a handoff by ID */ getHandoff(id: string): ContextHandoff | undefined { return this.activeHandoffs.get(id); } /** * Complete a handoff with result */ completeHandoff( handoffId: string, result: { success: boolean; output?: any; error?: string; processingTime: number; } ): HandoffResult { const handoff = this.activeHandoffs.get(handoffId); const handoffResult: HandoffResult = { handoffId, success: result.success, result: result.output, error: result.error, processingTime: result.processingTime }; // Store result this.completedHandoffs.set(handoffId, handoffResult); // Remove from active if (handoff) { this.activeHandoffs.delete(handoffId); // Execute callback if configured if (handoff.callback.endpoint) { this.executeCallback(handoff, handoffResult); } } return handoffResult; } /** * Cancel a handoff */ cancelHandoff(handoffId: string, reason: string): void { const handoff = this.activeHandoffs.get(handoffId); if (handoff) { this.completeHandoff(handoffId, { success: false, error: `Cancelled: ${reason}`, processingTime: 0 }); } } // ============================================================================ // Callback Execution // ============================================================================ private async executeCallback( handoff: ContextHandoff, result: HandoffResult ): Promise { const { endpoint, timeout, retries } = handoff.callback; for (let attempt = 0; attempt < retries; attempt++) { try { const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ handoffId: handoff.id, sourceAgent: handoff.sourceAgent, targetAgent: handoff.targetAgent, result }), signal: AbortSignal.timeout(timeout) }); if (response.ok) { return; } } catch (error) { console.error(`Callback attempt ${attempt + 1} failed:`, error); // Wait before retry if (attempt < retries - 1) { await this.sleep(1000 * (attempt + 1)); } } } } private sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } // ============================================================================ // Context Serialization // ============================================================================ /** * Serialize handoff for transmission */ serialize(handoff: ContextHandoff): string { return JSON.stringify({ id: handoff.id, sourceAgent: handoff.sourceAgent, targetAgent: handoff.targetAgent, request: handoff.request, context: handoff.context, constraints: handoff.constraints, callback: handoff.callback, createdAt: handoff.createdAt.toISOString(), expiresAt: handoff.expiresAt.toISOString() }); } /** * Deserialize handoff from transmission */ deserialize(data: string): ContextHandoff { const parsed = JSON.parse(data); return { ...parsed, createdAt: new Date(parsed.createdAt), expiresAt: new Date(parsed.expiresAt) }; } /** * Validate handoff integrity */ validate(handoff: ContextHandoff): { valid: boolean; errors: string[] } { const errors: string[] = []; if (!handoff.id) errors.push('Missing handoff ID'); if (!handoff.sourceAgent) errors.push('Missing source agent'); if (!handoff.targetAgent) errors.push('Missing target agent'); if (!handoff.request) errors.push('Missing request'); if (handoff.constraints.timeLimit <= 0) { errors.push('Invalid time limit'); } if (handoff.expiresAt <= new Date()) { errors.push('Handoff has expired'); } return { valid: errors.length === 0, errors }; } // ============================================================================ // Cleanup // ============================================================================ private startCleanupTimer(): void { this.cleanupTimer = setInterval(() => { this.cleanup(); }, this.config.cleanupInterval); } private cleanup(): void { const now = new Date(); // Remove expired handoffs for (const [id, handoff] of this.activeHandoffs) { if (handoff.expiresAt <= now) { this.cancelHandoff(id, 'Expired'); } } // Limit completed handoffs if (this.completedHandoffs.size > this.config.maxHandoffs) { const keys = Array.from(this.completedHandoffs.keys()); const toRemove = keys.slice(0, this.completedHandoffs.size - this.config.maxHandoffs); toRemove.forEach(key => this.completedHandoffs.delete(key)); } } /** * Stop cleanup timer and clear all */ shutdown(): void { if (this.cleanupTimer) { clearInterval(this.cleanupTimer); } this.activeHandoffs.clear(); this.completedHandoffs.clear(); } // ============================================================================ // Statistics // ============================================================================ getStats(): { active: number; completed: number; totalCreated: number; successRate: number; averageProcessingTime: number; } { const completed = Array.from(this.completedHandoffs.values()); const successful = completed.filter(r => r.success); return { active: this.activeHandoffs.size, completed: completed.length, totalCreated: this.handoffCounter, successRate: completed.length > 0 ? successful.length / completed.length : 0, averageProcessingTime: completed.length > 0 ? completed.reduce((sum, r) => sum + r.processingTime, 0) / completed.length : 0 }; } private generateHandoffId(): string { return `handoff-${Date.now()}-${Math.random().toString(36).substr(2, 6)}`; } } // ============================================================================ // Factory Function // ============================================================================ export function createContextHandoffManager(config?: Partial): ContextHandoffManager { return new ContextHandoffManager(config); } // ============================================================================ // Export // ============================================================================ export default ContextHandoffManager;