UNPKG

jay-code

Version:

Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability

710 lines (621 loc) 16.8 kB
/** * Memory persistence hooks for agentic-flow * * Provides cross-provider memory state management with * synchronization and persistence capabilities. */ import { agenticHookManager } from './hook-manager.js'; import type { AgenticHookContext, HookHandlerResult, MemoryHookPayload, SideEffect, } from './types.js'; // ===== Pre-Memory Store Hook ===== export const preMemoryStoreHook = { id: 'agentic-pre-memory-store', type: 'pre-memory-store' as const, priority: 100, handler: async ( payload: MemoryHookPayload, context: AgenticHookContext ): Promise<HookHandlerResult> => { const { namespace, key, value, ttl, provider } = payload; const sideEffects: SideEffect[] = []; // Validate memory constraints const validation = await validateMemoryStore(namespace, key, value, context); if (!validation.valid) { return { continue: false, sideEffects: [ { type: 'log', action: 'write', data: { level: 'error', message: 'Memory store validation failed', data: validation, }, }, ], }; } // Compress large values let processedValue = value; if (shouldCompress(value)) { processedValue = await compressValue(value); sideEffects.push({ type: 'metric', action: 'increment', data: { name: 'memory.compressions' }, }); } // Add metadata const enrichedValue = { data: processedValue, metadata: { stored: Date.now(), provider, sessionId: context.sessionId, compressed: processedValue !== value, size: getValueSize(processedValue), }, }; // Track memory usage sideEffects.push({ type: 'metric', action: 'update', data: { name: `memory.usage.${namespace}`, value: getValueSize(enrichedValue), }, }); return { continue: true, modified: true, payload: { ...payload, value: enrichedValue, }, sideEffects, }; }, }; // ===== Post-Memory Store Hook ===== export const postMemoryStoreHook = { id: 'agentic-post-memory-store', type: 'post-memory-store' as const, priority: 100, handler: async ( payload: MemoryHookPayload, context: AgenticHookContext ): Promise<HookHandlerResult> => { const { namespace, key, value, crossProvider, syncTargets } = payload; const sideEffects: SideEffect[] = []; // Cross-provider sync if enabled if (crossProvider && syncTargets && syncTargets.length > 0) { for (const target of syncTargets) { sideEffects.push({ type: 'memory', action: 'sync', data: { source: payload.provider, target, namespace, key, value, }, }); } } // Update memory index for search await updateMemoryIndex(namespace, key, value, context); // Neural pattern detection const patterns = await detectMemoryPatterns(namespace, key, value, context); if (patterns.length > 0) { sideEffects.push({ type: 'neural', action: 'analyze', data: { patterns, context: { namespace, key }, }, }); } // Emit memory change event sideEffects.push({ type: 'notification', action: 'emit', data: { event: 'memory:stored', data: { namespace, key, size: getValueSize(value) }, }, }); return { continue: true, sideEffects, }; }, }; // ===== Pre-Memory Retrieve Hook ===== export const preMemoryRetrieveHook = { id: 'agentic-pre-memory-retrieve', type: 'pre-memory-retrieve' as const, priority: 100, handler: async ( payload: MemoryHookPayload, context: AgenticHookContext ): Promise<HookHandlerResult> => { const { namespace, key } = payload; // Check local cache first const cached = await checkLocalCache(namespace, key!, context); if (cached) { return { continue: false, modified: true, payload: { ...payload, value: cached, }, sideEffects: [ { type: 'metric', action: 'increment', data: { name: 'memory.cache.hits' }, }, ], }; } // Pre-fetch related keys const relatedKeys = await findRelatedKeys(namespace, key!, context); if (relatedKeys.length > 0) { // Trigger background fetch prefetchKeys(namespace, relatedKeys, context); } return { continue: true, sideEffects: [ { type: 'metric', action: 'increment', data: { name: `memory.retrievals.${namespace}` }, }, ], }; }, }; // ===== Post-Memory Retrieve Hook ===== export const postMemoryRetrieveHook = { id: 'agentic-post-memory-retrieve', type: 'post-memory-retrieve' as const, priority: 100, handler: async ( payload: MemoryHookPayload, context: AgenticHookContext ): Promise<HookHandlerResult> => { const { namespace, key, value } = payload; if (!value) { return { continue: true }; } const sideEffects: SideEffect[] = []; // Decompress if needed let processedValue = value; if (value.metadata?.compressed) { processedValue = await decompressValue(value.data); sideEffects.push({ type: 'metric', action: 'increment', data: { name: 'memory.decompressions' }, }); } // Update access patterns await updateAccessPattern(namespace, key!, context); // Cache locally for fast access await cacheLocally(namespace, key!, processedValue, context); // Track retrieval latency const latency = Date.now() - context.timestamp; sideEffects.push({ type: 'metric', action: 'update', data: { name: `memory.latency.${namespace}`, value: latency, }, }); return { continue: true, modified: true, payload: { ...payload, value: processedValue.data || processedValue, }, sideEffects, }; }, }; // ===== Memory Sync Hook ===== export const memorySyncHook = { id: 'agentic-memory-sync', type: 'memory-sync' as const, priority: 100, handler: async ( payload: MemoryHookPayload, context: AgenticHookContext ): Promise<HookHandlerResult> => { const { operation, namespace, provider, syncTargets } = payload; const sideEffects: SideEffect[] = []; switch (operation) { case 'sync': // Bidirectional sync const changes = await detectMemoryChanges(namespace, provider, context); if (changes.length > 0) { sideEffects.push({ type: 'log', action: 'write', data: { level: 'info', message: `Syncing ${changes.length} memory changes`, data: { namespace, provider, targets: syncTargets }, }, }); // Apply changes for (const change of changes) { await applyMemoryChange(change, syncTargets || [], context); } sideEffects.push({ type: 'metric', action: 'update', data: { name: 'memory.sync.changes', value: changes.length, }, }); } break; case 'persist': // Persist to long-term storage const snapshot = await createMemorySnapshot(namespace, context); sideEffects.push({ type: 'memory', action: 'store', data: { key: `snapshot:${namespace}:${Date.now()}`, value: snapshot, ttl: 0, // No expiration }, }); sideEffects.push({ type: 'notification', action: 'emit', data: { event: 'memory:persisted', data: { namespace, size: snapshot.size }, }, }); break; case 'expire': // Clean up expired entries const expired = await findExpiredEntries(namespace, context); if (expired.length > 0) { for (const key of expired) { await removeMemoryEntry(namespace, key, context); } sideEffects.push({ type: 'metric', action: 'update', data: { name: 'memory.expired', value: expired.length, }, }); } break; } return { continue: true, sideEffects, }; }, }; // ===== Memory Persist Hook ===== export const memoryPersistHook = { id: 'agentic-memory-persist', type: 'memory-persist' as const, priority: 90, handler: async ( payload: MemoryHookPayload, context: AgenticHookContext ): Promise<HookHandlerResult> => { const { namespace } = payload; // Create full memory backup const backup = await createFullBackup(namespace, context); // Store backup with metadata const backupData = { timestamp: Date.now(), sessionId: context.sessionId, namespace, entries: backup.entries, size: backup.size, checksum: calculateChecksum(backup), }; return { continue: true, sideEffects: [ { type: 'memory', action: 'store', data: { key: `backup:${namespace}:${context.sessionId}`, value: backupData, ttl: 604800, // 7 days }, }, { type: 'notification', action: 'emit', data: { event: 'memory:backup:created', data: { namespace, size: backup.size, entries: backup.entries.length, }, }, }, ], }; }, }; // ===== Helper Functions ===== async function validateMemoryStore( namespace: string, key: string | undefined, value: any, context: AgenticHookContext ): Promise<{ valid: boolean; reason?: string }> { // Check size limits const size = getValueSize(value); const maxSize = 10 * 1024 * 1024; // 10MB if (size > maxSize) { return { valid: false, reason: `Value size ${size} exceeds limit ${maxSize}`, }; } // Check namespace quota const quota = await getNamespaceQuota(namespace, context); const usage = await getNamespaceUsage(namespace, context); if (usage + size > quota) { return { valid: false, reason: `Namespace quota exceeded: ${usage + size} > ${quota}`, }; } // Validate key format if (key && !isValidKey(key)) { return { valid: false, reason: `Invalid key format: ${key}`, }; } return { valid: true }; } function shouldCompress(value: any): boolean { const size = getValueSize(value); return size > 1024; // Compress if larger than 1KB } async function compressValue(value: any): Promise<any> { // Implement compression (placeholder) // In real implementation, use zlib or similar return { compressed: true, data: JSON.stringify(value), }; } async function decompressValue(value: any): Promise<any> { // Implement decompression (placeholder) if (value.compressed) { return JSON.parse(value.data); } return value; } function getValueSize(value: any): number { return Buffer.byteLength(JSON.stringify(value), 'utf8'); } async function updateMemoryIndex( namespace: string, key: string, value: any, context: AgenticHookContext ): Promise<void> { // Update search index (placeholder) // In real implementation, update inverted index for search } async function detectMemoryPatterns( namespace: string, key: string, value: any, context: AgenticHookContext ): Promise<any[]> { // Detect patterns in memory usage const patterns = []; // Check for sequential access pattern const accessHistory = await getAccessHistory(namespace, context); if (isSequentialPattern(accessHistory)) { patterns.push({ type: 'sequential', confidence: 0.8, suggestion: 'prefetch-next', }); } // Check for temporal patterns if (isTemporalPattern(accessHistory)) { patterns.push({ type: 'temporal', confidence: 0.7, suggestion: 'cache-duration', }); } return patterns; } async function checkLocalCache( namespace: string, key: string, context: AgenticHookContext ): Promise<any | null> { const cacheKey = `${namespace}:${key}`; return context.memory.cache.get(cacheKey); } async function findRelatedKeys( namespace: string, key: string, context: AgenticHookContext ): Promise<string[]> { // Find related keys based on patterns // Placeholder implementation return []; } async function prefetchKeys( namespace: string, keys: string[], context: AgenticHookContext ): Promise<void> { // Trigger background prefetch // Placeholder implementation } async function updateAccessPattern( namespace: string, key: string, context: AgenticHookContext ): Promise<void> { // Track access patterns for optimization const patternKey = `pattern:${namespace}:${key}`; const pattern = await context.memory.cache.get(patternKey) || { accesses: [], lastAccess: 0, }; pattern.accesses.push(Date.now()); pattern.lastAccess = Date.now(); // Keep last 100 accesses if (pattern.accesses.length > 100) { pattern.accesses = pattern.accesses.slice(-100); } await context.memory.cache.set(patternKey, pattern); } async function cacheLocally( namespace: string, key: string, value: any, context: AgenticHookContext ): Promise<void> { const cacheKey = `${namespace}:${key}`; context.memory.cache.set(cacheKey, value); } async function detectMemoryChanges( namespace: string, provider: string, context: AgenticHookContext ): Promise<any[]> { // Detect changes for sync // Placeholder implementation return []; } async function applyMemoryChange( change: any, targets: string[], context: AgenticHookContext ): Promise<void> { // Apply memory change to targets // Placeholder implementation } async function createMemorySnapshot( namespace: string, context: AgenticHookContext ): Promise<any> { // Create snapshot of namespace // Placeholder implementation return { namespace, timestamp: Date.now(), entries: [], size: 0, }; } async function findExpiredEntries( namespace: string, context: AgenticHookContext ): Promise<string[]> { // Find expired entries // Placeholder implementation return []; } async function removeMemoryEntry( namespace: string, key: string, context: AgenticHookContext ): Promise<void> { // Remove memory entry // Placeholder implementation } async function createFullBackup( namespace: string, context: AgenticHookContext ): Promise<any> { // Create full backup // Placeholder implementation return { entries: [], size: 0, }; } function calculateChecksum(data: any): string { // Calculate checksum // Placeholder implementation return 'checksum'; } async function getNamespaceQuota( namespace: string, context: AgenticHookContext ): Promise<number> { // Get namespace quota return 100 * 1024 * 1024; // 100MB default } async function getNamespaceUsage( namespace: string, context: AgenticHookContext ): Promise<number> { // Get current usage // Placeholder implementation return 0; } function isValidKey(key: string): boolean { // Validate key format return /^[a-zA-Z0-9:_\-./]+$/.test(key); } async function getAccessHistory( namespace: string, context: AgenticHookContext ): Promise<any[]> { // Get access history // Placeholder implementation return []; } function isSequentialPattern(history: any[]): boolean { // Check for sequential access // Placeholder implementation return false; } function isTemporalPattern(history: any[]): boolean { // Check for temporal patterns // Placeholder implementation return false; } // ===== Register Hooks ===== export function registerMemoryHooks(): void { agenticHookManager.register(preMemoryStoreHook); agenticHookManager.register(postMemoryStoreHook); agenticHookManager.register(preMemoryRetrieveHook); agenticHookManager.register(postMemoryRetrieveHook); agenticHookManager.register(memorySyncHook); agenticHookManager.register(memoryPersistHook); }