@kernel.chat/kbot 3.95.0 → 3.97.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.
@@ -0,0 +1,839 @@
1
+ // kbot Intelligence Coordinator — Unified Brain
2
+ //
3
+ // Orchestrates kbot's 14 intelligence systems into a coherent cognitive loop.
4
+ // Called before, during, and after each agent interaction.
5
+ //
6
+ // All module imports are LAZY (dynamic) to avoid circular dependency issues.
7
+ // State persists to ~/.kbot/coordinator-state.json.
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
11
+ import chalk from 'chalk';
12
+ // ── Lazy module loaders (dynamic imports to break circular deps) ──
13
+ async function getLearning() { return import('./learning.js'); }
14
+ async function getConfidence() { return import('./confidence.js'); }
15
+ async function getReasoning() { return import('./reasoning.js'); }
16
+ async function getGraphMemory() { return import('./graph-memory.js'); }
17
+ async function getLearnedRouter() { return import('./learned-router.js'); }
18
+ async function getIntentionality() { return import('./intentionality.js'); }
19
+ async function getTemporal() { return import('./temporal.js'); }
20
+ async function getBehaviour() { return import('./behaviour.js'); }
21
+ async function getEmergent() {
22
+ try {
23
+ // emergent.ts doesn't exist yet — will be created when emergent module is built
24
+ // Using indirect import to prevent TypeScript from resolving at compile time
25
+ const path = './tools/emergent' + '.js';
26
+ return await import(/* @vite-ignore */ path);
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ // ── Defaults ──
33
+ const DEFAULT_CONFIDENCE_THRESHOLD = 0.4;
34
+ const CONSOLIDATION_INTERVAL = 10; // every N interactions
35
+ const MAX_EVAL_HISTORY = 200;
36
+ const MAX_INSIGHTS = 100;
37
+ const MAX_CONFLICTS = 50;
38
+ const MAX_GOALS = 20;
39
+ function defaultState() {
40
+ return {
41
+ lastPolicy: 'balanced',
42
+ confidenceThreshold: DEFAULT_CONFIDENCE_THRESHOLD,
43
+ totalInteractions: 0,
44
+ successRate: 0.5,
45
+ activeGoals: [],
46
+ recentInsights: [],
47
+ conflictLog: [],
48
+ evalHistory: [],
49
+ patternsLearnedToday: 0,
50
+ patternsLearnedDate: new Date().toISOString().slice(0, 10),
51
+ routingAccuracy: 0.5,
52
+ lastConsolidation: null,
53
+ startedAt: new Date().toISOString(),
54
+ };
55
+ }
56
+ // ── Persistence helpers ──
57
+ const KBOT_DIR = join(homedir(), '.kbot');
58
+ const STATE_FILE = join(KBOT_DIR, 'coordinator-state.json');
59
+ function ensureDir() {
60
+ if (!existsSync(KBOT_DIR))
61
+ mkdirSync(KBOT_DIR, { recursive: true });
62
+ }
63
+ function loadState() {
64
+ ensureDir();
65
+ if (!existsSync(STATE_FILE))
66
+ return defaultState();
67
+ try {
68
+ return { ...defaultState(), ...JSON.parse(readFileSync(STATE_FILE, 'utf-8')) };
69
+ }
70
+ catch {
71
+ return defaultState();
72
+ }
73
+ }
74
+ function saveState(state) {
75
+ ensureDir();
76
+ try {
77
+ writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf-8');
78
+ }
79
+ catch {
80
+ // best-effort — coordinator state can be regenerated
81
+ }
82
+ }
83
+ // ── Utility ──
84
+ function shortHash(s) {
85
+ let hash = 0;
86
+ for (let i = 0; i < s.length; i++) {
87
+ hash = ((hash << 5) - hash + s.charCodeAt(i)) | 0;
88
+ }
89
+ return Math.abs(hash).toString(36).slice(0, 8);
90
+ }
91
+ function shortId() {
92
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
93
+ }
94
+ function todayStr() {
95
+ return new Date().toISOString().slice(0, 10);
96
+ }
97
+ // ── IntelligenceCoordinator ──
98
+ export class IntelligenceCoordinator {
99
+ state;
100
+ anticipatedTools = [];
101
+ currentSessionId = null;
102
+ pendingRouteAgent = null;
103
+ initTime = Date.now();
104
+ constructor() {
105
+ this.state = loadState();
106
+ // Reset daily pattern counter if it's a new day
107
+ if (this.state.patternsLearnedDate !== todayStr()) {
108
+ this.state.patternsLearnedToday = 0;
109
+ this.state.patternsLearnedDate = todayStr();
110
+ }
111
+ }
112
+ // ── Phase 1: Pre-Execution (before LLM API call) ──
113
+ async preProcess(message, sessionId) {
114
+ this.currentSessionId = sessionId;
115
+ this.state.totalInteractions++;
116
+ const result = {
117
+ agent: null,
118
+ confidence: 0.5,
119
+ graphContext: '',
120
+ reasoning: '',
121
+ toolHints: [],
122
+ systemPromptAddition: '',
123
+ needsClarification: false,
124
+ drives: null,
125
+ anticipation: null,
126
+ };
127
+ // 1. Route message through learned-router
128
+ try {
129
+ const router = await getLearnedRouter();
130
+ const route = router.learnedRoute(message);
131
+ if (route) {
132
+ result.agent = route.agent;
133
+ result.confidence = route.confidence;
134
+ this.pendingRouteAgent = route.agent;
135
+ }
136
+ }
137
+ catch { /* non-critical */ }
138
+ // 2. Check confidence — if below threshold, flag for clarification
139
+ try {
140
+ const conf = await getConfidence();
141
+ const score = conf.estimateConfidence(message, '');
142
+ result.confidence = Math.max(result.confidence, score.overall);
143
+ if (score.overall < this.state.confidenceThreshold) {
144
+ result.needsClarification = true;
145
+ result.clarificationReason = score.reasoning || 'Low confidence — consider asking for more details';
146
+ }
147
+ }
148
+ catch { /* non-critical */ }
149
+ // 3. Query graph-memory for relevant context
150
+ try {
151
+ const graph = await getGraphMemory();
152
+ const contextStr = graph.toContext(1500);
153
+ if (contextStr && contextStr.length > 10) {
154
+ result.graphContext = contextStr;
155
+ }
156
+ // Also find nodes related to message keywords
157
+ const found = graph.findNode(message.slice(0, 100));
158
+ if (found.length > 0) {
159
+ const entityNames = found.slice(0, 5).map(n => n.name).join(', ');
160
+ result.graphContext += `\nRelated entities: ${entityNames}`;
161
+ }
162
+ }
163
+ catch { /* non-critical */ }
164
+ // 4. Run abductive reasoning to infer intent
165
+ try {
166
+ const reasoning = await getReasoning();
167
+ const strategy = reasoning.selectStrategy(message, '');
168
+ result.reasoning = strategy.reasoning || strategy.chosenStrategy;
169
+ }
170
+ catch { /* non-critical */ }
171
+ // 5. Check intentionality drives
172
+ try {
173
+ const intent = await getIntentionality();
174
+ const drives = intent.getDriveState();
175
+ if (drives && drives.drives && drives.drives.length > 0) {
176
+ const top = drives.drives.reduce((a, b) => (b.weight * (1 - b.currentSatisfaction)) > (a.weight * (1 - a.currentSatisfaction)) ? b : a);
177
+ result.drives = { dominant: top.name, level: top.weight };
178
+ }
179
+ }
180
+ catch { /* non-critical */ }
181
+ // 6. Anticipate what tools will be needed (temporal)
182
+ try {
183
+ const temporal = await getTemporal();
184
+ const anticipated = temporal.anticipateNext([message], message);
185
+ if (anticipated && anticipated.length > 0 && anticipated[0].prediction) {
186
+ result.anticipation = anticipated[0].prediction;
187
+ result.toolHints = anticipated[0].preparation || [];
188
+ this.anticipatedTools = result.toolHints;
189
+ }
190
+ }
191
+ catch { /* non-critical */ }
192
+ // 7. Load behaviour rules for system prompt
193
+ try {
194
+ const behaviour = await getBehaviour();
195
+ const prompt = behaviour.getBehaviourPrompt();
196
+ if (prompt && prompt.length > 5) {
197
+ result.systemPromptAddition = prompt;
198
+ }
199
+ }
200
+ catch { /* non-critical */ }
201
+ // 8. Synthesize policy from signals
202
+ this.state.lastPolicy = this.synthesizePolicy(result.confidence);
203
+ // Add policy hint to system prompt
204
+ if (this.state.lastPolicy === 'explore') {
205
+ result.systemPromptAddition += '\n\nNote: Consider unconventional approaches. The user may benefit from a different perspective.';
206
+ }
207
+ else if (this.state.lastPolicy === 'exploit') {
208
+ result.systemPromptAddition += '\n\nNote: Use the most reliable, proven approach. The user needs a direct solution.';
209
+ }
210
+ // Trim empty lines from system prompt addition
211
+ result.systemPromptAddition = result.systemPromptAddition.trim();
212
+ this.save();
213
+ return result;
214
+ }
215
+ // ── Phase 2: Tool Oversight (before each tool execution) ──
216
+ evaluateToolCall(toolName, args, _context) {
217
+ const evaluation = {
218
+ allow: true,
219
+ anticipated: false,
220
+ };
221
+ // 1. Check if tool matches anticipated needs
222
+ if (this.anticipatedTools.length > 0) {
223
+ evaluation.anticipated = this.anticipatedTools.some(hint => toolName.includes(hint) || hint.includes(toolName));
224
+ }
225
+ // 2. Confidence-gate: warn if success rate is low for this tool pattern
226
+ // We check the eval history for similar tool usage patterns
227
+ const recentEvals = this.state.evalHistory.slice(-20);
228
+ if (recentEvals.length >= 5) {
229
+ const avgScore = recentEvals.reduce((s, e) => s + e.score, 0) / recentEvals.length;
230
+ if (avgScore < 0.3) {
231
+ evaluation.warn = `Recent interaction quality is low (${(avgScore * 100).toFixed(0)}%). Consider verifying approach.`;
232
+ }
233
+ }
234
+ // 3. Check behaviour rules for restrictions (synchronous)
235
+ // We do a lightweight check against known restricted patterns
236
+ try {
237
+ // Destructive tool patterns that should trigger caution
238
+ const cautionPatterns = [
239
+ 'delete', 'remove', 'drop', 'destroy', 'force', 'reset',
240
+ 'truncate', 'wipe', 'purge', 'nuke',
241
+ ];
242
+ const toolLower = toolName.toLowerCase();
243
+ const argsStr = JSON.stringify(args).toLowerCase();
244
+ const isDestructive = cautionPatterns.some(p => toolLower.includes(p) || argsStr.includes(p));
245
+ if (isDestructive && !evaluation.warn) {
246
+ evaluation.warn = `Tool "${toolName}" appears destructive. Verify intent.`;
247
+ }
248
+ }
249
+ catch { /* non-critical */ }
250
+ // 4. Log tool usage to graph-memory (async, fire-and-forget)
251
+ this.logToolToGraph(toolName, args).catch(() => { });
252
+ return evaluation;
253
+ }
254
+ // ── Phase 3: Post-Response Self-Evaluation ──
255
+ async postProcess(message, response, toolsUsed, sessionId) {
256
+ const result = {
257
+ score: 0.5,
258
+ patternsExtracted: 0,
259
+ insightsGenerated: 0,
260
+ graphUpdates: 0,
261
+ consolidationTriggered: false,
262
+ };
263
+ // 1. Self-evaluate: was the response helpful? (heuristic, no LLM call)
264
+ result.score = this.selfEvaluate(message, response, toolsUsed);
265
+ // Record the evaluation
266
+ const evalEntry = {
267
+ sessionId,
268
+ messageHash: shortHash(message),
269
+ score: result.score,
270
+ toolSuccessRate: toolsUsed.length > 0 ? result.score : 1,
271
+ responseAppropriate: result.score >= 0.4,
272
+ patternsMatched: 0,
273
+ timestamp: new Date().toISOString(),
274
+ };
275
+ this.state.evalHistory.push(evalEntry);
276
+ if (this.state.evalHistory.length > MAX_EVAL_HISTORY) {
277
+ this.state.evalHistory = this.state.evalHistory.slice(-MAX_EVAL_HISTORY);
278
+ }
279
+ // Update rolling success rate (exponential moving average, alpha=0.1)
280
+ this.state.successRate = this.state.successRate * 0.9 + result.score * 0.1;
281
+ // 2. Extract patterns from successful interaction
282
+ if (result.score >= 0.5) {
283
+ try {
284
+ const learning = await getLearning();
285
+ learning.learnFromExchange(message, response, toolsUsed);
286
+ result.patternsExtracted++;
287
+ this.state.patternsLearnedToday++;
288
+ }
289
+ catch { /* non-critical */ }
290
+ }
291
+ // 3. Record routing outcome
292
+ if (this.pendingRouteAgent) {
293
+ try {
294
+ const router = await getLearnedRouter();
295
+ router.recordRoute(message, this.pendingRouteAgent, 'learned', result.score >= 0.5);
296
+ // Update routing accuracy
297
+ const routerStats = router.getRoutingStats();
298
+ if (routerStats.totalRoutes > 0) {
299
+ this.state.routingAccuracy = routerStats.learnedHits / routerStats.totalRoutes;
300
+ }
301
+ }
302
+ catch { /* non-critical */ }
303
+ this.pendingRouteAgent = null;
304
+ }
305
+ // 4. Update graph-memory with entities from the exchange
306
+ try {
307
+ const graph = await getGraphMemory();
308
+ const entities = graph.extractEntities(message, response);
309
+ result.graphUpdates = entities.length;
310
+ }
311
+ catch { /* non-critical */ }
312
+ // 5. Update confidence calibration
313
+ try {
314
+ const conf = await getConfidence();
315
+ conf.recordCalibration(message, result.score, result.score >= 0.5 ? 1 : 0);
316
+ }
317
+ catch { /* non-critical */ }
318
+ // 6. Update intentionality drives
319
+ try {
320
+ const intent = await getIntentionality();
321
+ intent.updateMotivation({
322
+ type: result.score >= 0.6 ? 'task_success' : result.score >= 0.3 ? 'learned_something' : 'task_failure',
323
+ });
324
+ }
325
+ catch { /* non-critical */ }
326
+ // 7. Check if emergent insights arise
327
+ try {
328
+ const emergent = await getEmergent();
329
+ if (emergent && typeof emergent.synthesizeAcross === 'function') {
330
+ const insights = await emergent.synthesizeAcross(this.state.evalHistory.slice(-10).map(e => e.messageHash));
331
+ if (insights && Array.isArray(insights)) {
332
+ for (const insight of insights.slice(0, 3)) {
333
+ this.addInsight(typeof insight === 'string' ? insight : JSON.stringify(insight), 'emergent', 0.6);
334
+ result.insightsGenerated++;
335
+ }
336
+ }
337
+ }
338
+ }
339
+ catch { /* emergent module may not exist yet */ }
340
+ // 8. Check if consolidation is needed
341
+ if (this.state.totalInteractions % CONSOLIDATION_INTERVAL === 0) {
342
+ result.consolidationTriggered = true;
343
+ // Fire-and-forget — don't block the response
344
+ this.consolidate().catch(() => { });
345
+ }
346
+ this.save();
347
+ return result;
348
+ }
349
+ // ── Phase 4: Cross-Session Learning ──
350
+ async consolidate() {
351
+ const result = {
352
+ patternsConsolidated: 0,
353
+ rulesAdded: 0,
354
+ insightsFound: 0,
355
+ graphPruned: { nodes: 0, edges: 0 },
356
+ routingAccuracy: this.state.routingAccuracy,
357
+ };
358
+ // 1. Run selfTrain() on accumulated patterns
359
+ try {
360
+ const learning = await getLearning();
361
+ const trained = learning.selfTrain();
362
+ result.patternsConsolidated = trained.optimized ?? 0;
363
+ }
364
+ catch { /* non-critical */ }
365
+ // 2. Prune weak graph edges
366
+ try {
367
+ const graph = await getGraphMemory();
368
+ // Decay nodes unused for 30 days
369
+ graph.decayUnused(30);
370
+ // Prune very weak connections
371
+ const pruned = graph.prune(0.1);
372
+ result.graphPruned = { nodes: pruned.removedNodes, edges: pruned.removedEdges };
373
+ graph.save();
374
+ }
375
+ catch { /* non-critical */ }
376
+ // 3. Derive behaviour rules from recurring patterns
377
+ try {
378
+ const learning = await getLearning();
379
+ const topPatterns = learning.getTopPatterns(5);
380
+ const behaviour = await getBehaviour();
381
+ for (const pattern of topPatterns) {
382
+ // If a pattern has been used many times, it might warrant a behaviour rule
383
+ if (pattern.hits >= 10 && pattern.successRate >= 0.8) {
384
+ const ruleText = `When asked about "${pattern.intent}", prefer tools: ${pattern.toolSequence.join(', ')}`;
385
+ const added = behaviour.learnGeneral(ruleText);
386
+ if (added)
387
+ result.rulesAdded++;
388
+ }
389
+ }
390
+ }
391
+ catch { /* non-critical */ }
392
+ // 4. Run emergent synthesis
393
+ try {
394
+ const emergent = await getEmergent();
395
+ if (emergent && typeof emergent.consolidate === 'function') {
396
+ const consolidated = await emergent.consolidate();
397
+ if (consolidated && typeof consolidated.insights === 'number') {
398
+ result.insightsFound = consolidated.insights;
399
+ }
400
+ }
401
+ }
402
+ catch { /* emergent module may not exist yet */ }
403
+ // 5. Update routing weights from outcome history
404
+ try {
405
+ const router = await getLearnedRouter();
406
+ const stats = router.getRoutingStats();
407
+ if (stats.totalRoutes > 0) {
408
+ result.routingAccuracy = stats.learnedHits / stats.totalRoutes;
409
+ this.state.routingAccuracy = result.routingAccuracy;
410
+ }
411
+ }
412
+ catch { /* non-critical */ }
413
+ this.state.lastConsolidation = new Date().toISOString();
414
+ this.save();
415
+ return result;
416
+ }
417
+ // ── Self-Evaluation (heuristic, no LLM call) ──
418
+ selfEvaluate(message, response, toolsUsed) {
419
+ let score = 0.5; // neutral baseline
420
+ // Response length appropriateness
421
+ if (message.length > 50 && response.length < 20) {
422
+ score -= 0.15;
423
+ }
424
+ else if (response.length > 50) {
425
+ score += 0.1;
426
+ }
427
+ // Tool success signals
428
+ if (toolsUsed.length > 0) {
429
+ const errorPatterns = ['error', 'failed', 'could not', 'unable to', 'not found', 'permission denied'];
430
+ const responseLower = response.toLowerCase();
431
+ const errorCount = errorPatterns.filter(p => responseLower.includes(p)).length;
432
+ if (errorCount === 0)
433
+ score += 0.2;
434
+ else if (errorCount >= 3)
435
+ score -= 0.2;
436
+ }
437
+ else {
438
+ score += 0.05;
439
+ }
440
+ // Pattern match bonus
441
+ try {
442
+ if (this.pendingRouteAgent)
443
+ score += 0.1;
444
+ }
445
+ catch { /* non-critical */ }
446
+ // Actionable content bonus (code blocks, file paths)
447
+ if (response.includes('```') || response.match(/(?:\/[\w./-]+\.\w+)/)) {
448
+ score += 0.1;
449
+ }
450
+ // Repetition penalty
451
+ const recentHashes = this.state.evalHistory.slice(-3).map(e => e.messageHash);
452
+ if (recentHashes.includes(shortHash(response.slice(0, 200)))) {
453
+ score -= 0.1;
454
+ }
455
+ // Clamp to [0, 1]
456
+ return Math.max(0, Math.min(1, score));
457
+ }
458
+ // ── Policy Synthesis ──
459
+ synthesizePolicy(confidence) {
460
+ // High confidence + high success rate → exploit (use proven approaches)
461
+ if (confidence > 0.7 && this.state.successRate > 0.7)
462
+ return 'exploit';
463
+ // Low confidence or low success → explore (try new approaches)
464
+ if (confidence < 0.4 || this.state.successRate < 0.3)
465
+ return 'explore';
466
+ return 'balanced';
467
+ }
468
+ // ── Graph Memory Logging ──
469
+ async logToolToGraph(toolName, args) {
470
+ try {
471
+ const graph = await getGraphMemory();
472
+ // Add the tool as an entity
473
+ const toolNode = graph.findNode(toolName);
474
+ let toolNodeId;
475
+ if (toolNode.length > 0) {
476
+ toolNodeId = toolNode[0].id;
477
+ }
478
+ else {
479
+ const added = graph.addNode('entity', toolName, { kind: 'tool', lastUsed: new Date().toISOString() });
480
+ toolNodeId = added?.id || '';
481
+ }
482
+ // If we have a session goal, connect tool to goal
483
+ if (toolNodeId && this.state.activeGoals.length > 0) {
484
+ const currentGoal = this.state.activeGoals[this.state.activeGoals.length - 1];
485
+ const goalNodes = graph.findNode(currentGoal.description.slice(0, 50));
486
+ if (goalNodes.length > 0) {
487
+ graph.addEdge(toolNodeId, goalNodes[0].id, 'used_for', 0.5);
488
+ }
489
+ }
490
+ graph.save();
491
+ }
492
+ catch { /* graph logging is non-critical */ }
493
+ }
494
+ // ── Insight Management ──
495
+ addInsight(content, source, confidence) {
496
+ this.state.recentInsights.push({
497
+ id: shortId(),
498
+ content,
499
+ source,
500
+ confidence,
501
+ timestamp: new Date().toISOString(),
502
+ });
503
+ // Trim to max
504
+ if (this.state.recentInsights.length > MAX_INSIGHTS) {
505
+ this.state.recentInsights = this.state.recentInsights.slice(-MAX_INSIGHTS);
506
+ }
507
+ }
508
+ // ── Goal Management ──
509
+ addGoal(description, priority = 0.5) {
510
+ const goal = {
511
+ id: shortId(),
512
+ description,
513
+ priority: Math.max(0, Math.min(1, priority)),
514
+ status: 'active',
515
+ created: new Date().toISOString(),
516
+ toolsUsed: [],
517
+ };
518
+ this.state.activeGoals.push(goal);
519
+ // Trim old completed/abandoned goals
520
+ if (this.state.activeGoals.length > MAX_GOALS) {
521
+ this.state.activeGoals = this.state.activeGoals
522
+ .filter(g => g.status === 'active')
523
+ .slice(-MAX_GOALS);
524
+ }
525
+ this.save();
526
+ return goal;
527
+ }
528
+ completeGoal(goalId) {
529
+ const goal = this.state.activeGoals.find(g => g.id === goalId);
530
+ if (goal) {
531
+ goal.status = 'completed';
532
+ this.save();
533
+ }
534
+ }
535
+ // ── Conflict Detection ──
536
+ recordConflict(modules, description, resolution) {
537
+ this.state.conflictLog.push({
538
+ modules,
539
+ description,
540
+ resolution: resolution ?? null,
541
+ timestamp: new Date().toISOString(),
542
+ });
543
+ if (this.state.conflictLog.length > MAX_CONFLICTS) {
544
+ this.state.conflictLog = this.state.conflictLog.slice(-MAX_CONFLICTS);
545
+ }
546
+ this.save();
547
+ }
548
+ // ── Persistence ──
549
+ load() {
550
+ this.state = loadState();
551
+ if (this.state.patternsLearnedDate !== todayStr()) {
552
+ this.state.patternsLearnedToday = 0;
553
+ this.state.patternsLearnedDate = todayStr();
554
+ }
555
+ }
556
+ save() {
557
+ saveState(this.state);
558
+ }
559
+ // ── Diagnostics ──
560
+ getStats() {
561
+ return {
562
+ totalInteractions: this.state.totalInteractions,
563
+ successRate: Math.round(this.state.successRate * 1000) / 1000,
564
+ patternsLearnedToday: this.state.patternsLearnedToday,
565
+ routingAccuracy: Math.round(this.state.routingAccuracy * 1000) / 1000,
566
+ activeGoals: this.state.activeGoals.filter(g => g.status === 'active').length,
567
+ recentInsights: this.state.recentInsights.length,
568
+ conflicts: this.state.conflictLog.length,
569
+ lastConsolidation: this.state.lastConsolidation,
570
+ policy: this.state.lastPolicy,
571
+ confidenceThreshold: this.state.confidenceThreshold,
572
+ uptimeMs: Date.now() - this.initTime,
573
+ };
574
+ }
575
+ getHealthReport() {
576
+ const stats = this.getStats();
577
+ const lines = [
578
+ '=== Intelligence Coordinator Health Report ===',
579
+ '',
580
+ `Total interactions: ${stats.totalInteractions}`,
581
+ `Success rate: ${(stats.successRate * 100).toFixed(1)}%`,
582
+ `Routing accuracy: ${(stats.routingAccuracy * 100).toFixed(1)}%`,
583
+ `Policy: ${stats.policy}`,
584
+ `Confidence threshold: ${stats.confidenceThreshold}`,
585
+ '',
586
+ `Active goals: ${stats.activeGoals}`,
587
+ `Recent insights: ${stats.recentInsights}`,
588
+ `Conflicts: ${stats.conflicts}`,
589
+ `Patterns learned today: ${stats.patternsLearnedToday}`,
590
+ '',
591
+ `Last consolidation: ${stats.lastConsolidation ?? 'never'}`,
592
+ `Uptime: ${Math.round(stats.uptimeMs / 1000)}s`,
593
+ ];
594
+ // Health checks
595
+ const issues = [];
596
+ if (stats.successRate < 0.3)
597
+ issues.push('LOW: Success rate below 30%');
598
+ if (stats.routingAccuracy < 0.3)
599
+ issues.push('LOW: Routing accuracy below 30%');
600
+ if (stats.totalInteractions > 50 && stats.patternsLearnedToday === 0) {
601
+ issues.push('STALE: No patterns learned today despite activity');
602
+ }
603
+ if (!stats.lastConsolidation) {
604
+ issues.push('PENDING: No consolidation has ever run');
605
+ }
606
+ if (issues.length > 0) {
607
+ lines.push('', '--- Issues ---');
608
+ for (const issue of issues)
609
+ lines.push(` ! ${issue}`);
610
+ }
611
+ else {
612
+ lines.push('', 'All systems nominal.');
613
+ }
614
+ return lines.join('\n');
615
+ }
616
+ getState() {
617
+ return this.state;
618
+ }
619
+ /** Adjust the confidence threshold (e.g., user prefers fewer clarification requests) */
620
+ setConfidenceThreshold(threshold) {
621
+ this.state.confidenceThreshold = Math.max(0, Math.min(1, threshold));
622
+ this.save();
623
+ }
624
+ /** Reset all state (for testing or fresh start) */
625
+ reset() {
626
+ this.state = defaultState();
627
+ this.anticipatedTools = [];
628
+ this.pendingRouteAgent = null;
629
+ this.save();
630
+ }
631
+ }
632
+ // ── Singleton ──
633
+ let singleton = null;
634
+ export function getCoordinator() {
635
+ if (!singleton) {
636
+ singleton = new IntelligenceCoordinator();
637
+ }
638
+ return singleton;
639
+ }
640
+ // ── Tool Registration ──
641
+ /** Register coordinator tools with the kbot tool registry */
642
+ export function registerCoordinatorTools() {
643
+ // Lazy import to avoid circular deps at module load time
644
+ import('./tools/index.js').then(({ registerTool }) => {
645
+ registerTool({
646
+ name: 'coordinator_status',
647
+ description: 'Show intelligence coordinator stats: success rate, routing accuracy, policy, interactions, goals, insights',
648
+ parameters: {},
649
+ tier: 'free',
650
+ execute: async () => {
651
+ const c = getCoordinator();
652
+ const stats = c.getStats();
653
+ return JSON.stringify(stats, null, 2);
654
+ },
655
+ });
656
+ registerTool({
657
+ name: 'coordinator_health',
658
+ description: 'Run a health check on all intelligence subsystems and report issues',
659
+ parameters: {},
660
+ tier: 'free',
661
+ execute: async () => {
662
+ const c = getCoordinator();
663
+ return c.getHealthReport();
664
+ },
665
+ });
666
+ registerTool({
667
+ name: 'coordinator_consolidate',
668
+ description: 'Force a cross-session learning consolidation: self-train patterns, prune graph, derive behaviour rules, synthesize insights',
669
+ parameters: {},
670
+ tier: 'free',
671
+ execute: async () => {
672
+ const c = getCoordinator();
673
+ const result = await c.consolidate();
674
+ return JSON.stringify(result, null, 2);
675
+ },
676
+ });
677
+ registerTool({
678
+ name: 'coordinator_orchestrate',
679
+ description: 'Decompose a goal into sub-tasks, assign specialist agents, execute in dependency order, and synthesize results',
680
+ parameters: { goal: { type: 'string', description: 'The high-level goal to orchestrate', required: true } },
681
+ tier: 'free',
682
+ execute: async (args) => coordinate(args.goal),
683
+ });
684
+ }).catch(() => {
685
+ // tools/index.js not available — skip registration
686
+ });
687
+ }
688
+ // ── Goal Decomposition & Multi-Agent Orchestration ──
689
+ //
690
+ // Takes a high-level goal and:
691
+ // 1. Decomposes it into ordered sub-tasks via LLM
692
+ // 2. Assigns each sub-task to the best specialist agent
693
+ // 3. Manages dependencies between sub-tasks
694
+ // 4. Synthesizes results into a final output
695
+ // 5. Learns from the execution for next time
696
+ const COORD_AMETHYST = typeof chalk.hex === 'function' ? chalk.hex('#6B5B95') : ((s) => s);
697
+ const DECOMPOSE_PROMPT = `You are a task decomposition engine. Break the goal into 2-6 concrete sub-tasks.
698
+ Output ONLY valid JSON: {"tasks":[{"id":"t1","goal":"...","agent":"agent_id","dependencies":[]}]}
699
+ Agents: kernel (general), coder (code/debug), researcher (research), writer (docs), analyst (strategy), guardian (security), infrastructure (devops).
700
+ Rules: each task independently verifiable, use dependencies for ordering, assign best agent, one objective per task.`;
701
+ /**
702
+ * Decompose a high-level goal into ordered sub-tasks with dependencies.
703
+ * Uses the LLM (via runAgent) to break the goal into 2-6 concrete tasks.
704
+ */
705
+ export async function decompose(goal) {
706
+ const planId = shortId();
707
+ const plan = {
708
+ id: planId,
709
+ goal,
710
+ tasks: [],
711
+ createdAt: new Date().toISOString(),
712
+ status: 'planning',
713
+ };
714
+ process.stderr.write(` ${COORD_AMETHYST('◆ decompose')} ${chalk.dim(goal.slice(0, 80))}${goal.length > 80 ? '...' : ''}\n`);
715
+ try {
716
+ // Lazy import to avoid circular dependency
717
+ const { runAgent } = await import('./agent.js');
718
+ const response = await runAgent(`${DECOMPOSE_PROMPT}\n\nGoal: ${goal}\n\nOutput JSON:`, { agent: 'kernel', skipPlanner: true, sessionId: `coord-${planId}` });
719
+ // Parse JSON from response
720
+ const jsonMatch = response.content.match(/\{[\s\S]*\}/);
721
+ if (jsonMatch) {
722
+ const parsed = JSON.parse(jsonMatch[0]);
723
+ plan.tasks = (parsed.tasks || []).slice(0, 6).map(t => ({
724
+ id: t.id || shortId(),
725
+ goal: t.goal,
726
+ status: 'pending',
727
+ agent: t.agent || 'kernel',
728
+ dependencies: t.dependencies || [],
729
+ }));
730
+ }
731
+ }
732
+ catch (err) {
733
+ process.stderr.write(` ${chalk.red('✗')} decomposition failed: ${err instanceof Error ? err.message : String(err)}\n`);
734
+ }
735
+ // Fallback: if decomposition produced nothing, create a single task
736
+ if (plan.tasks.length === 0) {
737
+ plan.tasks = [{
738
+ id: 't1',
739
+ goal,
740
+ status: 'pending',
741
+ agent: 'kernel',
742
+ dependencies: [],
743
+ }];
744
+ }
745
+ plan.status = 'executing';
746
+ for (const task of plan.tasks) {
747
+ const deps = task.dependencies.length > 0 ? chalk.dim(` -> ${task.dependencies.join(',')}`) : '';
748
+ process.stderr.write(` ${chalk.dim('│')} ${chalk.cyan(task.id)} ${task.goal} ${chalk.magenta(`@${task.agent}`)}${deps}\n`);
749
+ }
750
+ return plan;
751
+ }
752
+ /**
753
+ * Execute a plan's tasks in dependency order (sequential for now).
754
+ * Tasks whose dependencies are all 'done' are eligible to run.
755
+ */
756
+ export async function execute(plan) {
757
+ const { runAgent } = await import('./agent.js');
758
+ const completed = new Set();
759
+ process.stderr.write(`\n ${COORD_AMETHYST('◆ execute')} ${plan.tasks.length} tasks\n`);
760
+ while (completed.size < plan.tasks.length) {
761
+ // Find tasks whose dependencies are all satisfied
762
+ const ready = plan.tasks.filter(t => t.status === 'pending' &&
763
+ t.dependencies.every(dep => completed.has(dep)));
764
+ if (ready.length === 0) {
765
+ // Remaining tasks have unmet deps — mark them failed
766
+ for (const t of plan.tasks) {
767
+ if (t.status === 'pending') {
768
+ t.status = 'failed';
769
+ t.error = 'Unmet dependencies (earlier tasks failed)';
770
+ completed.add(t.id);
771
+ }
772
+ }
773
+ break;
774
+ }
775
+ // Execute ready tasks sequentially (parallel execution can come later)
776
+ for (const task of ready) {
777
+ task.status = 'running';
778
+ process.stderr.write(` ${chalk.dim('├')} ${chalk.yellow('●')} ${task.id}: ${task.goal}\n`);
779
+ // Gather dependency results as context
780
+ const depCtx = task.dependencies
781
+ .map(id => { const d = plan.tasks.find(t => t.id === id); return d?.result ? `[${id}]: ${d.result.slice(0, 500)}` : ''; })
782
+ .filter(Boolean).join('\n');
783
+ const prompt = `You are executing a sub-task of a larger plan.${depCtx ? `\n\nPrevious results:\n${depCtx}` : ''}\n\nYour task: ${task.goal}\n\nExecute this now.`;
784
+ try {
785
+ const response = await runAgent(prompt, {
786
+ agent: task.agent,
787
+ skipPlanner: true,
788
+ sessionId: `coord-${plan.id}-${task.id}`,
789
+ });
790
+ task.result = response.content;
791
+ task.status = 'done';
792
+ process.stderr.write(` ${chalk.dim('│')} ${chalk.green('✓')} ${task.id} done\n`);
793
+ }
794
+ catch (err) {
795
+ task.status = 'failed';
796
+ task.error = err instanceof Error ? err.message : String(err);
797
+ process.stderr.write(` ${chalk.dim('│')} ${chalk.red('✗')} ${task.id} failed: ${task.error}\n`);
798
+ }
799
+ completed.add(task.id);
800
+ }
801
+ }
802
+ // Determine final plan status
803
+ const failed = plan.tasks.filter(t => t.status === 'failed');
804
+ plan.status = failed.length === 0 ? 'done' : 'failed';
805
+ plan.completedAt = new Date().toISOString();
806
+ const done = plan.tasks.filter(t => t.status === 'done').length;
807
+ process.stderr.write(` ${chalk.dim('└')} ${done}/${plan.tasks.length} tasks succeeded\n`);
808
+ // Record outcome for learning
809
+ try {
810
+ const coord = getCoordinator();
811
+ if (plan.status === 'done')
812
+ coord.addGoal(plan.goal, 0.7).status = 'completed';
813
+ const learning = await getLearning();
814
+ learning.learnFromExchange(`[coordinator] ${plan.goal}`, `${done}/${plan.tasks.length} done`, plan.tasks.map(t => t.agent));
815
+ }
816
+ catch { /* non-critical */ }
817
+ return plan;
818
+ }
819
+ /**
820
+ * Convenience: decompose a goal, execute the plan, and synthesize results.
821
+ * Returns a human-readable summary of what was accomplished.
822
+ */
823
+ export async function coordinate(goal) {
824
+ const plan = await decompose(goal);
825
+ const executed = await execute(plan);
826
+ // Synthesize results
827
+ const results = executed.tasks
828
+ .filter(t => t.status === 'done' && t.result)
829
+ .map(t => `## ${t.goal}\n${t.result}`)
830
+ .join('\n\n');
831
+ const failed = executed.tasks.filter(t => t.status === 'failed');
832
+ const failSummary = failed.length > 0
833
+ ? `\n\n---\n${failed.length} task(s) failed:\n${failed.map(t => `- ${t.goal}: ${t.error}`).join('\n')}`
834
+ : '';
835
+ return results + failSummary;
836
+ }
837
+ // Auto-register tools when this module is imported
838
+ registerCoordinatorTools();
839
+ //# sourceMappingURL=coordinator.js.map