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