@soulcraft/brainy 3.34.0 → 3.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Memory Detection Utilities
3
+ * Detects available system memory across different environments:
4
+ * - Docker/Kubernetes (cgroups v1 and v2)
5
+ * - Bare metal servers
6
+ * - Cloud instances
7
+ * - Development environments
8
+ *
9
+ * Scales from 2GB to 128GB+ with intelligent allocation
10
+ */
11
+ import * as os from 'os';
12
+ import * as fs from 'fs';
13
+ import { prodLog } from './logger.js';
14
+ /**
15
+ * Detect available memory across all environments
16
+ */
17
+ export function detectAvailableMemory() {
18
+ const warnings = [];
19
+ // Try cgroups v2 first (modern Docker/K8s)
20
+ const cgroupV2 = detectCgroupV2Memory();
21
+ if (cgroupV2 !== null) {
22
+ const systemTotal = os.totalmem();
23
+ const free = os.freemem();
24
+ return {
25
+ available: cgroupV2,
26
+ source: 'cgroup-v2',
27
+ isContainer: true,
28
+ systemTotal,
29
+ free,
30
+ warnings: cgroupV2 < systemTotal
31
+ ? [`Container limited to ${formatBytes(cgroupV2)} (host has ${formatBytes(systemTotal)})`]
32
+ : []
33
+ };
34
+ }
35
+ // Try cgroups v1 (older Docker/K8s)
36
+ const cgroupV1 = detectCgroupV1Memory();
37
+ if (cgroupV1 !== null) {
38
+ const systemTotal = os.totalmem();
39
+ const free = os.freemem();
40
+ return {
41
+ available: cgroupV1,
42
+ source: 'cgroup-v1',
43
+ isContainer: true,
44
+ systemTotal,
45
+ free,
46
+ warnings: cgroupV1 < systemTotal
47
+ ? [`Container limited to ${formatBytes(cgroupV1)} (host has ${formatBytes(systemTotal)})`]
48
+ : []
49
+ };
50
+ }
51
+ // Use system memory (bare metal, VM, or unlimited container)
52
+ const systemTotal = os.totalmem();
53
+ const free = os.freemem();
54
+ // Check if we might be in an unlimited container
55
+ if (process.env.KUBERNETES_SERVICE_HOST || process.env.DOCKER_CONTAINER) {
56
+ warnings.push('Container detected but no memory limit set - using host memory');
57
+ }
58
+ return {
59
+ available: systemTotal,
60
+ source: 'system',
61
+ isContainer: false,
62
+ systemTotal,
63
+ free,
64
+ warnings
65
+ };
66
+ }
67
+ /**
68
+ * Detect memory limit from cgroups v2 (modern containers)
69
+ * Path: /sys/fs/cgroup/memory.max
70
+ */
71
+ function detectCgroupV2Memory() {
72
+ try {
73
+ const memoryMaxPath = '/sys/fs/cgroup/memory.max';
74
+ if (!fs.existsSync(memoryMaxPath)) {
75
+ return null;
76
+ }
77
+ const content = fs.readFileSync(memoryMaxPath, 'utf8').trim();
78
+ // 'max' means unlimited
79
+ if (content === 'max') {
80
+ return null;
81
+ }
82
+ const bytes = parseInt(content, 10);
83
+ // Sanity check: Must be reasonable number (between 64MB and 1TB)
84
+ if (bytes < 64 * 1024 * 1024 || bytes > 1024 * 1024 * 1024 * 1024) {
85
+ prodLog.warn(`Suspicious cgroup v2 memory limit: ${formatBytes(bytes)}`);
86
+ return null;
87
+ }
88
+ return bytes;
89
+ }
90
+ catch (error) {
91
+ // Not in a cgroup v2 environment
92
+ return null;
93
+ }
94
+ }
95
+ /**
96
+ * Detect memory limit from cgroups v1 (older containers)
97
+ * Path: /sys/fs/cgroup/memory/memory.limit_in_bytes
98
+ */
99
+ function detectCgroupV1Memory() {
100
+ try {
101
+ const limitPath = '/sys/fs/cgroup/memory/memory.limit_in_bytes';
102
+ if (!fs.existsSync(limitPath)) {
103
+ return null;
104
+ }
105
+ const content = fs.readFileSync(limitPath, 'utf8').trim();
106
+ const bytes = parseInt(content, 10);
107
+ // cgroup v1 uses very large number (2^63-1) to indicate unlimited
108
+ // If limit is > 1TB, consider it unlimited
109
+ if (bytes > 1024 * 1024 * 1024 * 1024) {
110
+ return null;
111
+ }
112
+ // Sanity check: Must be reasonable number (between 64MB and 1TB)
113
+ if (bytes < 64 * 1024 * 1024) {
114
+ prodLog.warn(`Suspicious cgroup v1 memory limit: ${formatBytes(bytes)}`);
115
+ return null;
116
+ }
117
+ return bytes;
118
+ }
119
+ catch (error) {
120
+ // Not in a cgroup v1 environment
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Calculate optimal cache size based on available memory
126
+ * Scales intelligently from 2GB to 128GB+
127
+ *
128
+ * v3.36.0+: Accounts for embedding model memory (150MB Q8, 250MB FP32)
129
+ */
130
+ export function calculateOptimalCacheSize(memoryInfo, options = {}) {
131
+ const minSize = options.minSize || 256 * 1024 * 1024; // 256MB minimum
132
+ const maxSize = options.maxSize || null;
133
+ // Detect model memory usage (v3.36.0+)
134
+ const modelInfo = detectModelMemory({ precision: options.modelPrecision || 'q8' });
135
+ const modelMemory = modelInfo.bytes;
136
+ // Reserve model memory from available RAM BEFORE calculating cache
137
+ // This ensures we don't over-allocate and cause OOM
138
+ const availableForCache = Math.max(0, memoryInfo.available - modelMemory);
139
+ // Manual override takes precedence
140
+ if (options.manualSize !== undefined) {
141
+ const clamped = Math.max(minSize, options.manualSize);
142
+ return {
143
+ cacheSize: clamped,
144
+ ratio: clamped / availableForCache,
145
+ minSize,
146
+ maxSize,
147
+ environment: 'unknown',
148
+ modelMemory,
149
+ modelPrecision: modelInfo.precision,
150
+ availableForCache,
151
+ reasoning: 'Manual override specified'
152
+ };
153
+ }
154
+ // Determine environment and allocation ratio
155
+ let ratio;
156
+ let environment;
157
+ let reasoning;
158
+ if (options.developmentMode || process.env.NODE_ENV === 'development') {
159
+ // Development: More conservative (25%)
160
+ ratio = 0.25;
161
+ environment = 'development';
162
+ reasoning = `Development mode - conservative allocation (25% of ${formatBytes(availableForCache)} after ${formatBytes(modelMemory)} model)`;
163
+ }
164
+ else if (memoryInfo.isContainer) {
165
+ // Container: Moderate allocation (40%)
166
+ // Containers often have tight limits, leave room for heap growth
167
+ ratio = 0.40;
168
+ environment = 'container';
169
+ reasoning = `Container environment - moderate allocation (40% of ${formatBytes(availableForCache)} after ${formatBytes(modelMemory)} model)`;
170
+ }
171
+ else {
172
+ // Production bare metal/VM: Aggressive allocation (50%)
173
+ // More memory available, can be more aggressive
174
+ ratio = 0.50;
175
+ environment = 'production';
176
+ reasoning = `Production environment - aggressive allocation (50% of ${formatBytes(availableForCache)} after ${formatBytes(modelMemory)} model)`;
177
+ }
178
+ // Calculate base cache size from AVAILABLE memory (after model reservation)
179
+ let cacheSize = Math.floor(availableForCache * ratio);
180
+ // Apply minimum constraint
181
+ if (cacheSize < minSize) {
182
+ const originalSize = cacheSize;
183
+ cacheSize = minSize;
184
+ reasoning += ` (increased from ${formatBytes(originalSize)} to meet minimum)`;
185
+ // Warn if available memory is very low
186
+ if (availableForCache < minSize * 2) {
187
+ prodLog.warn(`⚠️ Low available memory for cache (${formatBytes(availableForCache)} after ${formatBytes(modelMemory)} model). ` +
188
+ `Cache size ${formatBytes(cacheSize)} may cause memory pressure.`);
189
+ }
190
+ }
191
+ // Apply maximum constraint
192
+ if (maxSize !== null && cacheSize > maxSize) {
193
+ const originalSize = cacheSize;
194
+ cacheSize = maxSize;
195
+ reasoning += ` (capped from ${formatBytes(originalSize)} to maximum)`;
196
+ }
197
+ // Intelligent scaling for large memory systems
198
+ // For systems with >64GB available for cache, use logarithmic scaling to avoid over-allocation
199
+ if (availableForCache > 64 * 1024 * 1024 * 1024) {
200
+ // Above 64GB, scale more conservatively
201
+ // Formula: base + log2(availableForCache/64GB) * 8GB
202
+ const base = 32 * 1024 * 1024 * 1024; // 32GB base
203
+ const scaleFactor = Math.log2(availableForCache / (64 * 1024 * 1024 * 1024));
204
+ const scaled = base + scaleFactor * 8 * 1024 * 1024 * 1024; // +8GB per doubling
205
+ if (scaled < cacheSize) {
206
+ const originalSize = cacheSize;
207
+ cacheSize = Math.floor(scaled);
208
+ reasoning += ` (scaled down from ${formatBytes(originalSize)} for large memory system)`;
209
+ }
210
+ }
211
+ return {
212
+ cacheSize,
213
+ ratio,
214
+ minSize,
215
+ maxSize,
216
+ environment,
217
+ modelMemory,
218
+ modelPrecision: modelInfo.precision,
219
+ availableForCache,
220
+ reasoning
221
+ };
222
+ }
223
+ /**
224
+ * Get recommended cache configuration for current environment
225
+ */
226
+ export function getRecommendedCacheConfig(options = {}) {
227
+ const memoryInfo = detectAvailableMemory();
228
+ const allocation = calculateOptimalCacheSize(memoryInfo, options);
229
+ const warnings = [...memoryInfo.warnings];
230
+ // Add allocation warnings
231
+ if (allocation.cacheSize === allocation.minSize) {
232
+ warnings.push(`Cache size at minimum (${formatBytes(allocation.minSize)}). ` +
233
+ `Consider increasing available memory for better performance.`);
234
+ }
235
+ if (allocation.ratio > 0.6) {
236
+ warnings.push(`Cache using ${(allocation.ratio * 100).toFixed(0)}% of available memory. ` +
237
+ `Monitor for memory pressure.`);
238
+ }
239
+ return {
240
+ memoryInfo,
241
+ allocation,
242
+ warnings
243
+ };
244
+ }
245
+ /**
246
+ * Detect embedding model memory usage
247
+ *
248
+ * Returns estimated runtime memory for the embedding model:
249
+ * - Q8 (quantized, default): ~150MB runtime (22MB on disk)
250
+ * - FP32 (full precision): ~250MB runtime (86MB on disk)
251
+ *
252
+ * Breakdown for Q8:
253
+ * - Model weights: 22MB
254
+ * - ONNX Runtime: 15-30MB
255
+ * - Session workspace: 50-100MB (peak during inference)
256
+ * - Total: ~100-150MB (we use 150MB conservative)
257
+ */
258
+ export function detectModelMemory(options = {}) {
259
+ const precision = options.precision || 'q8';
260
+ if (precision === 'q8') {
261
+ // Q8 quantized model (default)
262
+ return {
263
+ bytes: 150 * 1024 * 1024, // 150MB
264
+ precision: 'q8',
265
+ breakdown: {
266
+ modelWeights: 22 * 1024 * 1024, // 22MB
267
+ onnxRuntime: 30 * 1024 * 1024, // 30MB (conservative)
268
+ sessionWorkspace: 98 * 1024 * 1024 // 98MB (peak during inference)
269
+ }
270
+ };
271
+ }
272
+ else {
273
+ // FP32 full precision model
274
+ return {
275
+ bytes: 250 * 1024 * 1024, // 250MB
276
+ precision: 'fp32',
277
+ breakdown: {
278
+ modelWeights: 86 * 1024 * 1024, // 86MB
279
+ onnxRuntime: 30 * 1024 * 1024, // 30MB
280
+ sessionWorkspace: 134 * 1024 * 1024 // 134MB (peak during inference)
281
+ }
282
+ };
283
+ }
284
+ }
285
+ /**
286
+ * Format bytes to human-readable string
287
+ */
288
+ export function formatBytes(bytes) {
289
+ if (bytes === 0)
290
+ return '0 B';
291
+ const k = 1024;
292
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
293
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
294
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
295
+ }
296
+ /**
297
+ * Monitor memory usage and warn if approaching limits
298
+ */
299
+ export function checkMemoryPressure(cacheSize, memoryInfo) {
300
+ const warnings = [];
301
+ const heapUsed = process.memoryUsage().heapUsed;
302
+ const totalUsed = heapUsed + cacheSize;
303
+ const utilization = totalUsed / memoryInfo.available;
304
+ if (utilization > 0.95) {
305
+ warnings.push(`🔴 CRITICAL: Memory utilization at ${(utilization * 100).toFixed(1)}%. ` +
306
+ `Reduce cache size or increase available memory.`);
307
+ return { pressure: 'critical', warnings };
308
+ }
309
+ if (utilization > 0.85) {
310
+ warnings.push(`🟠 HIGH: Memory utilization at ${(utilization * 100).toFixed(1)}%. ` +
311
+ `Consider increasing available memory.`);
312
+ return { pressure: 'high', warnings };
313
+ }
314
+ if (utilization > 0.70) {
315
+ warnings.push(`🟡 MODERATE: Memory utilization at ${(utilization * 100).toFixed(1)}%. ` +
316
+ `Monitor for memory pressure.`);
317
+ return { pressure: 'moderate', warnings };
318
+ }
319
+ return { pressure: 'none', warnings: [] };
320
+ }
321
+ //# sourceMappingURL=memoryDetection.js.map
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * UnifiedCache - Single cache for both HNSW and MetadataIndex
3
3
  * Prevents resource competition with cost-aware eviction
4
+ *
5
+ * Features (v3.36.0+):
6
+ * - Adaptive sizing: Automatically scales from 2GB to 128GB+ based on available memory
7
+ * - Container-aware: Detects Docker/K8s limits (cgroups v1/v2)
8
+ * - Environment detection: Production vs development allocation strategies
9
+ * - Memory pressure monitoring: Warns when approaching limits
4
10
  */
5
11
  export interface CacheItem {
6
12
  key: string;
@@ -12,11 +18,24 @@ export interface CacheItem {
12
18
  accessCount: number;
13
19
  }
14
20
  export interface UnifiedCacheConfig {
21
+ /** Maximum cache size in bytes (auto-detected if not specified) */
15
22
  maxSize?: number;
23
+ /** Minimum cache size in bytes (default 256MB) */
24
+ minSize?: number;
25
+ /** Force development mode allocation (25% instead of 40-50%) */
26
+ developmentMode?: boolean;
27
+ /** Enable request coalescing to prevent duplicate loads */
16
28
  enableRequestCoalescing?: boolean;
29
+ /** Enable fairness monitoring to prevent cache starvation */
17
30
  enableFairnessCheck?: boolean;
31
+ /** Fairness check interval in milliseconds */
18
32
  fairnessCheckInterval?: number;
33
+ /** Enable access pattern persistence for warm starts */
19
34
  persistPatterns?: boolean;
35
+ /** Enable memory pressure monitoring (default true) */
36
+ enableMemoryMonitoring?: boolean;
37
+ /** Memory pressure check interval in milliseconds (default 30s) */
38
+ memoryCheckInterval?: number;
20
39
  }
21
40
  export declare class UnifiedCache {
22
41
  private cache;
@@ -27,11 +46,21 @@ export declare class UnifiedCache {
27
46
  private currentSize;
28
47
  private readonly maxSize;
29
48
  private readonly config;
49
+ private readonly memoryInfo;
50
+ private readonly allocationStrategy;
51
+ private memoryPressureCheckTimer;
52
+ private lastMemoryWarning;
30
53
  constructor(config?: UnifiedCacheConfig);
31
54
  /**
32
55
  * Get item from cache with request coalescing
33
56
  */
34
57
  get(key: string, loadFn?: () => Promise<any>): Promise<any>;
58
+ /**
59
+ * Synchronous cache lookup (v3.36.0+)
60
+ * Returns cached data immediately or undefined if not cached
61
+ * Use for sync fast path optimization - zero async overhead
62
+ */
63
+ getSync(key: string): any | undefined;
35
64
  /**
36
65
  * Set item in cache with cost-aware eviction
37
66
  */
@@ -62,7 +91,16 @@ export declare class UnifiedCache {
62
91
  */
63
92
  clear(type?: 'hnsw' | 'metadata' | 'embedding' | 'other'): void;
64
93
  /**
65
- * Get cache statistics
94
+ * Start memory pressure monitoring
95
+ * Periodically checks if we're approaching memory limits
96
+ */
97
+ private startMemoryPressureMonitor;
98
+ /**
99
+ * Check current memory pressure and warn if needed
100
+ */
101
+ private checkMemoryPressure;
102
+ /**
103
+ * Get cache statistics with memory information
66
104
  */
67
105
  getStats(): {
68
106
  totalSize: number;
@@ -89,6 +127,42 @@ export declare class UnifiedCache {
89
127
  };
90
128
  totalAccessCount: number;
91
129
  hitRate: number;
130
+ memory: {
131
+ available: number;
132
+ source: "cgroup-v2" | "cgroup-v1" | "system" | "fallback";
133
+ isContainer: boolean;
134
+ systemTotal: number;
135
+ allocationRatio: number;
136
+ environment: "production" | "development" | "container" | "unknown";
137
+ };
138
+ };
139
+ /**
140
+ * Get detailed memory information
141
+ */
142
+ getMemoryInfo(): {
143
+ memoryInfo: {
144
+ available: number;
145
+ source: "cgroup-v2" | "cgroup-v1" | "system" | "fallback";
146
+ isContainer: boolean;
147
+ systemTotal: number;
148
+ free: number;
149
+ warnings: string[];
150
+ };
151
+ allocationStrategy: {
152
+ cacheSize: number;
153
+ ratio: number;
154
+ minSize: number;
155
+ maxSize: number | null;
156
+ environment: "production" | "development" | "container" | "unknown";
157
+ modelMemory: number;
158
+ modelPrecision: "q8" | "fp32";
159
+ availableForCache: number;
160
+ reasoning: string;
161
+ };
162
+ currentPressure: {
163
+ pressure: "none" | "moderate" | "high" | "critical";
164
+ warnings: string[];
165
+ };
92
166
  };
93
167
  /**
94
168
  * Save access patterns for cold start optimization
@@ -1,8 +1,15 @@
1
1
  /**
2
2
  * UnifiedCache - Single cache for both HNSW and MetadataIndex
3
3
  * Prevents resource competition with cost-aware eviction
4
+ *
5
+ * Features (v3.36.0+):
6
+ * - Adaptive sizing: Automatically scales from 2GB to 128GB+ based on available memory
7
+ * - Container-aware: Detects Docker/K8s limits (cgroups v1/v2)
8
+ * - Environment detection: Production vs development allocation strategies
9
+ * - Memory pressure monitoring: Warns when approaching limits
4
10
  */
5
11
  import { prodLog } from './logger.js';
12
+ import { getRecommendedCacheConfig, formatBytes, checkMemoryPressure } from './memoryDetection.js';
6
13
  export class UnifiedCache {
7
14
  constructor(config = {}) {
8
15
  this.cache = new Map();
@@ -11,17 +18,51 @@ export class UnifiedCache {
11
18
  this.typeAccessCounts = { hnsw: 0, metadata: 0, embedding: 0, other: 0 };
12
19
  this.totalAccessCount = 0;
13
20
  this.currentSize = 0;
14
- this.maxSize = config.maxSize || 2 * 1024 * 1024 * 1024; // 2GB default
21
+ this.memoryPressureCheckTimer = null;
22
+ this.lastMemoryWarning = 0;
23
+ // Adaptive cache sizing (v3.36.0+)
24
+ const recommendation = getRecommendedCacheConfig({
25
+ manualSize: config.maxSize,
26
+ minSize: config.minSize,
27
+ developmentMode: config.developmentMode
28
+ });
29
+ this.memoryInfo = recommendation.memoryInfo;
30
+ this.allocationStrategy = recommendation.allocation;
31
+ this.maxSize = recommendation.allocation.cacheSize;
32
+ // Log allocation decision (v3.36.0+: includes model memory)
33
+ prodLog.info(`UnifiedCache initialized: ${formatBytes(this.maxSize)} ` +
34
+ `(${this.allocationStrategy.environment} mode, ` +
35
+ `${(this.allocationStrategy.ratio * 100).toFixed(0)}% of ${formatBytes(this.allocationStrategy.availableForCache)} ` +
36
+ `after ${formatBytes(this.allocationStrategy.modelMemory)} ${this.allocationStrategy.modelPrecision.toUpperCase()} model)`);
37
+ // Log memory detection details
38
+ prodLog.debug(`Memory detection: source=${this.memoryInfo.source}, ` +
39
+ `container=${this.memoryInfo.isContainer}, ` +
40
+ `system=${formatBytes(this.memoryInfo.systemTotal)}, ` +
41
+ `free=${formatBytes(this.memoryInfo.free)}, ` +
42
+ `totalAvailable=${formatBytes(this.memoryInfo.available)}, ` +
43
+ `modelReserved=${formatBytes(this.allocationStrategy.modelMemory)}, ` +
44
+ `availableForCache=${formatBytes(this.allocationStrategy.availableForCache)}`);
45
+ // Log warnings if any
46
+ for (const warning of recommendation.warnings) {
47
+ prodLog.warn(`UnifiedCache: ${warning}`);
48
+ }
49
+ // Finalize configuration
15
50
  this.config = {
16
51
  enableRequestCoalescing: true,
17
52
  enableFairnessCheck: true,
18
53
  fairnessCheckInterval: 60000, // Check fairness every minute
19
54
  persistPatterns: true,
55
+ enableMemoryMonitoring: true,
56
+ memoryCheckInterval: 30000, // Check memory every 30s
20
57
  ...config
21
58
  };
59
+ // Start monitoring
22
60
  if (this.config.enableFairnessCheck) {
23
61
  this.startFairnessMonitor();
24
62
  }
63
+ if (this.config.enableMemoryMonitoring) {
64
+ this.startMemoryPressureMonitor();
65
+ }
25
66
  }
26
67
  /**
27
68
  * Get item from cache with request coalescing
@@ -62,6 +103,25 @@ export class UnifiedCache {
62
103
  }
63
104
  }
64
105
  }
106
+ /**
107
+ * Synchronous cache lookup (v3.36.0+)
108
+ * Returns cached data immediately or undefined if not cached
109
+ * Use for sync fast path optimization - zero async overhead
110
+ */
111
+ getSync(key) {
112
+ // Check if in cache
113
+ const item = this.cache.get(key);
114
+ if (item) {
115
+ // Update access tracking synchronously
116
+ this.access.set(key, (this.access.get(key) || 0) + 1);
117
+ this.totalAccessCount++;
118
+ item.lastAccess = Date.now();
119
+ item.accessCount++;
120
+ this.typeAccessCounts[item.type]++;
121
+ return item.data;
122
+ }
123
+ return undefined;
124
+ }
65
125
  /**
66
126
  * Set item in cache with cost-aware eviction
67
127
  */
@@ -234,7 +294,45 @@ export class UnifiedCache {
234
294
  }
235
295
  }
236
296
  /**
237
- * Get cache statistics
297
+ * Start memory pressure monitoring
298
+ * Periodically checks if we're approaching memory limits
299
+ */
300
+ startMemoryPressureMonitor() {
301
+ const checkInterval = this.config.memoryCheckInterval || 30000;
302
+ this.memoryPressureCheckTimer = setInterval(() => {
303
+ this.checkMemoryPressure();
304
+ }, checkInterval);
305
+ // Unref so it doesn't keep process alive
306
+ if (this.memoryPressureCheckTimer.unref) {
307
+ this.memoryPressureCheckTimer.unref();
308
+ }
309
+ }
310
+ /**
311
+ * Check current memory pressure and warn if needed
312
+ */
313
+ checkMemoryPressure() {
314
+ const pressure = checkMemoryPressure(this.currentSize, this.memoryInfo);
315
+ // Only log warnings every 5 minutes to avoid spam
316
+ const now = Date.now();
317
+ const fiveMinutes = 5 * 60 * 1000;
318
+ if (pressure.warnings.length > 0 && now - this.lastMemoryWarning > fiveMinutes) {
319
+ for (const warning of pressure.warnings) {
320
+ prodLog.warn(`UnifiedCache: ${warning}`);
321
+ }
322
+ this.lastMemoryWarning = now;
323
+ }
324
+ // If critical, force aggressive eviction
325
+ if (pressure.pressure === 'critical') {
326
+ const targetSize = Math.floor(this.maxSize * 0.7); // Evict to 70%
327
+ const bytesToFree = this.currentSize - targetSize;
328
+ if (bytesToFree > 0) {
329
+ prodLog.warn(`UnifiedCache: Critical memory pressure - forcing eviction of ${formatBytes(bytesToFree)}`);
330
+ this.evictForSize(bytesToFree);
331
+ }
332
+ }
333
+ }
334
+ /**
335
+ * Get cache statistics with memory information
238
336
  */
239
337
  getStats() {
240
338
  const typeSizes = { hnsw: 0, metadata: 0, embedding: 0, other: 0 };
@@ -243,7 +341,10 @@ export class UnifiedCache {
243
341
  typeSizes[item.type] += item.size;
244
342
  typeCounts[item.type]++;
245
343
  }
344
+ const hitRate = this.cache.size > 0 ?
345
+ Array.from(this.cache.values()).reduce((sum, item) => sum + item.accessCount, 0) / this.totalAccessCount : 0;
246
346
  return {
347
+ // Cache statistics
247
348
  totalSize: this.currentSize,
248
349
  maxSize: this.maxSize,
249
350
  utilization: this.currentSize / this.maxSize,
@@ -252,8 +353,26 @@ export class UnifiedCache {
252
353
  typeCounts,
253
354
  typeAccessCounts: this.typeAccessCounts,
254
355
  totalAccessCount: this.totalAccessCount,
255
- hitRate: this.cache.size > 0 ?
256
- Array.from(this.cache.values()).reduce((sum, item) => sum + item.accessCount, 0) / this.totalAccessCount : 0
356
+ hitRate,
357
+ // Memory management (v3.36.0+)
358
+ memory: {
359
+ available: this.memoryInfo.available,
360
+ source: this.memoryInfo.source,
361
+ isContainer: this.memoryInfo.isContainer,
362
+ systemTotal: this.memoryInfo.systemTotal,
363
+ allocationRatio: this.allocationStrategy.ratio,
364
+ environment: this.allocationStrategy.environment
365
+ }
366
+ };
367
+ }
368
+ /**
369
+ * Get detailed memory information
370
+ */
371
+ getMemoryInfo() {
372
+ return {
373
+ memoryInfo: { ...this.memoryInfo },
374
+ allocationStrategy: { ...this.allocationStrategy },
375
+ currentPressure: checkMemoryPressure(this.currentSize, this.memoryInfo)
257
376
  };
258
377
  }
259
378
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "3.34.0",
3
+ "version": "3.36.0",
4
4
  "description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns × 40 verbs for infinite expressiveness.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",