@sparkleideas/neural 3.5.2-patch.1

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.
Files changed (122) hide show
  1. package/README.md +260 -0
  2. package/__tests__/README.md +235 -0
  3. package/__tests__/algorithms.test.ts +582 -0
  4. package/__tests__/patterns.test.ts +549 -0
  5. package/__tests__/sona.test.ts +445 -0
  6. package/docs/SONA_INTEGRATION.md +460 -0
  7. package/docs/SONA_QUICKSTART.md +168 -0
  8. package/examples/sona-usage.ts +318 -0
  9. package/package.json +23 -0
  10. package/src/algorithms/a2c.d.ts +86 -0
  11. package/src/algorithms/a2c.d.ts.map +1 -0
  12. package/src/algorithms/a2c.js +361 -0
  13. package/src/algorithms/a2c.js.map +1 -0
  14. package/src/algorithms/a2c.ts +478 -0
  15. package/src/algorithms/curiosity.d.ts +82 -0
  16. package/src/algorithms/curiosity.d.ts.map +1 -0
  17. package/src/algorithms/curiosity.js +392 -0
  18. package/src/algorithms/curiosity.js.map +1 -0
  19. package/src/algorithms/curiosity.ts +509 -0
  20. package/src/algorithms/decision-transformer.d.ts +82 -0
  21. package/src/algorithms/decision-transformer.d.ts.map +1 -0
  22. package/src/algorithms/decision-transformer.js +415 -0
  23. package/src/algorithms/decision-transformer.js.map +1 -0
  24. package/src/algorithms/decision-transformer.ts +521 -0
  25. package/src/algorithms/dqn.d.ts +72 -0
  26. package/src/algorithms/dqn.d.ts.map +1 -0
  27. package/src/algorithms/dqn.js +303 -0
  28. package/src/algorithms/dqn.js.map +1 -0
  29. package/src/algorithms/dqn.ts +382 -0
  30. package/src/algorithms/index.d.ts +32 -0
  31. package/src/algorithms/index.d.ts.map +1 -0
  32. package/src/algorithms/index.js +74 -0
  33. package/src/algorithms/index.js.map +1 -0
  34. package/src/algorithms/index.ts +122 -0
  35. package/src/algorithms/ppo.d.ts +72 -0
  36. package/src/algorithms/ppo.d.ts.map +1 -0
  37. package/src/algorithms/ppo.js +331 -0
  38. package/src/algorithms/ppo.js.map +1 -0
  39. package/src/algorithms/ppo.ts +429 -0
  40. package/src/algorithms/q-learning.d.ts +77 -0
  41. package/src/algorithms/q-learning.d.ts.map +1 -0
  42. package/src/algorithms/q-learning.js +259 -0
  43. package/src/algorithms/q-learning.js.map +1 -0
  44. package/src/algorithms/q-learning.ts +333 -0
  45. package/src/algorithms/sarsa.d.ts +82 -0
  46. package/src/algorithms/sarsa.d.ts.map +1 -0
  47. package/src/algorithms/sarsa.js +297 -0
  48. package/src/algorithms/sarsa.js.map +1 -0
  49. package/src/algorithms/sarsa.ts +383 -0
  50. package/src/algorithms/tmp.json +0 -0
  51. package/src/application/index.ts +11 -0
  52. package/src/application/services/neural-application-service.ts +217 -0
  53. package/src/domain/entities/pattern.ts +169 -0
  54. package/src/domain/index.ts +18 -0
  55. package/src/domain/services/learning-service.ts +256 -0
  56. package/src/index.d.ts +118 -0
  57. package/src/index.d.ts.map +1 -0
  58. package/src/index.js +201 -0
  59. package/src/index.js.map +1 -0
  60. package/src/index.ts +363 -0
  61. package/src/modes/balanced.d.ts +60 -0
  62. package/src/modes/balanced.d.ts.map +1 -0
  63. package/src/modes/balanced.js +234 -0
  64. package/src/modes/balanced.js.map +1 -0
  65. package/src/modes/balanced.ts +299 -0
  66. package/src/modes/base.ts +163 -0
  67. package/src/modes/batch.d.ts +82 -0
  68. package/src/modes/batch.d.ts.map +1 -0
  69. package/src/modes/batch.js +316 -0
  70. package/src/modes/batch.js.map +1 -0
  71. package/src/modes/batch.ts +434 -0
  72. package/src/modes/edge.d.ts +85 -0
  73. package/src/modes/edge.d.ts.map +1 -0
  74. package/src/modes/edge.js +310 -0
  75. package/src/modes/edge.js.map +1 -0
  76. package/src/modes/edge.ts +409 -0
  77. package/src/modes/index.d.ts +55 -0
  78. package/src/modes/index.d.ts.map +1 -0
  79. package/src/modes/index.js +83 -0
  80. package/src/modes/index.js.map +1 -0
  81. package/src/modes/index.ts +16 -0
  82. package/src/modes/real-time.d.ts +58 -0
  83. package/src/modes/real-time.d.ts.map +1 -0
  84. package/src/modes/real-time.js +196 -0
  85. package/src/modes/real-time.js.map +1 -0
  86. package/src/modes/real-time.ts +257 -0
  87. package/src/modes/research.d.ts +79 -0
  88. package/src/modes/research.d.ts.map +1 -0
  89. package/src/modes/research.js +389 -0
  90. package/src/modes/research.js.map +1 -0
  91. package/src/modes/research.ts +486 -0
  92. package/src/modes/tmp.json +0 -0
  93. package/src/pattern-learner.d.ts +117 -0
  94. package/src/pattern-learner.d.ts.map +1 -0
  95. package/src/pattern-learner.js +603 -0
  96. package/src/pattern-learner.js.map +1 -0
  97. package/src/pattern-learner.ts +757 -0
  98. package/src/reasoning-bank.d.ts +259 -0
  99. package/src/reasoning-bank.d.ts.map +1 -0
  100. package/src/reasoning-bank.js +993 -0
  101. package/src/reasoning-bank.js.map +1 -0
  102. package/src/reasoning-bank.ts +1279 -0
  103. package/src/reasoningbank-adapter.ts +697 -0
  104. package/src/sona-integration.d.ts +168 -0
  105. package/src/sona-integration.d.ts.map +1 -0
  106. package/src/sona-integration.js +316 -0
  107. package/src/sona-integration.js.map +1 -0
  108. package/src/sona-integration.ts +432 -0
  109. package/src/sona-manager.d.ts +147 -0
  110. package/src/sona-manager.d.ts.map +1 -0
  111. package/src/sona-manager.js +695 -0
  112. package/src/sona-manager.js.map +1 -0
  113. package/src/sona-manager.ts +835 -0
  114. package/src/tmp.json +0 -0
  115. package/src/types.d.ts +431 -0
  116. package/src/types.d.ts.map +1 -0
  117. package/src/types.js +11 -0
  118. package/src/types.js.map +1 -0
  119. package/src/types.ts +590 -0
  120. package/tmp.json +0 -0
  121. package/tsconfig.json +9 -0
  122. package/vitest.config.ts +19 -0
@@ -0,0 +1,757 @@
1
+ /**
2
+ * Pattern Learner
3
+ *
4
+ * Implements pattern extraction, matching, and evolution for
5
+ * continuous learning from agent experiences.
6
+ *
7
+ * Performance Targets:
8
+ * - Pattern matching: <1ms
9
+ * - Pattern extraction: <5ms
10
+ * - Evolution step: <2ms
11
+ */
12
+
13
+ import type {
14
+ Pattern,
15
+ PatternMatch,
16
+ PatternEvolution,
17
+ Trajectory,
18
+ DistilledMemory,
19
+ NeuralEvent,
20
+ NeuralEventListener,
21
+ } from './types.js';
22
+
23
+ /**
24
+ * Configuration for Pattern Learner
25
+ */
26
+ export interface PatternLearnerConfig {
27
+ /** Maximum number of patterns to store */
28
+ maxPatterns: number;
29
+
30
+ /** Similarity threshold for matching */
31
+ matchThreshold: number;
32
+
33
+ /** Minimum usages before pattern is stable */
34
+ minUsagesForStable: number;
35
+
36
+ /** Quality threshold for pattern inclusion */
37
+ qualityThreshold: number;
38
+
39
+ /** Enable pattern clustering */
40
+ enableClustering: boolean;
41
+
42
+ /** Number of clusters (if clustering enabled) */
43
+ numClusters: number;
44
+
45
+ /** Evolution learning rate */
46
+ evolutionLearningRate: number;
47
+ }
48
+
49
+ /**
50
+ * Default Pattern Learner configuration
51
+ */
52
+ const DEFAULT_CONFIG: PatternLearnerConfig = {
53
+ maxPatterns: 1000,
54
+ matchThreshold: 0.7,
55
+ minUsagesForStable: 5,
56
+ qualityThreshold: 0.5,
57
+ enableClustering: true,
58
+ numClusters: 50,
59
+ evolutionLearningRate: 0.1,
60
+ };
61
+
62
+ /**
63
+ * Cluster representation for fast pattern lookup
64
+ */
65
+ interface PatternCluster {
66
+ clusterId: number;
67
+ centroid: Float32Array;
68
+ patternIds: Set<string>;
69
+ }
70
+
71
+ /**
72
+ * Pattern Learner - Manages pattern extraction, matching, and evolution
73
+ */
74
+ export class PatternLearner {
75
+ private config: PatternLearnerConfig;
76
+ private patterns: Map<string, Pattern> = new Map();
77
+ private clusters: PatternCluster[] = [];
78
+ private patternToCluster: Map<string, number> = new Map();
79
+
80
+ // Performance tracking
81
+ private matchCount = 0;
82
+ private totalMatchTime = 0;
83
+ private extractionCount = 0;
84
+ private totalExtractionTime = 0;
85
+ private evolutionCount = 0;
86
+ private totalEvolutionTime = 0;
87
+
88
+ // Event listeners
89
+ private eventListeners: Set<NeuralEventListener> = new Set();
90
+
91
+ constructor(config: Partial<PatternLearnerConfig> = {}) {
92
+ this.config = { ...DEFAULT_CONFIG, ...config };
93
+ }
94
+
95
+ // ==========================================================================
96
+ // Pattern Matching
97
+ // ==========================================================================
98
+
99
+ /**
100
+ * Find matching patterns for a query embedding
101
+ * Target: <1ms
102
+ */
103
+ findMatches(queryEmbedding: Float32Array, k: number = 3): PatternMatch[] {
104
+ const startTime = performance.now();
105
+
106
+ if (this.patterns.size === 0) {
107
+ return [];
108
+ }
109
+
110
+ let candidates: Pattern[];
111
+
112
+ // Use clustering for faster search if enabled and clusters exist
113
+ if (this.config.enableClustering && this.clusters.length > 0) {
114
+ candidates = this.getCandidatesFromClusters(queryEmbedding);
115
+ } else {
116
+ candidates = Array.from(this.patterns.values());
117
+ }
118
+
119
+ // Compute similarities
120
+ const matches: PatternMatch[] = [];
121
+
122
+ for (const pattern of candidates) {
123
+ const similarity = this.cosineSimilarity(queryEmbedding, pattern.embedding);
124
+
125
+ if (similarity >= this.config.matchThreshold) {
126
+ matches.push({
127
+ pattern,
128
+ similarity,
129
+ confidence: this.computeMatchConfidence(pattern, similarity),
130
+ latencyMs: 0,
131
+ });
132
+ }
133
+ }
134
+
135
+ // Sort by similarity
136
+ matches.sort((a, b) => b.similarity - a.similarity);
137
+ const result = matches.slice(0, k);
138
+
139
+ // Track performance
140
+ const elapsed = performance.now() - startTime;
141
+ this.matchCount++;
142
+ this.totalMatchTime += elapsed;
143
+
144
+ // Warn if over target
145
+ if (elapsed > 1) {
146
+ console.warn(`Pattern matching exceeded target: ${elapsed.toFixed(2)}ms > 1ms`);
147
+ }
148
+
149
+ return result;
150
+ }
151
+
152
+ /**
153
+ * Find best single match
154
+ */
155
+ findBestMatch(queryEmbedding: Float32Array): PatternMatch | null {
156
+ const matches = this.findMatches(queryEmbedding, 1);
157
+ return matches.length > 0 ? matches[0] : null;
158
+ }
159
+
160
+ // ==========================================================================
161
+ // Pattern Extraction
162
+ // ==========================================================================
163
+
164
+ /**
165
+ * Extract a pattern from a trajectory
166
+ * Target: <5ms
167
+ */
168
+ extractPattern(trajectory: Trajectory, memory?: DistilledMemory): Pattern | null {
169
+ const startTime = performance.now();
170
+
171
+ // Validate trajectory
172
+ if (!trajectory.isComplete || trajectory.qualityScore < this.config.qualityThreshold) {
173
+ return null;
174
+ }
175
+
176
+ // Check for duplicates
177
+ const embedding = this.computePatternEmbedding(trajectory);
178
+ const existing = this.findSimilarPattern(embedding, 0.95);
179
+
180
+ if (existing) {
181
+ // Update existing pattern instead
182
+ this.updatePatternFromTrajectory(existing, trajectory);
183
+ return existing;
184
+ }
185
+
186
+ // Create new pattern
187
+ const pattern: Pattern = {
188
+ patternId: `pat_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
189
+ name: this.generatePatternName(trajectory),
190
+ domain: trajectory.domain,
191
+ embedding,
192
+ strategy: this.extractStrategy(trajectory),
193
+ successRate: trajectory.qualityScore,
194
+ usageCount: 1,
195
+ qualityHistory: [trajectory.qualityScore],
196
+ evolutionHistory: [],
197
+ createdAt: Date.now(),
198
+ updatedAt: Date.now(),
199
+ };
200
+
201
+ // Store pattern
202
+ this.patterns.set(pattern.patternId, pattern);
203
+
204
+ // Update clusters if enabled
205
+ if (this.config.enableClustering) {
206
+ this.assignToCluster(pattern);
207
+ }
208
+
209
+ // Prune if over capacity
210
+ if (this.patterns.size > this.config.maxPatterns) {
211
+ this.prunePatterns();
212
+ }
213
+
214
+ // Track performance
215
+ const elapsed = performance.now() - startTime;
216
+ this.extractionCount++;
217
+ this.totalExtractionTime += elapsed;
218
+
219
+ return pattern;
220
+ }
221
+
222
+ /**
223
+ * Extract patterns from multiple trajectories in batch
224
+ */
225
+ extractPatternsBatch(trajectories: Trajectory[]): Pattern[] {
226
+ const patterns: Pattern[] = [];
227
+
228
+ for (const trajectory of trajectories) {
229
+ const pattern = this.extractPattern(trajectory);
230
+ if (pattern) {
231
+ patterns.push(pattern);
232
+ }
233
+ }
234
+
235
+ // Rebuild clusters after batch extraction
236
+ if (this.config.enableClustering && patterns.length > 10) {
237
+ this.rebuildClusters();
238
+ }
239
+
240
+ return patterns;
241
+ }
242
+
243
+ // ==========================================================================
244
+ // Pattern Evolution
245
+ // ==========================================================================
246
+
247
+ /**
248
+ * Evolve a pattern based on new experience
249
+ * Target: <2ms
250
+ */
251
+ evolvePattern(patternId: string, quality: number, context?: string): void {
252
+ const startTime = performance.now();
253
+
254
+ const pattern = this.patterns.get(patternId);
255
+ if (!pattern) return;
256
+
257
+ const previousQuality = pattern.successRate;
258
+ const lr = this.config.evolutionLearningRate;
259
+
260
+ // Update quality history
261
+ pattern.qualityHistory.push(quality);
262
+ if (pattern.qualityHistory.length > 100) {
263
+ pattern.qualityHistory = pattern.qualityHistory.slice(-100);
264
+ }
265
+
266
+ // Exponential moving average for success rate
267
+ pattern.successRate = pattern.successRate * (1 - lr) + quality * lr;
268
+ pattern.usageCount++;
269
+ pattern.updatedAt = Date.now();
270
+
271
+ // Record evolution
272
+ const evolutionType = this.determineEvolutionType(previousQuality, pattern.successRate);
273
+ pattern.evolutionHistory.push({
274
+ timestamp: Date.now(),
275
+ type: evolutionType,
276
+ previousQuality,
277
+ newQuality: pattern.successRate,
278
+ description: context || 'Updated from new experience',
279
+ });
280
+
281
+ // Keep evolution history bounded
282
+ if (pattern.evolutionHistory.length > 50) {
283
+ pattern.evolutionHistory = pattern.evolutionHistory.slice(-50);
284
+ }
285
+
286
+ // Emit event
287
+ this.emitEvent({
288
+ type: 'pattern_evolved',
289
+ patternId,
290
+ evolutionType,
291
+ });
292
+
293
+ // Track performance
294
+ const elapsed = performance.now() - startTime;
295
+ this.evolutionCount++;
296
+ this.totalEvolutionTime += elapsed;
297
+ }
298
+
299
+ /**
300
+ * Merge two similar patterns
301
+ */
302
+ mergePatterns(patternId1: string, patternId2: string): Pattern | null {
303
+ const p1 = this.patterns.get(patternId1);
304
+ const p2 = this.patterns.get(patternId2);
305
+
306
+ if (!p1 || !p2) return null;
307
+
308
+ // Keep the higher quality pattern as base
309
+ const [keep, remove] = p1.successRate >= p2.successRate ? [p1, p2] : [p2, p1];
310
+
311
+ // Merge embeddings (weighted average)
312
+ const totalUsage = keep.usageCount + remove.usageCount;
313
+ const w1 = keep.usageCount / totalUsage;
314
+ const w2 = remove.usageCount / totalUsage;
315
+
316
+ for (let i = 0; i < keep.embedding.length; i++) {
317
+ keep.embedding[i] = keep.embedding[i] * w1 + remove.embedding[i] * w2;
318
+ }
319
+
320
+ // Merge statistics
321
+ keep.usageCount += remove.usageCount;
322
+ keep.qualityHistory.push(...remove.qualityHistory);
323
+ keep.successRate = keep.qualityHistory.reduce((a, b) => a + b, 0) / keep.qualityHistory.length;
324
+
325
+ // Record merge
326
+ keep.evolutionHistory.push({
327
+ timestamp: Date.now(),
328
+ type: 'merge',
329
+ previousQuality: p1.successRate,
330
+ newQuality: keep.successRate,
331
+ description: `Merged with pattern ${remove.patternId}`,
332
+ });
333
+
334
+ // Remove the merged pattern
335
+ this.patterns.delete(remove.patternId);
336
+ this.patternToCluster.delete(remove.patternId);
337
+
338
+ return keep;
339
+ }
340
+
341
+ /**
342
+ * Split a pattern into more specific sub-patterns
343
+ */
344
+ splitPattern(patternId: string, numSplits: number = 2): Pattern[] {
345
+ const pattern = this.patterns.get(patternId);
346
+ if (!pattern || numSplits < 2) return [];
347
+
348
+ const splits: Pattern[] = [];
349
+
350
+ for (let i = 0; i < numSplits; i++) {
351
+ // Create variation of embedding with noise
352
+ const newEmbedding = new Float32Array(pattern.embedding.length);
353
+ for (let j = 0; j < newEmbedding.length; j++) {
354
+ const noise = (Math.random() - 0.5) * 0.1;
355
+ newEmbedding[j] = pattern.embedding[j] + noise;
356
+ }
357
+
358
+ const newPattern: Pattern = {
359
+ patternId: `pat_${Date.now()}_${i}_${Math.random().toString(36).slice(2, 6)}`,
360
+ name: `${pattern.name}_split_${i}`,
361
+ domain: pattern.domain,
362
+ embedding: newEmbedding,
363
+ strategy: pattern.strategy,
364
+ successRate: pattern.successRate * 0.9, // Slight penalty for uncertainty
365
+ usageCount: 0,
366
+ qualityHistory: [],
367
+ evolutionHistory: [{
368
+ timestamp: Date.now(),
369
+ type: 'split',
370
+ previousQuality: pattern.successRate,
371
+ newQuality: pattern.successRate * 0.9,
372
+ description: `Split from pattern ${patternId}`,
373
+ }],
374
+ createdAt: Date.now(),
375
+ updatedAt: Date.now(),
376
+ };
377
+
378
+ this.patterns.set(newPattern.patternId, newPattern);
379
+ splits.push(newPattern);
380
+ }
381
+
382
+ // Remove original pattern
383
+ this.patterns.delete(patternId);
384
+ this.patternToCluster.delete(patternId);
385
+
386
+ // Rebuild clusters
387
+ if (this.config.enableClustering) {
388
+ this.rebuildClusters();
389
+ }
390
+
391
+ return splits;
392
+ }
393
+
394
+ // ==========================================================================
395
+ // Pattern Access
396
+ // ==========================================================================
397
+
398
+ /**
399
+ * Get all patterns
400
+ */
401
+ getPatterns(): Pattern[] {
402
+ return Array.from(this.patterns.values());
403
+ }
404
+
405
+ /**
406
+ * Get pattern by ID
407
+ */
408
+ getPattern(patternId: string): Pattern | undefined {
409
+ return this.patterns.get(patternId);
410
+ }
411
+
412
+ /**
413
+ * Get patterns by domain
414
+ */
415
+ getPatternsByDomain(domain: string): Pattern[] {
416
+ return Array.from(this.patterns.values()).filter(p => p.domain === domain);
417
+ }
418
+
419
+ /**
420
+ * Get stable patterns (sufficient usage)
421
+ */
422
+ getStablePatterns(): Pattern[] {
423
+ return Array.from(this.patterns.values())
424
+ .filter(p => p.usageCount >= this.config.minUsagesForStable);
425
+ }
426
+
427
+ // ==========================================================================
428
+ // Statistics
429
+ // ==========================================================================
430
+
431
+ getStats(): Record<string, number> {
432
+ const patterns = Array.from(this.patterns.values());
433
+
434
+ return {
435
+ totalPatterns: this.patterns.size,
436
+ stablePatterns: patterns.filter(p => p.usageCount >= this.config.minUsagesForStable).length,
437
+ avgSuccessRate: patterns.length > 0
438
+ ? patterns.reduce((s, p) => s + p.successRate, 0) / patterns.length
439
+ : 0,
440
+ avgUsageCount: patterns.length > 0
441
+ ? patterns.reduce((s, p) => s + p.usageCount, 0) / patterns.length
442
+ : 0,
443
+ numClusters: this.clusters.length,
444
+ avgMatchTimeMs: this.matchCount > 0 ? this.totalMatchTime / this.matchCount : 0,
445
+ avgExtractionTimeMs: this.extractionCount > 0 ? this.totalExtractionTime / this.extractionCount : 0,
446
+ avgEvolutionTimeMs: this.evolutionCount > 0 ? this.totalEvolutionTime / this.evolutionCount : 0,
447
+ };
448
+ }
449
+
450
+ // ==========================================================================
451
+ // Event System
452
+ // ==========================================================================
453
+
454
+ addEventListener(listener: NeuralEventListener): void {
455
+ this.eventListeners.add(listener);
456
+ }
457
+
458
+ removeEventListener(listener: NeuralEventListener): void {
459
+ this.eventListeners.delete(listener);
460
+ }
461
+
462
+ private emitEvent(event: NeuralEvent): void {
463
+ for (const listener of this.eventListeners) {
464
+ try {
465
+ listener(event);
466
+ } catch (error) {
467
+ console.error('Error in PatternLearner event listener:', error);
468
+ }
469
+ }
470
+ }
471
+
472
+ // ==========================================================================
473
+ // Private Helper Methods
474
+ // ==========================================================================
475
+
476
+ private cosineSimilarity(a: Float32Array, b: Float32Array): number {
477
+ if (a.length !== b.length) return 0;
478
+
479
+ let dot = 0, normA = 0, normB = 0;
480
+ for (let i = 0; i < a.length; i++) {
481
+ dot += a[i] * b[i];
482
+ normA += a[i] * a[i];
483
+ normB += b[i] * b[i];
484
+ }
485
+
486
+ const denom = Math.sqrt(normA) * Math.sqrt(normB);
487
+ return denom > 0 ? dot / denom : 0;
488
+ }
489
+
490
+ private computeMatchConfidence(pattern: Pattern, similarity: number): number {
491
+ // Combine similarity with pattern reliability
492
+ const usageWeight = Math.min(pattern.usageCount / 10, 1);
493
+ const qualityWeight = pattern.successRate;
494
+
495
+ return similarity * (1 - usageWeight * 0.2 - qualityWeight * 0.2) +
496
+ usageWeight * 0.1 +
497
+ qualityWeight * 0.1;
498
+ }
499
+
500
+ private getCandidatesFromClusters(queryEmbedding: Float32Array): Pattern[] {
501
+ // Find nearest clusters
502
+ const clusterScores: Array<{ cluster: PatternCluster; score: number }> = [];
503
+
504
+ for (const cluster of this.clusters) {
505
+ const score = this.cosineSimilarity(queryEmbedding, cluster.centroid);
506
+ clusterScores.push({ cluster, score });
507
+ }
508
+
509
+ clusterScores.sort((a, b) => b.score - a.score);
510
+
511
+ // Get patterns from top 3 clusters
512
+ const candidates: Pattern[] = [];
513
+ for (const { cluster } of clusterScores.slice(0, 3)) {
514
+ for (const patternId of cluster.patternIds) {
515
+ const pattern = this.patterns.get(patternId);
516
+ if (pattern) {
517
+ candidates.push(pattern);
518
+ }
519
+ }
520
+ }
521
+
522
+ return candidates;
523
+ }
524
+
525
+ private findSimilarPattern(embedding: Float32Array, threshold: number): Pattern | null {
526
+ for (const pattern of this.patterns.values()) {
527
+ const sim = this.cosineSimilarity(embedding, pattern.embedding);
528
+ if (sim >= threshold) {
529
+ return pattern;
530
+ }
531
+ }
532
+ return null;
533
+ }
534
+
535
+ private updatePatternFromTrajectory(pattern: Pattern, trajectory: Trajectory): void {
536
+ // Update quality
537
+ pattern.qualityHistory.push(trajectory.qualityScore);
538
+ if (pattern.qualityHistory.length > 100) {
539
+ pattern.qualityHistory = pattern.qualityHistory.slice(-100);
540
+ }
541
+
542
+ // EMA for success rate
543
+ const lr = this.config.evolutionLearningRate;
544
+ pattern.successRate = pattern.successRate * (1 - lr) + trajectory.qualityScore * lr;
545
+ pattern.usageCount++;
546
+ pattern.updatedAt = Date.now();
547
+ }
548
+
549
+ private computePatternEmbedding(trajectory: Trajectory): Float32Array {
550
+ if (trajectory.steps.length === 0) {
551
+ return new Float32Array(768);
552
+ }
553
+
554
+ const dim = trajectory.steps[0].stateAfter.length;
555
+ const embedding = new Float32Array(dim);
556
+
557
+ // Weighted average (higher weight for later steps)
558
+ let totalWeight = 0;
559
+ for (let i = 0; i < trajectory.steps.length; i++) {
560
+ const weight = (i + 1) / trajectory.steps.length;
561
+ totalWeight += weight;
562
+ for (let j = 0; j < dim; j++) {
563
+ embedding[j] += trajectory.steps[i].stateAfter[j] * weight;
564
+ }
565
+ }
566
+
567
+ for (let j = 0; j < dim; j++) {
568
+ embedding[j] /= totalWeight;
569
+ }
570
+
571
+ return embedding;
572
+ }
573
+
574
+ private generatePatternName(trajectory: Trajectory): string {
575
+ const domain = trajectory.domain;
576
+ const quality = trajectory.qualityScore > 0.7 ? 'high' : 'mid';
577
+ const steps = trajectory.steps.length > 5 ? 'complex' : 'simple';
578
+ return `${domain}_${quality}_${steps}_${Date.now() % 10000}`;
579
+ }
580
+
581
+ private extractStrategy(trajectory: Trajectory): string {
582
+ const actions = trajectory.steps.map(s => s.action);
583
+ if (actions.length === 0) return 'empty';
584
+ if (actions.length <= 3) return actions.join(' -> ');
585
+ return `${actions.slice(0, 2).join(' -> ')} ... ${actions[actions.length - 1]}`;
586
+ }
587
+
588
+ private assignToCluster(pattern: Pattern): void {
589
+ if (this.clusters.length === 0) {
590
+ // Create first cluster
591
+ this.clusters.push({
592
+ clusterId: 0,
593
+ centroid: new Float32Array(pattern.embedding),
594
+ patternIds: new Set([pattern.patternId]),
595
+ });
596
+ this.patternToCluster.set(pattern.patternId, 0);
597
+ return;
598
+ }
599
+
600
+ // Find nearest cluster
601
+ let bestCluster = 0;
602
+ let bestSim = -1;
603
+
604
+ for (let i = 0; i < this.clusters.length; i++) {
605
+ const sim = this.cosineSimilarity(pattern.embedding, this.clusters[i].centroid);
606
+ if (sim > bestSim) {
607
+ bestSim = sim;
608
+ bestCluster = i;
609
+ }
610
+ }
611
+
612
+ // Create new cluster if not similar enough and under limit
613
+ if (bestSim < 0.7 && this.clusters.length < this.config.numClusters) {
614
+ const newId = this.clusters.length;
615
+ this.clusters.push({
616
+ clusterId: newId,
617
+ centroid: new Float32Array(pattern.embedding),
618
+ patternIds: new Set([pattern.patternId]),
619
+ });
620
+ this.patternToCluster.set(pattern.patternId, newId);
621
+ } else {
622
+ // Add to existing cluster and update centroid
623
+ const cluster = this.clusters[bestCluster];
624
+ cluster.patternIds.add(pattern.patternId);
625
+ this.patternToCluster.set(pattern.patternId, bestCluster);
626
+ this.updateClusterCentroid(cluster);
627
+ }
628
+ }
629
+
630
+ private updateClusterCentroid(cluster: PatternCluster): void {
631
+ const dim = cluster.centroid.length;
632
+ const newCentroid = new Float32Array(dim);
633
+ let count = 0;
634
+
635
+ for (const patternId of cluster.patternIds) {
636
+ const pattern = this.patterns.get(patternId);
637
+ if (pattern) {
638
+ for (let i = 0; i < dim; i++) {
639
+ newCentroid[i] += pattern.embedding[i];
640
+ }
641
+ count++;
642
+ }
643
+ }
644
+
645
+ if (count > 0) {
646
+ for (let i = 0; i < dim; i++) {
647
+ newCentroid[i] /= count;
648
+ }
649
+ cluster.centroid = newCentroid;
650
+ }
651
+ }
652
+
653
+ private rebuildClusters(): void {
654
+ if (this.patterns.size === 0) {
655
+ this.clusters = [];
656
+ this.patternToCluster.clear();
657
+ return;
658
+ }
659
+
660
+ const patterns = Array.from(this.patterns.values());
661
+ const k = Math.min(this.config.numClusters, Math.ceil(patterns.length / 5));
662
+ const dim = patterns[0].embedding.length;
663
+
664
+ // Initialize clusters with random patterns
665
+ this.clusters = [];
666
+ this.patternToCluster.clear();
667
+
668
+ const indices = new Set<number>();
669
+ while (indices.size < k && indices.size < patterns.length) {
670
+ indices.add(Math.floor(Math.random() * patterns.length));
671
+ }
672
+
673
+ let clusterId = 0;
674
+ for (const idx of indices) {
675
+ this.clusters.push({
676
+ clusterId: clusterId++,
677
+ centroid: new Float32Array(patterns[idx].embedding),
678
+ patternIds: new Set(),
679
+ });
680
+ }
681
+
682
+ // K-means iterations
683
+ for (let iter = 0; iter < 10; iter++) {
684
+ // Clear assignments
685
+ for (const cluster of this.clusters) {
686
+ cluster.patternIds.clear();
687
+ }
688
+
689
+ // Assign patterns to nearest cluster
690
+ for (const pattern of patterns) {
691
+ let bestCluster = 0;
692
+ let bestSim = -1;
693
+
694
+ for (let c = 0; c < this.clusters.length; c++) {
695
+ const sim = this.cosineSimilarity(pattern.embedding, this.clusters[c].centroid);
696
+ if (sim > bestSim) {
697
+ bestSim = sim;
698
+ bestCluster = c;
699
+ }
700
+ }
701
+
702
+ this.clusters[bestCluster].patternIds.add(pattern.patternId);
703
+ this.patternToCluster.set(pattern.patternId, bestCluster);
704
+ }
705
+
706
+ // Update centroids
707
+ for (const cluster of this.clusters) {
708
+ this.updateClusterCentroid(cluster);
709
+ }
710
+ }
711
+
712
+ // Remove empty clusters
713
+ this.clusters = this.clusters.filter(c => c.patternIds.size > 0);
714
+ }
715
+
716
+ private prunePatterns(): void {
717
+ // Sort by score (quality * log(usage))
718
+ const scored = Array.from(this.patterns.entries())
719
+ .map(([id, pattern]) => ({
720
+ id,
721
+ pattern,
722
+ score: pattern.successRate * Math.log(pattern.usageCount + 1),
723
+ }))
724
+ .sort((a, b) => a.score - b.score);
725
+
726
+ // Remove lowest scoring patterns
727
+ const toRemove = scored.length - Math.floor(this.config.maxPatterns * 0.8);
728
+ for (let i = 0; i < toRemove && i < scored.length; i++) {
729
+ this.patterns.delete(scored[i].id);
730
+ this.patternToCluster.delete(scored[i].id);
731
+ }
732
+
733
+ // Rebuild clusters
734
+ if (this.config.enableClustering) {
735
+ this.rebuildClusters();
736
+ }
737
+ }
738
+
739
+ private determineEvolutionType(
740
+ prev: number,
741
+ curr: number
742
+ ): 'improvement' | 'merge' | 'split' | 'prune' {
743
+ const delta = curr - prev;
744
+ if (delta > 0.05) return 'improvement';
745
+ if (delta < -0.15) return 'prune';
746
+ return 'improvement';
747
+ }
748
+ }
749
+
750
+ /**
751
+ * Factory function for creating PatternLearner
752
+ */
753
+ export function createPatternLearner(
754
+ config?: Partial<PatternLearnerConfig>
755
+ ): PatternLearner {
756
+ return new PatternLearner(config);
757
+ }