jay-code
Version:
Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability
710 lines (621 loc) • 16.8 kB
text/typescript
/**
* 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);
}