@timmeck/brain-core 2.36.66 → 2.36.68

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.
@@ -89,6 +89,7 @@ export class ResearchOrchestrator {
89
89
  runtimeInfluenceTracker = null;
90
90
  loopDetector = null;
91
91
  governanceLayer = null;
92
+ adaptiveScheduler = null;
92
93
  lastAutoMissionTime = 0;
93
94
  lastGoalMissionTime = 0;
94
95
  roadmapBootstrapped = false;
@@ -139,6 +140,14 @@ export class ResearchOrchestrator {
139
140
  setOnSuggestion(callback) {
140
141
  this.onSuggestionCallback = callback;
141
142
  }
143
+ /** Set the AdaptiveScheduler for dynamic cycle intervals. */
144
+ setAdaptiveScheduler(scheduler) {
145
+ this.adaptiveScheduler = scheduler;
146
+ }
147
+ /** Get the AdaptiveScheduler instance. */
148
+ getAdaptiveScheduler() {
149
+ return this.adaptiveScheduler;
150
+ }
142
151
  /** Set the DataMiner instance for DB-driven engine feeding. */
143
152
  setDataMiner(miner) {
144
153
  this.dataMiner = miner;
@@ -326,20 +335,31 @@ export class ResearchOrchestrator {
326
335
  start(intervalMs = 300_000) {
327
336
  if (this.feedbackTimer)
328
337
  return;
329
- this.feedbackTimer = setInterval(() => {
338
+ this.startIntervalMs = intervalMs;
339
+ this.scheduleNextCycle(intervalMs);
340
+ this.log.info(`[orchestrator] Research orchestrator started (feedback every ${intervalMs}ms)`);
341
+ }
342
+ /** Schedule the next feedback cycle (supports adaptive intervals). */
343
+ scheduleNextCycle(intervalMs) {
344
+ if (this.feedbackTimer)
345
+ clearTimeout(this.feedbackTimer);
346
+ this.feedbackTimer = setTimeout(async () => {
330
347
  try {
331
- void this.runFeedbackCycle();
348
+ await this.runFeedbackCycle();
332
349
  }
333
350
  catch (err) {
334
351
  this.log.error('[orchestrator] Feedback cycle error', { error: err.message });
335
352
  }
353
+ // Re-schedule with adaptive or base interval
354
+ const nextInterval = this.adaptiveScheduler?.getNextInterval() ?? this.startIntervalMs;
355
+ this.scheduleNextCycle(nextInterval);
336
356
  }, intervalMs);
337
- this.log.info(`[orchestrator] Research orchestrator started (feedback every ${intervalMs}ms)`);
338
357
  }
358
+ startIntervalMs = 300_000;
339
359
  /** Stop the feedback loop. */
340
360
  stop() {
341
361
  if (this.feedbackTimer) {
342
- clearInterval(this.feedbackTimer);
362
+ clearTimeout(this.feedbackTimer);
343
363
  this.feedbackTimer = null;
344
364
  }
345
365
  this.dreamEngine?.stop();
@@ -514,6 +534,22 @@ export class ResearchOrchestrator {
514
534
  this.hypothesisEngine.observe({ source: this.brainName, type: `anomaly:${a.metric}`, value: a.deviation, timestamp: now, metadata: { severity: a.severity } });
515
535
  }
516
536
  }
537
+ // 2d. Prediction accuracy as standard observation — feeds HypothesisEngine + PredictionEngine self-accuracy
538
+ if (this.predictionEngine) {
539
+ try {
540
+ const accByDomain = this.predictionEngine.getAccuracy();
541
+ const overall = accByDomain.length > 0
542
+ ? accByDomain.reduce((s, a) => s + a.accuracy_rate, 0) / accByDomain.length
543
+ : 0;
544
+ this.hypothesisEngine.observe({ source: this.brainName, type: 'prediction_accuracy_rate', value: overall, timestamp: now });
545
+ for (const acc of accByDomain) {
546
+ this.hypothesisEngine.observe({ source: this.brainName, type: `prediction_accuracy:${acc.domain}`, value: acc.accuracy_rate, timestamp: now, metadata: { total: acc.total, correct: acc.correct } });
547
+ }
548
+ // Brain predicts its own accuracy
549
+ this.predictionEngine.recordMetric('self_accuracy_rate', overall, 'metric');
550
+ }
551
+ catch { /* prediction accuracy non-critical */ }
552
+ }
517
553
  // 2e. Accumulate evidence for hypotheses based on cycle observations
518
554
  {
519
555
  const pendingHypotheses = this.hypothesisEngine.list('proposed', 50)
@@ -1210,6 +1246,33 @@ export class ResearchOrchestrator {
1210
1246
  this.predictionEngine.recordMetric(`gap_score:${gap.topic}`, gap.gapScore, 'metric');
1211
1247
  }
1212
1248
  }
1249
+ // Internal domain observations: feed concrete metrics for gap topics
1250
+ for (const gap of gaps.slice(0, 5)) {
1251
+ const topic = gap.topic.toLowerCase();
1252
+ try {
1253
+ if (topic.includes('prediction')) {
1254
+ // Feed per-domain prediction accuracy into HypothesisEngine
1255
+ const accByDomain = this.predictionEngine?.getAccuracy() ?? [];
1256
+ for (const acc of accByDomain) {
1257
+ this.hypothesisEngine.observe({ source: this.brainName, type: `internal:prediction_accuracy:${acc.domain}`, value: acc.accuracy_rate, timestamp: now, metadata: { total: acc.total, correct: acc.correct } });
1258
+ }
1259
+ }
1260
+ else if (topic.includes('anomaly')) {
1261
+ const anomalyCount = this.anomalyDetective.getAnomalies(undefined, 100).length;
1262
+ this.hypothesisEngine.observe({ source: this.brainName, type: 'internal:anomaly_detection_count', value: anomalyCount, timestamp: now });
1263
+ }
1264
+ else if (topic.includes('distill') || topic.includes('knowledge')) {
1265
+ const kSummary = this.knowledgeDistiller.getSummary();
1266
+ this.hypothesisEngine.observe({ source: this.brainName, type: 'internal:distillation_throughput', value: kSummary.principles + kSummary.antiPatterns + kSummary.strategies, timestamp: now, metadata: { principles: kSummary.principles, avgConfidence: kSummary.avgConfidence } });
1267
+ }
1268
+ else if (topic.includes('dream') || topic.includes('consolidat')) {
1269
+ const dStatus = this.dreamEngine?.getStatus();
1270
+ const totals = dStatus?.totals;
1271
+ this.hypothesisEngine.observe({ source: this.brainName, type: 'internal:dream_consolidation_count', value: (totals?.memoriesConsolidated ?? 0), timestamp: now });
1272
+ }
1273
+ }
1274
+ catch { /* internal observation non-critical */ }
1275
+ }
1213
1276
  // Journal dark zones (truly unknown territory)
1214
1277
  for (const gap of gaps.filter(g => g.gapType === 'dark_zone').slice(0, 2)) {
1215
1278
  this.journal.write({
@@ -1442,10 +1505,18 @@ export class ResearchOrchestrator {
1442
1505
  if (this.parameterRegistry && this.predictionEngine) {
1443
1506
  const alpha = this.parameterRegistry.get('prediction', 'ewmaAlpha');
1444
1507
  const beta = this.parameterRegistry.get('prediction', 'trendBeta');
1445
- if (alpha !== undefined || beta !== undefined) {
1508
+ const minConf = this.parameterRegistry.get('prediction', 'minConfidence');
1509
+ const minDP = this.parameterRegistry.get('prediction', 'minDataPoints');
1510
+ const maxPred = this.parameterRegistry.get('prediction', 'maxPredictionsPerCycle');
1511
+ const horizon = this.parameterRegistry.get('prediction', 'defaultHorizonMs');
1512
+ if (alpha !== undefined || beta !== undefined || minConf !== undefined || minDP !== undefined || maxPred !== undefined || horizon !== undefined) {
1446
1513
  this.predictionEngine.updateConfig({
1447
1514
  ...(alpha !== undefined ? { ewmaAlpha: alpha } : {}),
1448
1515
  ...(beta !== undefined ? { trendBeta: beta } : {}),
1516
+ ...(minConf !== undefined ? { minConfidence: minConf } : {}),
1517
+ ...(minDP !== undefined ? { minDataPoints: minDP } : {}),
1518
+ ...(maxPred !== undefined ? { maxPredictionsPerCycle: maxPred } : {}),
1519
+ ...(horizon !== undefined ? { defaultHorizonMs: horizon } : {}),
1449
1520
  });
1450
1521
  }
1451
1522
  }
@@ -2888,6 +2959,15 @@ export class ResearchOrchestrator {
2888
2959
  }
2889
2960
  catch { /* checkpoint save should never break the cycle */ }
2890
2961
  }
2962
+ // Adaptive Scheduling: record cycle outcome for interval optimization
2963
+ if (this.adaptiveScheduler) {
2964
+ this.adaptiveScheduler.recordOutcome({
2965
+ insightsFound: insights.length,
2966
+ rulesLearned: 0, // rules come from external learning engines
2967
+ anomaliesDetected: anomalies.length,
2968
+ durationMs: duration,
2969
+ });
2970
+ }
2891
2971
  // Step-profiling summary: log slow steps if any
2892
2972
  if (stepTimings.length > 0) {
2893
2973
  this.log.warn(`[orchestrator] Cycle #${this.cycleCount} slow steps: ${stepTimings.map(s => `${s.step}(${s.ms}ms)`).join(', ')}`);