claude-flow 3.5.73 → 3.5.75

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-flow",
3
- "version": "3.5.73",
3
+ "version": "3.5.75",
4
4
  "description": "Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -260,6 +260,17 @@ const trainCommand = {
260
260
  // Flush patterns to disk
261
261
  flushPatterns();
262
262
  const persistence = getPersistenceStatus();
263
+ // Save LoRA checkpoint via ruvllm TrainingPipeline if available
264
+ try {
265
+ const { LoRAAdapter } = await import('../ruvector/lora-adapter.js');
266
+ const path = await import('path');
267
+ const cpDir = path.join(process.cwd(), '.claude-flow', 'neural');
268
+ const cpPath = path.join(cpDir, `lora-checkpoint-${Date.now()}.json`);
269
+ const adapter = new LoRAAdapter({ inputDim: dim, outputDim: dim, rank: 4 });
270
+ await adapter.initBackend();
271
+ await adapter.saveCheckpoint(cpPath);
272
+ }
273
+ catch { /* checkpoint save is best-effort */ }
263
274
  output.writeln();
264
275
  // Display results
265
276
  const tableData = [
@@ -421,6 +432,43 @@ const statusCommand = {
421
432
  status: output.success('Available'),
422
433
  details: '~4x memory reduction',
423
434
  },
435
+ {
436
+ component: 'ruvllm Coordinator',
437
+ status: stats._ruvllmBackend === 'active' ? output.success('Active') : output.dim('Unavailable'),
438
+ details: stats._ruvllmBackend === 'active'
439
+ ? `SonaCoordinator | ${stats._ruvllmTrajectories} trajectories`
440
+ : 'Install @ruvector/ruvllm',
441
+ },
442
+ {
443
+ component: 'Contrastive Trainer',
444
+ status: stats._contrastiveTrainer && stats._contrastiveTrainer !== 'unavailable' ? output.success('Active') : output.dim('Unavailable'),
445
+ details: stats._contrastiveTrainer && stats._contrastiveTrainer !== 'unavailable'
446
+ ? `${stats._contrastiveTrainer.triplets ?? 0} triplets, ${stats._contrastiveTrainer.agents ?? 0} agents`
447
+ : 'Install @ruvector/ruvllm',
448
+ },
449
+ {
450
+ component: 'Training Pipeline',
451
+ status: stats._trainingBackend === 'ruvllm' ? output.success('Active') : output.dim(stats._trainingBackend || 'Unavailable'),
452
+ details: stats._trainingBackend === 'ruvllm'
453
+ ? 'ruvllm checkpoints enabled'
454
+ : 'JS fallback (no checkpoints)',
455
+ },
456
+ await (async () => {
457
+ try {
458
+ const { getGraphStats } = await import('../ruvector/graph-backend.js');
459
+ const gs = await getGraphStats();
460
+ return {
461
+ component: 'Graph Database',
462
+ status: gs.backend === 'graph-node' ? output.success('Active') : output.dim('Unavailable'),
463
+ details: gs.backend === 'graph-node'
464
+ ? `${gs.totalNodes} nodes, ${gs.totalEdges} edges`
465
+ : 'Install @ruvector/graph-node',
466
+ };
467
+ }
468
+ catch {
469
+ return { component: 'Graph Database', status: output.dim('Unavailable'), details: 'Not loaded' };
470
+ }
471
+ })(),
424
472
  ],
425
473
  });
426
474
  if (verbose) {
@@ -678,6 +726,12 @@ const optimizeCommand = {
678
726
  await initializeIntelligence();
679
727
  const patterns = await getAllPatterns();
680
728
  const stats = getIntelligenceStats();
729
+ // Trigger ruvllm background learning if available
730
+ try {
731
+ const { runBackgroundLearning } = await import('../memory/intelligence.js');
732
+ await runBackgroundLearning();
733
+ }
734
+ catch { /* background learning is best-effort */ }
681
735
  // Get actual pattern storage size
682
736
  const patternDir = path.join(process.cwd(), '.claude-flow', 'neural');
683
737
  let beforeSize = 0;
@@ -184,6 +184,12 @@ export const agentTools = [
184
184
  };
185
185
  store.agents[agentId] = agent;
186
186
  saveAgentStore(store);
187
+ // Record agent in graph database (ADR-087, best-effort)
188
+ try {
189
+ const { addNode } = await import('../ruvector/graph-backend.js');
190
+ await addNode({ id: agentId, type: 'agent', name: agentType });
191
+ }
192
+ catch { /* graph-node not available */ }
187
193
  // Include Agent Booster routing info if applicable
188
194
  const response = {
189
195
  success: true,
@@ -243,6 +243,20 @@ export const agentdbCausalEdge = {
243
243
  return { success: false, error: 'targetId is required (non-empty string)' };
244
244
  if (!relation)
245
245
  return { success: false, error: 'relation is required (non-empty string)' };
246
+ // Try native graph-node backend first (ADR-087)
247
+ try {
248
+ const graphBackend = await import('../ruvector/graph-backend.js');
249
+ if (await graphBackend.isGraphBackendAvailable()) {
250
+ const graphResult = await graphBackend.recordCausalEdge(sourceId, targetId, relation, typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined);
251
+ if (graphResult.success) {
252
+ // Also record in AgentDB bridge for compatibility
253
+ const bridge = await getBridge();
254
+ await bridge.bridgeRecordCausalEdge({ sourceId, targetId, relation, weight: typeof params.weight === 'number' ? validateScore(params.weight, 0.5) : undefined }).catch(() => { });
255
+ return { ...graphResult, _graphNodeBackend: true };
256
+ }
257
+ }
258
+ }
259
+ catch { /* graph-node not available, fall through */ }
246
260
  const bridge = await getBridge();
247
261
  const result = await bridge.bridgeRecordCausalEdge({
248
262
  sourceId,
@@ -2033,6 +2033,46 @@ export const hooksIntelligence = {
2033
2033
  implemented: true,
2034
2034
  note: 'Real ONNX embeddings via Xenova/all-MiniLM-L6-v2',
2035
2035
  },
2036
+ ruvllmCoordinator: await (async () => {
2037
+ try {
2038
+ const { getIntelligenceStats } = await import('../memory/intelligence.js');
2039
+ const s = getIntelligenceStats();
2040
+ return { status: s._ruvllmBackend || 'unavailable', trajectories: s._ruvllmTrajectories || 0, note: s._ruvllmBackend === 'active' ? 'SonaCoordinator forwarding trajectories' : '@ruvector/ruvllm not loaded' };
2041
+ }
2042
+ catch {
2043
+ return { status: 'unavailable', trajectories: 0, note: 'Not initialized' };
2044
+ }
2045
+ })(),
2046
+ contrastiveTrainer: await (async () => {
2047
+ try {
2048
+ const { getSONAStats } = await import('../memory/sona-optimizer.js');
2049
+ const s = await getSONAStats();
2050
+ return { status: s._contrastiveTrainer !== 'unavailable' ? 'active' : 'unavailable', details: s._contrastiveTrainer, note: s._contrastiveTrainer !== 'unavailable' ? 'Agent embedding learning active' : '@ruvector/ruvllm not loaded' };
2051
+ }
2052
+ catch {
2053
+ return { status: 'unavailable', details: null, note: 'Not initialized' };
2054
+ }
2055
+ })(),
2056
+ trainingPipeline: await (async () => {
2057
+ try {
2058
+ const loraInst = await getLoRAAdapter();
2059
+ const s = loraInst?.getStats();
2060
+ return { status: s?._trainingBackend || 'unavailable', note: s?._trainingBackend === 'ruvllm' ? 'Checkpoint save/load via ruvllm' : 'JS fallback' };
2061
+ }
2062
+ catch {
2063
+ return { status: 'unavailable', note: 'Not initialized' };
2064
+ }
2065
+ })(),
2066
+ graphDatabase: await (async () => {
2067
+ try {
2068
+ const { getGraphStats } = await import('../ruvector/graph-backend.js');
2069
+ const gs = await getGraphStats();
2070
+ return { status: gs.backend, totalNodes: gs.totalNodes, totalEdges: gs.totalEdges, avgDegree: gs.avgDegree, note: gs.backend === 'graph-node' ? 'Native Rust graph with hyperedges and k-hop queries' : '@ruvector/graph-node not loaded' };
2071
+ }
2072
+ catch {
2073
+ return { status: 'unavailable', totalNodes: 0, totalEdges: 0, avgDegree: 0, note: 'Not initialized' };
2074
+ }
2075
+ })(),
2036
2076
  },
2037
2077
  realMetrics: {
2038
2078
  trajectories: realStats.trajectories,
@@ -2044,7 +2084,7 @@ export const hooksIntelligence = {
2044
2084
  working: [
2045
2085
  'memory-store', 'embeddings', 'trajectory-recording', 'claims', 'swarm-coordination',
2046
2086
  'hnsw-index', 'pattern-storage', 'sona-optimizer', 'ewc-consolidation', 'moe-routing',
2047
- 'flash-attention', 'lora-adapter'
2087
+ 'flash-attention', 'lora-adapter', 'ruvllm-coordinator', 'contrastive-trainer', 'training-pipeline', 'graph-database'
2048
2088
  ],
2049
2089
  partial: [],
2050
2090
  notImplemented: [],
@@ -2307,6 +2347,12 @@ export const hooksTrajectoryEnd = {
2307
2347
  // SONA learning failed, continue without it
2308
2348
  }
2309
2349
  }
2350
+ // Trigger ruvllm background learning after trajectory end
2351
+ try {
2352
+ const { runBackgroundLearning } = await import('../memory/intelligence.js');
2353
+ await runBackgroundLearning();
2354
+ }
2355
+ catch { /* best-effort */ }
2310
2356
  // Try EWC++ consolidation on successful trajectories
2311
2357
  if (success) {
2312
2358
  const ewc = await getEWCConsolidator();
@@ -2671,12 +2717,38 @@ export const hooksIntelligenceStats = {
2671
2717
  implementation: 'real-lora',
2672
2718
  };
2673
2719
  }
2720
+ // ruvllm native backend stats
2721
+ let ruvllmStats = { coordinator: 'unavailable', trajectories: 0, contrastiveTrainer: 'unavailable', trainingBackend: 'unavailable', graphDatabase: { backend: 'unavailable', totalNodes: 0, totalEdges: 0 } };
2722
+ try {
2723
+ const { getIntelligenceStats } = await import('../memory/intelligence.js');
2724
+ const iStats = getIntelligenceStats();
2725
+ ruvllmStats.coordinator = iStats._ruvllmBackend || 'unavailable';
2726
+ ruvllmStats.trajectories = iStats._ruvllmTrajectories || 0;
2727
+ }
2728
+ catch { /* not initialized */ }
2729
+ try {
2730
+ const { getSONAStats: getSONA } = await import('../memory/sona-optimizer.js');
2731
+ const sStats = await getSONA();
2732
+ ruvllmStats.contrastiveTrainer = sStats._contrastiveTrainer || 'unavailable';
2733
+ }
2734
+ catch { /* not initialized */ }
2735
+ if (lora) {
2736
+ const ls = lora.getStats();
2737
+ ruvllmStats.trainingBackend = ls._trainingBackend || 'unavailable';
2738
+ }
2739
+ try {
2740
+ const { getGraphStats } = await import('../ruvector/graph-backend.js');
2741
+ const gs = await getGraphStats();
2742
+ ruvllmStats.graphDatabase = { backend: gs.backend, totalNodes: gs.totalNodes, totalEdges: gs.totalEdges, avgDegree: gs.avgDegree };
2743
+ }
2744
+ catch { /* not available */ }
2674
2745
  const stats = {
2675
2746
  sona: sonaStats,
2676
2747
  moe: moeStats,
2677
2748
  ewc: ewcStats,
2678
2749
  flash: flashStats,
2679
2750
  lora: loraStats,
2751
+ ruvllm: ruvllmStats,
2680
2752
  hnsw: {
2681
2753
  indexSize: memoryStats.memory.indexSize,
2682
2754
  avgSearchTimeMs: 0.12,
@@ -16,8 +16,32 @@ export const ruvllmWasmTools = [
16
16
  handler: async () => {
17
17
  try {
18
18
  const mod = await loadRuvllmWasm();
19
- const status = await mod.getRuvllmStatus();
20
- return { content: [{ type: 'text', text: JSON.stringify(status, null, 2) }] };
19
+ const wasmStatus = await mod.getRuvllmStatus();
20
+ // Also include native ruvllm CJS backend status (ADR-086)
21
+ let nativeBackend = { available: false };
22
+ try {
23
+ const { getIntelligenceStats } = await import('../memory/intelligence.js');
24
+ const iStats = getIntelligenceStats();
25
+ const { getSONAStats } = await import('../memory/sona-optimizer.js');
26
+ const sStats = await getSONAStats();
27
+ nativeBackend = {
28
+ available: iStats._ruvllmBackend === 'active',
29
+ coordinator: iStats._ruvllmBackend || 'unavailable',
30
+ trajectories: iStats._ruvllmTrajectories || 0,
31
+ contrastiveTrainer: sStats._contrastiveTrainer !== 'unavailable' ? 'active' : 'unavailable',
32
+ trainingBackend: iStats._trainingBackend || 'unknown',
33
+ };
34
+ }
35
+ catch { /* not initialized yet */ }
36
+ // Graph database status (ADR-087)
37
+ let graphStatus = { available: false };
38
+ try {
39
+ const { getGraphStats } = await import('../ruvector/graph-backend.js');
40
+ const gs = await getGraphStats();
41
+ graphStatus = { available: gs.backend === 'graph-node', ...gs };
42
+ }
43
+ catch { /* not loaded */ }
44
+ return { content: [{ type: 'text', text: JSON.stringify({ wasm: wasmStatus, native: nativeBackend, graph: graphStatus }, null, 2) }] };
21
45
  }
22
46
  catch (err) {
23
47
  return { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }], isError: true };
@@ -250,7 +250,15 @@ export declare function findSimilarPatterns(query: string, options?: {
250
250
  /**
251
251
  * Get intelligence system statistics
252
252
  */
253
- export declare function getIntelligenceStats(): IntelligenceStats;
253
+ export declare function getIntelligenceStats(): IntelligenceStats & {
254
+ _ruvllmBackend: string;
255
+ _ruvllmTrajectories: number;
256
+ _contrastiveTrainer?: {
257
+ triplets: number;
258
+ agents: number;
259
+ } | string;
260
+ _trainingBackend?: string;
261
+ };
254
262
  /**
255
263
  * Get SONA coordinator for advanced operations
256
264
  */
@@ -329,6 +337,11 @@ export declare function clearAllPatterns(): Promise<void>;
329
337
  * Get the neural data directory path
330
338
  */
331
339
  export declare function getNeuralDataDir(): string;
340
+ /**
341
+ * Trigger background learning on the @ruvector/ruvllm SonaCoordinator.
342
+ * No-op if ruvllm is not installed.
343
+ */
344
+ export declare function runBackgroundLearning(): Promise<void>;
332
345
  /**
333
346
  * Get persistence status
334
347
  */
@@ -559,6 +559,26 @@ class LocalReasoningBank {
559
559
  }
560
560
  }
561
561
  // ============================================================================
562
+ // @ruvector/ruvllm SonaCoordinator Integration
563
+ // ============================================================================
564
+ let ruvllmCoordinator = null;
565
+ let ruvllmLoaded = false;
566
+ async function loadRuvllmCoordinator() {
567
+ if (ruvllmLoaded)
568
+ return ruvllmCoordinator;
569
+ ruvllmLoaded = true;
570
+ try {
571
+ const { createRequire } = await import('module');
572
+ const requireCjs = createRequire(import.meta.url);
573
+ const ruvllm = requireCjs('@ruvector/ruvllm');
574
+ ruvllmCoordinator = new ruvllm.SonaCoordinator(ruvllm.DEFAULT_SONA_CONFIG);
575
+ return ruvllmCoordinator;
576
+ }
577
+ catch {
578
+ return null;
579
+ }
580
+ }
581
+ // ============================================================================
562
582
  // Module State
563
583
  // ============================================================================
564
584
  let sonaCoordinator = null;
@@ -634,6 +654,8 @@ export async function initializeIntelligence(config) {
634
654
  });
635
655
  // Load persisted stats if available
636
656
  loadPersistedStats();
657
+ // Eagerly load ruvllm coordinator so stats reflect backend status
658
+ await loadRuvllmCoordinator();
637
659
  intelligenceInitialized = true;
638
660
  return {
639
661
  success: true,
@@ -773,6 +795,26 @@ export async function recordTrajectory(steps, verdict) {
773
795
  }
774
796
  }
775
797
  }
798
+ // Forward trajectory to @ruvector/ruvllm SonaCoordinator if available
799
+ const ruvllmCoord = await loadRuvllmCoordinator();
800
+ if (ruvllmCoord) {
801
+ try {
802
+ const avgQuality = verdict === 'success' ? 1.0 : verdict === 'partial' ? 0.5 : 0.0;
803
+ ruvllmCoord.recordTrajectory({
804
+ steps: enrichedSteps.map(s => ({
805
+ state: s.content,
806
+ action: s.type,
807
+ reward: avgQuality,
808
+ embedding: s.embedding || []
809
+ })),
810
+ totalReward: avgQuality,
811
+ success: verdict === 'success'
812
+ });
813
+ }
814
+ catch {
815
+ // ruvllm recording failed silently
816
+ }
817
+ }
776
818
  globalStats.trajectoriesRecorded++;
777
819
  globalStats.lastAdaptation = Date.now();
778
820
  savePersistedStats();
@@ -837,6 +879,19 @@ export async function findSimilarPatterns(query, options) {
837
879
  export function getIntelligenceStats() {
838
880
  const sonaStats = sonaCoordinator?.stats();
839
881
  const bankStats = reasoningBank?.stats();
882
+ const ruvllmStats = ruvllmCoordinator?.stats?.() || null;
883
+ // Fetch cross-module stats for unified reporting
884
+ let contrastiveTrainer = 'unavailable';
885
+ let trainingBackend = 'unavailable';
886
+ try {
887
+ // Synchronous check — contrastiveTrainer is module-level in sona-optimizer
888
+ // We read it via the SONAOptimizer singleton if available
889
+ const sonaModule = globalThis.__claudeFlowSonaStats;
890
+ if (sonaModule) {
891
+ contrastiveTrainer = sonaModule._contrastiveTrainer || 'unavailable';
892
+ }
893
+ }
894
+ catch { /* not available */ }
840
895
  return {
841
896
  sonaEnabled: !!sonaCoordinator,
842
897
  reasoningBankSize: bankStats?.size ?? 0,
@@ -844,7 +899,11 @@ export function getIntelligenceStats() {
844
899
  signalsProcessed: globalStats.signalsProcessed,
845
900
  trajectoriesRecorded: globalStats.trajectoriesRecorded,
846
901
  lastAdaptation: globalStats.lastAdaptation,
847
- avgAdaptationTime: sonaStats?.avgAdaptationMs ?? 0
902
+ avgAdaptationTime: sonaStats?.avgAdaptationMs ?? 0,
903
+ _ruvllmBackend: ruvllmStats ? 'active' : 'unavailable',
904
+ _ruvllmTrajectories: ruvllmStats?.trajectoriesBuffered || 0,
905
+ _contrastiveTrainer: contrastiveTrainer,
906
+ _trainingBackend: trainingBackend,
848
907
  };
849
908
  }
850
909
  /**
@@ -1088,6 +1147,15 @@ export async function clearAllPatterns() {
1088
1147
  export function getNeuralDataDir() {
1089
1148
  return getDataDir();
1090
1149
  }
1150
+ /**
1151
+ * Trigger background learning on the @ruvector/ruvllm SonaCoordinator.
1152
+ * No-op if ruvllm is not installed.
1153
+ */
1154
+ export async function runBackgroundLearning() {
1155
+ const coord = await loadRuvllmCoordinator();
1156
+ if (coord)
1157
+ coord.runBackgroundLoop();
1158
+ }
1091
1159
  /**
1092
1160
  * Get persistence status
1093
1161
  */
@@ -87,6 +87,11 @@ export interface SONAStats {
87
87
  qLearningEnabled: boolean;
88
88
  /** Time of last learning update */
89
89
  lastUpdate: number | null;
90
+ /** Contrastive trainer status (from @ruvector/ruvllm) */
91
+ _contrastiveTrainer?: {
92
+ triplets: number;
93
+ agents: number;
94
+ } | 'unavailable';
90
95
  }
91
96
  /**
92
97
  * SONA Optimizer for adaptive routing based on trajectory outcomes
@@ -149,6 +154,17 @@ export declare class SONAOptimizer {
149
154
  * Get optimizer statistics
150
155
  */
151
156
  getStats(): SONAStats;
157
+ /**
158
+ * Trigger contrastive training on accumulated agent embeddings.
159
+ * Returns training metrics or { trained: false } if insufficient data.
160
+ *
161
+ * @param _epochs - reserved for future use (epochs are set at ContrastiveTrainer construction)
162
+ */
163
+ trainAgentEmbeddings(_epochs?: number): Promise<{
164
+ trained: boolean;
165
+ loss?: number;
166
+ triplets?: number;
167
+ }>;
152
168
  /**
153
169
  * Apply temporal decay to pattern confidence
154
170
  * Reduces confidence of unused patterns
@@ -166,6 +182,12 @@ export declare class SONAOptimizer {
166
182
  * Import patterns (for migration or testing)
167
183
  */
168
184
  importPatterns(patterns: Record<string, LearnedPattern>): number;
185
+ /**
186
+ * Convert extracted keywords into a lightweight 384-dim embedding proxy.
187
+ * Uses a deterministic hash-scatter so each keyword set maps to a
188
+ * consistent unit-length vector compatible with ContrastiveTrainer.
189
+ */
190
+ private keywordsToEmbedding;
169
191
  /**
170
192
  * Extract meaningful keywords from task description
171
193
  */
@@ -26,6 +26,26 @@ const CONFIDENCE_INCREMENT = 0.1;
26
26
  const CONFIDENCE_DECREMENT = 0.15;
27
27
  const DECAY_RATE = 0.01; // Per day
28
28
  const MAX_PATTERNS = 1000;
29
+ // ============================================================================
30
+ // Contrastive Trainer (lazy-loaded from @ruvector/ruvllm)
31
+ // ============================================================================
32
+ let contrastiveTrainer = null;
33
+ let trainerLoaded = false;
34
+ async function loadContrastiveTrainer() {
35
+ if (trainerLoaded)
36
+ return contrastiveTrainer;
37
+ trainerLoaded = true;
38
+ try {
39
+ const { createRequire } = await import('module');
40
+ const requireCjs = createRequire(import.meta.url);
41
+ const ruvllm = requireCjs('@ruvector/ruvllm');
42
+ contrastiveTrainer = new ruvllm.ContrastiveTrainer({ batchSize: 32, margin: 0.5 });
43
+ return contrastiveTrainer;
44
+ }
45
+ catch {
46
+ return null;
47
+ }
48
+ }
29
49
  /**
30
50
  * Common agent types for routing
31
51
  */
@@ -159,6 +179,8 @@ export class SONAOptimizer {
159
179
  // Q-learning not available, continue without it
160
180
  this.qLearningEnabled = false;
161
181
  }
182
+ // Eagerly load ContrastiveTrainer so stats reflect backend status
183
+ await loadContrastiveTrainer();
162
184
  return {
163
185
  success: true,
164
186
  patternsLoaded: loaded ? this.patterns.size : 0,
@@ -218,6 +240,16 @@ export class SONAOptimizer {
218
240
  const reward = success ? 1.0 : -0.5;
219
241
  this.qLearningRouter.update(task, agent, reward);
220
242
  }
243
+ // Feed outcome into contrastive trainer for agent embedding learning (fire-and-forget)
244
+ if (success) {
245
+ loadContrastiveTrainer().then(trainer => {
246
+ if (!trainer)
247
+ return;
248
+ // Use keyword vector as a lightweight embedding proxy
249
+ const embedding = this.keywordsToEmbedding(keywords);
250
+ trainer.addAgentEmbedding(agent, embedding);
251
+ }).catch(() => { });
252
+ }
221
253
  // Persist to disk (debounced)
222
254
  this.saveToDisk();
223
255
  return {
@@ -334,8 +366,25 @@ export class SONAOptimizer {
334
366
  avgConfidence: this.patterns.size > 0 ? totalConfidence / this.patterns.size : 0,
335
367
  qLearningEnabled: this.qLearningEnabled,
336
368
  lastUpdate: this.lastUpdate,
369
+ _contrastiveTrainer: contrastiveTrainer
370
+ ? { triplets: contrastiveTrainer.getTripletCount?.() ?? 0, agents: contrastiveTrainer.getAgentEmbeddings?.()?.size ?? 0 }
371
+ : 'unavailable',
337
372
  };
338
373
  }
374
+ /**
375
+ * Trigger contrastive training on accumulated agent embeddings.
376
+ * Returns training metrics or { trained: false } if insufficient data.
377
+ *
378
+ * @param _epochs - reserved for future use (epochs are set at ContrastiveTrainer construction)
379
+ */
380
+ async trainAgentEmbeddings(_epochs = 5) {
381
+ const trainer = await loadContrastiveTrainer();
382
+ if (!trainer || (trainer.getTripletCount?.() ?? 0) < 3) {
383
+ return { trained: false };
384
+ }
385
+ const result = trainer.train();
386
+ return { trained: true, loss: result.finalLoss, triplets: result.tripletCount };
387
+ }
339
388
  /**
340
389
  * Apply temporal decay to pattern confidence
341
390
  * Reduces confidence of unused patterns
@@ -404,6 +453,33 @@ export class SONAOptimizer {
404
453
  // ============================================================================
405
454
  // Private Methods
406
455
  // ============================================================================
456
+ /**
457
+ * Convert extracted keywords into a lightweight 384-dim embedding proxy.
458
+ * Uses a deterministic hash-scatter so each keyword set maps to a
459
+ * consistent unit-length vector compatible with ContrastiveTrainer.
460
+ */
461
+ keywordsToEmbedding(keywords) {
462
+ const dim = 384;
463
+ const vec = new Float32Array(dim);
464
+ for (const kw of keywords) {
465
+ // Simple FNV-1a-like hash per character to scatter energy across dims
466
+ let h = 0x811c9dc5;
467
+ for (let i = 0; i < kw.length; i++) {
468
+ h ^= kw.charCodeAt(i);
469
+ h = Math.imul(h, 0x01000193);
470
+ }
471
+ const idx = Math.abs(h) % dim;
472
+ vec[idx] += (h & 1) ? 1 : -1;
473
+ }
474
+ // L2-normalize
475
+ let norm = 0;
476
+ for (let i = 0; i < dim; i++)
477
+ norm += vec[i] * vec[i];
478
+ norm = Math.sqrt(norm) || 1;
479
+ for (let i = 0; i < dim; i++)
480
+ vec[i] /= norm;
481
+ return vec;
482
+ }
407
483
  /**
408
484
  * Extract meaningful keywords from task description
409
485
  */
@@ -0,0 +1,79 @@
1
+ /**
2
+ * @ruvector/graph-node native graph database backend (ADR-087)
3
+ *
4
+ * Provides persistent graph storage for agent relationships, causal edges,
5
+ * task dependencies, and swarm topology using native Rust bindings.
6
+ *
7
+ * API requirements discovered via testing:
8
+ * - createNode: requires { id, type, embedding }
9
+ * - createEdge: requires { from, to, label, description, embedding, properties }
10
+ * - createHyperedge: requires { nodes[], label, description, embedding, properties }
11
+ * - kHopNeighbors(nodeId, k): returns string[] of node IDs
12
+ * - stats(): returns { totalNodes, totalEdges, avgDegree }
13
+ */
14
+ export interface GraphNodeData {
15
+ id: string;
16
+ type: string;
17
+ name?: string;
18
+ properties?: Record<string, unknown>;
19
+ }
20
+ export interface GraphEdgeData {
21
+ from: string;
22
+ to: string;
23
+ label: string;
24
+ description?: string;
25
+ weight?: number;
26
+ properties?: Record<string, unknown>;
27
+ }
28
+ export interface GraphStats {
29
+ totalNodes: number;
30
+ totalEdges: number;
31
+ avgDegree: number;
32
+ backend: 'graph-node' | 'unavailable';
33
+ }
34
+ /**
35
+ * Check if graph-node backend is available
36
+ */
37
+ export declare function isGraphBackendAvailable(): Promise<boolean>;
38
+ /**
39
+ * Add a node to the graph (agent, task, pattern, etc.)
40
+ */
41
+ export declare function addNode(data: GraphNodeData): Promise<string | null>;
42
+ /**
43
+ * Add an edge between two nodes
44
+ */
45
+ export declare function addEdge(data: GraphEdgeData): Promise<string | null>;
46
+ /**
47
+ * Create a hyperedge connecting multiple nodes (e.g., swarm teams)
48
+ */
49
+ export declare function addHyperedge(nodeIds: string[], label: string, description?: string, properties?: Record<string, unknown>): Promise<string | null>;
50
+ /**
51
+ * Get k-hop neighbors of a node
52
+ */
53
+ export declare function getNeighbors(nodeId: string, hops?: number): Promise<string[]>;
54
+ /**
55
+ * Get graph statistics
56
+ */
57
+ export declare function getGraphStats(): Promise<GraphStats>;
58
+ /**
59
+ * Record a causal edge (used by agentdb_causal-edge MCP tool)
60
+ */
61
+ export declare function recordCausalEdge(sourceId: string, targetId: string, relation: string, weight?: number): Promise<{
62
+ success: boolean;
63
+ edgeId?: string;
64
+ backend: string;
65
+ }>;
66
+ /**
67
+ * Record agent collaboration (used by swarm coordination)
68
+ */
69
+ export declare function recordCollaboration(agentId: string, agentType: string, taskId: string, taskName: string): Promise<{
70
+ success: boolean;
71
+ }>;
72
+ /**
73
+ * Record swarm team as a hyperedge
74
+ */
75
+ export declare function recordSwarmTeam(agentIds: string[], topology: string, taskDescription?: string): Promise<{
76
+ success: boolean;
77
+ hyperedgeId?: string;
78
+ }>;
79
+ //# sourceMappingURL=graph-backend.d.ts.map
@@ -0,0 +1,220 @@
1
+ /**
2
+ * @ruvector/graph-node native graph database backend (ADR-087)
3
+ *
4
+ * Provides persistent graph storage for agent relationships, causal edges,
5
+ * task dependencies, and swarm topology using native Rust bindings.
6
+ *
7
+ * API requirements discovered via testing:
8
+ * - createNode: requires { id, type, embedding }
9
+ * - createEdge: requires { from, to, label, description, embedding, properties }
10
+ * - createHyperedge: requires { nodes[], label, description, embedding, properties }
11
+ * - kHopNeighbors(nodeId, k): returns string[] of node IDs
12
+ * - stats(): returns { totalNodes, totalEdges, avgDegree }
13
+ */
14
+ import { join } from 'path';
15
+ // Lazy-loaded graph-node module
16
+ let graphNodeModule = null;
17
+ let graphDb = null;
18
+ let graphBackendLoaded = false;
19
+ let graphBackendAvailable = false;
20
+ const DEFAULT_EMBEDDING_DIM = 8; // Minimal embedding for graph structure
21
+ /**
22
+ * Load @ruvector/graph-node via createRequire (CJS package)
23
+ */
24
+ async function loadGraphNode() {
25
+ if (graphBackendLoaded)
26
+ return graphNodeModule;
27
+ graphBackendLoaded = true;
28
+ try {
29
+ const { createRequire } = await import('module');
30
+ const requireCjs = createRequire(import.meta.url);
31
+ graphNodeModule = requireCjs('@ruvector/graph-node');
32
+ graphBackendAvailable = true;
33
+ return graphNodeModule;
34
+ }
35
+ catch {
36
+ graphBackendAvailable = false;
37
+ return null;
38
+ }
39
+ }
40
+ /**
41
+ * Get or create the singleton graph database instance
42
+ */
43
+ async function getGraphDb() {
44
+ if (graphDb)
45
+ return graphDb;
46
+ const mod = await loadGraphNode();
47
+ if (!mod)
48
+ return null;
49
+ // Use persistent path if available, otherwise in-memory
50
+ const dataDir = join(process.cwd(), '.claude-flow', 'graph');
51
+ try {
52
+ const fs = await import('fs');
53
+ fs.mkdirSync(dataDir, { recursive: true });
54
+ graphDb = new mod.GraphDatabase(join(dataDir, 'agents.db'));
55
+ }
56
+ catch {
57
+ // Fallback to in-memory
58
+ graphDb = new mod.GraphDatabase();
59
+ }
60
+ return graphDb;
61
+ }
62
+ /**
63
+ * Create a minimal embedding for non-vector graph operations.
64
+ * Uses a deterministic hash of the string content.
65
+ */
66
+ function textToMiniEmbedding(text) {
67
+ const emb = new Float32Array(DEFAULT_EMBEDDING_DIM);
68
+ for (let i = 0; i < text.length; i++) {
69
+ emb[i % DEFAULT_EMBEDDING_DIM] += text.charCodeAt(i) / 256;
70
+ }
71
+ // Normalize
72
+ let norm = 0;
73
+ for (let i = 0; i < emb.length; i++)
74
+ norm += emb[i] * emb[i];
75
+ norm = Math.sqrt(norm) || 1;
76
+ for (let i = 0; i < emb.length; i++)
77
+ emb[i] /= norm;
78
+ return emb;
79
+ }
80
+ /**
81
+ * Check if graph-node backend is available
82
+ */
83
+ export async function isGraphBackendAvailable() {
84
+ await loadGraphNode();
85
+ return graphBackendAvailable;
86
+ }
87
+ /**
88
+ * Add a node to the graph (agent, task, pattern, etc.)
89
+ */
90
+ export async function addNode(data) {
91
+ const db = await getGraphDb();
92
+ if (!db)
93
+ return null;
94
+ try {
95
+ const embedding = textToMiniEmbedding(`${data.type}:${data.name || data.id}`);
96
+ return await db.createNode({
97
+ id: data.id,
98
+ type: data.type,
99
+ ...data.properties,
100
+ embedding,
101
+ });
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ }
107
+ /**
108
+ * Add an edge between two nodes
109
+ */
110
+ export async function addEdge(data) {
111
+ const db = await getGraphDb();
112
+ if (!db)
113
+ return null;
114
+ try {
115
+ const embedding = textToMiniEmbedding(`${data.label}:${data.from}->${data.to}`);
116
+ return await db.createEdge({
117
+ from: data.from,
118
+ to: data.to,
119
+ label: data.label,
120
+ description: data.description || data.label,
121
+ embedding,
122
+ properties: { weight: data.weight ?? 1.0, ...data.properties },
123
+ });
124
+ }
125
+ catch {
126
+ return null;
127
+ }
128
+ }
129
+ /**
130
+ * Create a hyperedge connecting multiple nodes (e.g., swarm teams)
131
+ */
132
+ export async function addHyperedge(nodeIds, label, description, properties) {
133
+ const db = await getGraphDb();
134
+ if (!db)
135
+ return null;
136
+ try {
137
+ const embedding = textToMiniEmbedding(`${label}:${nodeIds.join(',')}`);
138
+ return await db.createHyperedge({
139
+ nodes: nodeIds,
140
+ label,
141
+ description: description || label,
142
+ embedding,
143
+ properties: properties || {},
144
+ });
145
+ }
146
+ catch {
147
+ return null;
148
+ }
149
+ }
150
+ /**
151
+ * Get k-hop neighbors of a node
152
+ */
153
+ export async function getNeighbors(nodeId, hops = 2) {
154
+ const db = await getGraphDb();
155
+ if (!db)
156
+ return [];
157
+ try {
158
+ return await db.kHopNeighbors(nodeId, hops);
159
+ }
160
+ catch {
161
+ return [];
162
+ }
163
+ }
164
+ /**
165
+ * Get graph statistics
166
+ */
167
+ export async function getGraphStats() {
168
+ const db = await getGraphDb();
169
+ if (!db)
170
+ return { totalNodes: 0, totalEdges: 0, avgDegree: 0, backend: 'unavailable' };
171
+ try {
172
+ const stats = await db.stats();
173
+ return { ...stats, backend: 'graph-node' };
174
+ }
175
+ catch {
176
+ return { totalNodes: 0, totalEdges: 0, avgDegree: 0, backend: 'unavailable' };
177
+ }
178
+ }
179
+ /**
180
+ * Record a causal edge (used by agentdb_causal-edge MCP tool)
181
+ */
182
+ export async function recordCausalEdge(sourceId, targetId, relation, weight) {
183
+ // Ensure both nodes exist
184
+ await addNode({ id: sourceId, type: 'memory-entry' });
185
+ await addNode({ id: targetId, type: 'memory-entry' });
186
+ const edgeId = await addEdge({
187
+ from: sourceId,
188
+ to: targetId,
189
+ label: relation,
190
+ description: `${relation}: ${sourceId} -> ${targetId}`,
191
+ weight,
192
+ });
193
+ return {
194
+ success: edgeId !== null,
195
+ edgeId: edgeId ?? undefined,
196
+ backend: graphBackendAvailable ? 'graph-node' : 'unavailable',
197
+ };
198
+ }
199
+ /**
200
+ * Record agent collaboration (used by swarm coordination)
201
+ */
202
+ export async function recordCollaboration(agentId, agentType, taskId, taskName) {
203
+ await addNode({ id: agentId, type: 'agent', name: agentType });
204
+ await addNode({ id: taskId, type: 'task', name: taskName });
205
+ const edgeId = await addEdge({
206
+ from: agentId,
207
+ to: taskId,
208
+ label: 'assigned_to',
209
+ description: `${agentType} works on ${taskName}`,
210
+ });
211
+ return { success: edgeId !== null };
212
+ }
213
+ /**
214
+ * Record swarm team as a hyperedge
215
+ */
216
+ export async function recordSwarmTeam(agentIds, topology, taskDescription) {
217
+ const heId = await addHyperedge(agentIds, 'swarm-team', taskDescription || `${topology} swarm with ${agentIds.length} agents`, { topology, agentCount: agentIds.length });
218
+ return { success: heId !== null, hyperedgeId: heId ?? undefined };
219
+ }
220
+ //# sourceMappingURL=graph-backend.js.map
@@ -94,6 +94,8 @@ export interface LoRAStats {
94
94
  avgAdaptationNorm: number;
95
95
  /** Last update timestamp */
96
96
  lastUpdate: number | null;
97
+ /** Training backend in use ('ruvllm' | 'js-fallback') */
98
+ _trainingBackend?: string;
97
99
  }
98
100
  /**
99
101
  * Low-Rank Adaptation module for efficient embedding fine-tuning
@@ -107,6 +109,16 @@ export declare class LoRAAdapter {
107
109
  private lastUpdate;
108
110
  private updatesSinceLastSave;
109
111
  constructor(config?: Partial<LoRAConfig>);
112
+ /**
113
+ * Eagerly load the ruvllm TrainingPipeline backend.
114
+ * Called automatically during first checkpoint operation,
115
+ * or call explicitly to make getStats() report backend status.
116
+ */
117
+ initBackend(): Promise<void>;
118
+ /**
119
+ * Get a readonly copy of the adapter configuration
120
+ */
121
+ getConfig(): Readonly<LoRAConfig>;
110
122
  /**
111
123
  * Initialize weights with Kaiming/He initialization
112
124
  */
@@ -156,6 +168,16 @@ export declare class LoRAAdapter {
156
168
  * Load weights from disk
157
169
  */
158
170
  loadWeights(): boolean;
171
+ /**
172
+ * Save a training checkpoint via ruvllm TrainingPipeline.
173
+ * Falls back to writing our own weight JSON if ruvllm is unavailable.
174
+ */
175
+ saveCheckpoint(path: string): Promise<boolean>;
176
+ /**
177
+ * Load a training checkpoint via ruvllm TrainingPipeline.
178
+ * Falls back to reading our own weight JSON if ruvllm is unavailable.
179
+ */
180
+ loadCheckpoint(path: string): Promise<boolean>;
159
181
  /**
160
182
  * Export weights as JSON
161
183
  */
@@ -53,6 +53,33 @@ const DEFAULT_CONFIG = {
53
53
  autoSaveInterval: 50,
54
54
  };
55
55
  // ============================================================================
56
+ // @ruvector/ruvllm TrainingPipeline (lazy-loaded)
57
+ // ============================================================================
58
+ let ruvllmPipeline = null;
59
+ let pipelineLoaded = false;
60
+ async function loadTrainingPipeline(adapter) {
61
+ if (pipelineLoaded)
62
+ return ruvllmPipeline;
63
+ pipelineLoaded = true;
64
+ try {
65
+ const { createRequire } = await import('module');
66
+ const requireCjs = createRequire(import.meta.url);
67
+ const ruvllm = requireCjs('@ruvector/ruvllm');
68
+ const config = adapter.getConfig();
69
+ // Create a ruvllm LoraAdapter that mirrors our JS adapter's config
70
+ const nativeAdapter = new ruvllm.LoraAdapter(config.inputDim, config.outputDim, config.rank);
71
+ ruvllmPipeline = new ruvllm.TrainingPipeline({
72
+ adapter: nativeAdapter,
73
+ learningRate: 0.001,
74
+ batchSize: 8,
75
+ });
76
+ return ruvllmPipeline;
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ // ============================================================================
56
83
  // LoRA Adapter Class
57
84
  // ============================================================================
58
85
  /**
@@ -70,6 +97,20 @@ export class LoRAAdapter {
70
97
  this.config = { ...DEFAULT_CONFIG, ...config };
71
98
  this.weights = this.initializeWeights();
72
99
  }
100
+ /**
101
+ * Eagerly load the ruvllm TrainingPipeline backend.
102
+ * Called automatically during first checkpoint operation,
103
+ * or call explicitly to make getStats() report backend status.
104
+ */
105
+ async initBackend() {
106
+ await loadTrainingPipeline(this);
107
+ }
108
+ /**
109
+ * Get a readonly copy of the adapter configuration
110
+ */
111
+ getConfig() {
112
+ return this.config;
113
+ }
73
114
  /**
74
115
  * Initialize weights with Kaiming/He initialization
75
116
  */
@@ -254,6 +295,9 @@ export class LoRAAdapter {
254
295
  ? this.adaptationNormSum / this.totalAdaptations
255
296
  : 0,
256
297
  lastUpdate: this.lastUpdate,
298
+ _trainingBackend: pipelineLoaded
299
+ ? (ruvllmPipeline ? 'ruvllm' : 'js-fallback')
300
+ : 'js-fallback',
257
301
  };
258
302
  }
259
303
  /**
@@ -341,6 +385,57 @@ export class LoRAAdapter {
341
385
  return false;
342
386
  }
343
387
  }
388
+ /**
389
+ * Save a training checkpoint via ruvllm TrainingPipeline.
390
+ * Falls back to writing our own weight JSON if ruvllm is unavailable.
391
+ */
392
+ async saveCheckpoint(path) {
393
+ const pipeline = await loadTrainingPipeline(this);
394
+ if (pipeline) {
395
+ try {
396
+ pipeline.saveCheckpoint(path);
397
+ // Verify ruvllm actually wrote the file (some versions are no-op)
398
+ const fs = await import('fs');
399
+ if (fs.existsSync(path))
400
+ return true;
401
+ }
402
+ catch { /* fall through to JS fallback */ }
403
+ }
404
+ // Fallback: save our own weights as JSON
405
+ try {
406
+ const fs = await import('fs');
407
+ const data = JSON.stringify(this.exportWeights());
408
+ fs.writeFileSync(path, data);
409
+ return true;
410
+ }
411
+ catch {
412
+ return false;
413
+ }
414
+ }
415
+ /**
416
+ * Load a training checkpoint via ruvllm TrainingPipeline.
417
+ * Falls back to reading our own weight JSON if ruvllm is unavailable.
418
+ */
419
+ async loadCheckpoint(path) {
420
+ const pipeline = await loadTrainingPipeline(this);
421
+ if (pipeline) {
422
+ try {
423
+ pipeline.loadCheckpoint(path);
424
+ return true;
425
+ }
426
+ catch { /* fall through to JS fallback */ }
427
+ }
428
+ // Fallback: load from our JSON format
429
+ try {
430
+ const fs = await import('fs');
431
+ if (fs.existsSync(path)) {
432
+ const data = JSON.parse(fs.readFileSync(path, 'utf-8'));
433
+ return this.importWeights(data);
434
+ }
435
+ }
436
+ catch { /* return false below */ }
437
+ return false;
438
+ }
344
439
  /**
345
440
  * Export weights as JSON
346
441
  */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.5.73",
3
+ "version": "3.5.75",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",