@kernel.chat/kbot 3.23.0 → 3.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +33 -22
  2. package/dist/agent.d.ts.map +1 -1
  3. package/dist/agent.js +3 -1
  4. package/dist/agent.js.map +1 -1
  5. package/dist/agents/trader.d.ts +32 -0
  6. package/dist/agents/trader.d.ts.map +1 -0
  7. package/dist/agents/trader.js +190 -0
  8. package/dist/agents/trader.js.map +1 -0
  9. package/dist/cli.js +219 -15
  10. package/dist/cli.js.map +1 -1
  11. package/dist/context.d.ts +4 -1
  12. package/dist/context.d.ts.map +1 -1
  13. package/dist/context.js +28 -1
  14. package/dist/context.js.map +1 -1
  15. package/dist/doctor.d.ts.map +1 -1
  16. package/dist/doctor.js +71 -1
  17. package/dist/doctor.js.map +1 -1
  18. package/dist/inference.d.ts +9 -0
  19. package/dist/inference.d.ts.map +1 -1
  20. package/dist/inference.js +38 -5
  21. package/dist/inference.js.map +1 -1
  22. package/dist/introspection.d.ts +17 -0
  23. package/dist/introspection.d.ts.map +1 -0
  24. package/dist/introspection.js +490 -0
  25. package/dist/introspection.js.map +1 -0
  26. package/dist/learned-router.d.ts.map +1 -1
  27. package/dist/learned-router.js +3 -0
  28. package/dist/learned-router.js.map +1 -1
  29. package/dist/machine.d.ts +85 -0
  30. package/dist/machine.d.ts.map +1 -0
  31. package/dist/machine.js +538 -0
  32. package/dist/machine.js.map +1 -0
  33. package/dist/matrix.d.ts.map +1 -1
  34. package/dist/matrix.js +11 -0
  35. package/dist/matrix.js.map +1 -1
  36. package/dist/provider-fallback.d.ts +6 -0
  37. package/dist/provider-fallback.d.ts.map +1 -1
  38. package/dist/provider-fallback.js +29 -0
  39. package/dist/provider-fallback.js.map +1 -1
  40. package/dist/synthesis-engine.d.ts +175 -0
  41. package/dist/synthesis-engine.d.ts.map +1 -0
  42. package/dist/synthesis-engine.js +783 -0
  43. package/dist/synthesis-engine.js.map +1 -0
  44. package/dist/tool-pipeline.d.ts +7 -1
  45. package/dist/tool-pipeline.d.ts.map +1 -1
  46. package/dist/tool-pipeline.js +39 -1
  47. package/dist/tool-pipeline.js.map +1 -1
  48. package/dist/tools/finance.d.ts +2 -0
  49. package/dist/tools/finance.d.ts.map +1 -0
  50. package/dist/tools/finance.js +1116 -0
  51. package/dist/tools/finance.js.map +1 -0
  52. package/dist/tools/finance.test.d.ts +2 -0
  53. package/dist/tools/finance.test.d.ts.map +1 -0
  54. package/dist/tools/finance.test.js +245 -0
  55. package/dist/tools/finance.test.js.map +1 -0
  56. package/dist/tools/index.d.ts.map +1 -1
  57. package/dist/tools/index.js +5 -0
  58. package/dist/tools/index.js.map +1 -1
  59. package/dist/tools/machine-tools.d.ts +2 -0
  60. package/dist/tools/machine-tools.d.ts.map +1 -0
  61. package/dist/tools/machine-tools.js +690 -0
  62. package/dist/tools/machine-tools.js.map +1 -0
  63. package/dist/tools/sentiment.d.ts +2 -0
  64. package/dist/tools/sentiment.d.ts.map +1 -0
  65. package/dist/tools/sentiment.js +513 -0
  66. package/dist/tools/sentiment.js.map +1 -0
  67. package/dist/tools/stocks.d.ts +2 -0
  68. package/dist/tools/stocks.d.ts.map +1 -0
  69. package/dist/tools/stocks.js +345 -0
  70. package/dist/tools/stocks.js.map +1 -0
  71. package/dist/tools/stocks.test.d.ts +2 -0
  72. package/dist/tools/stocks.test.d.ts.map +1 -0
  73. package/dist/tools/stocks.test.js +82 -0
  74. package/dist/tools/stocks.test.js.map +1 -0
  75. package/dist/tools/wallet.d.ts +2 -0
  76. package/dist/tools/wallet.d.ts.map +1 -0
  77. package/dist/tools/wallet.js +698 -0
  78. package/dist/tools/wallet.js.map +1 -0
  79. package/dist/tools/wallet.test.d.ts +2 -0
  80. package/dist/tools/wallet.test.d.ts.map +1 -0
  81. package/dist/tools/wallet.test.js +205 -0
  82. package/dist/tools/wallet.test.js.map +1 -0
  83. package/dist/ui.js +1 -1
  84. package/dist/ui.js.map +1 -1
  85. package/package.json +94 -42
@@ -0,0 +1,783 @@
1
+ // kbot Synthesis Engine — Closed-Loop Intelligence Compounding
2
+ //
3
+ // The bridge between SELF-DISCOVERY (learning, reflections, skill ratings)
4
+ // and UNIVERSE-DISCOVERY (tools, agents, papers, opportunities, engagement).
5
+ //
6
+ // Eight synthesis operations:
7
+ // 1. consumeDiscoveredTools — evaluate discovered tools against failure patterns
8
+ // 2. instantiateProposedAgents — trial proposed agents against skill gaps
9
+ // 3. extractPaperInsights — pull implementable patterns from academic papers
10
+ // 4. buildActiveCorrections — corrections + reflections → prompt injection
11
+ // 5. closeReflectionLoop — reflections → skill rating adjustments
12
+ // 6. crossPollinatePatterns — transfer patterns across projects
13
+ // 7. buildSkillMap — Bayesian ratings → human-readable map
14
+ // 8. feedEngagementBack — engagement outcomes → topic weights
15
+ //
16
+ // All operations are heuristic — no LLM calls. Fast and free.
17
+ // Storage: ~/.kbot/memory/synthesis-engine.json
18
+ // ~/.kbot/memory/active-corrections.json
19
+ // ~/.kbot/memory/skill-map.json
20
+ // .kbot-discovery/topic-weights.json (if discovery exists)
21
+ import { homedir } from 'node:os';
22
+ import { join } from 'node:path';
23
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
24
+ // ══════════════════════════════════════════════════════════════════════
25
+ // Paths
26
+ // ══════════════════════════════════════════════════════════════════════
27
+ const MEMORY_DIR = join(homedir(), '.kbot', 'memory');
28
+ const STATE_FILE = join(MEMORY_DIR, 'synthesis-engine.json');
29
+ const CORRECTIONS_FILE = join(MEMORY_DIR, 'active-corrections.json');
30
+ const SKILL_MAP_FILE = join(MEMORY_DIR, 'skill-map.json');
31
+ // Learning stores
32
+ const PATTERNS_FILE = join(MEMORY_DIR, 'patterns.json');
33
+ const REFLECTIONS_FILE = join(MEMORY_DIR, 'reflections.json');
34
+ const SKILL_RATINGS_FILE = join(MEMORY_DIR, 'skill-ratings.json');
35
+ const CORRECTIONS_LEARNING_FILE = join(MEMORY_DIR, 'corrections.json');
36
+ const PROJECTS_FILE = join(MEMORY_DIR, 'projects.json');
37
+ const ROUTING_HISTORY_FILE = join(MEMORY_DIR, 'routing-history.json');
38
+ // ══════════════════════════════════════════════════════════════════════
39
+ // Helpers
40
+ // ══════════════════════════════════════════════════════════════════════
41
+ function ensureDir(dir) {
42
+ if (!existsSync(dir))
43
+ mkdirSync(dir, { recursive: true });
44
+ }
45
+ function loadJSON(path, fallback) {
46
+ if (!existsSync(path))
47
+ return fallback;
48
+ try {
49
+ return JSON.parse(readFileSync(path, 'utf-8'));
50
+ }
51
+ catch {
52
+ return fallback;
53
+ }
54
+ }
55
+ function saveJSON(path, data) {
56
+ ensureDir(join(path, '..'));
57
+ writeFileSync(path, JSON.stringify(data, null, 2));
58
+ }
59
+ function loadState() {
60
+ return loadJSON(STATE_FILE, {
61
+ toolAdoptions: [],
62
+ agentTrials: [],
63
+ paperInsights: [],
64
+ skillMap: [],
65
+ topicWeights: [],
66
+ crossPollinatedCount: 0,
67
+ lastCycleAt: '',
68
+ totalCycles: 0,
69
+ stats: {
70
+ toolsEvaluated: 0, toolsAdopted: 0, toolsRejected: 0,
71
+ agentsTrialed: 0, agentsKept: 0, agentsDissolved: 0,
72
+ papersAnalyzed: 0, patternsImplemented: 0,
73
+ correctionsActive: 0, reflectionsClosed: 0,
74
+ patternsTransferred: 0, engagementsFedBack: 0,
75
+ },
76
+ });
77
+ }
78
+ function saveState(state) {
79
+ ensureDir(MEMORY_DIR);
80
+ saveJSON(STATE_FILE, state);
81
+ }
82
+ /**
83
+ * Evaluate discovered tools against failure patterns in reflections.
84
+ * If a discovered tool solves a problem that caused repeated failures,
85
+ * mark it for adoption. Otherwise, mark as evaluated and move on.
86
+ */
87
+ export function consumeDiscoveredTools(discoveryDir) {
88
+ const state = loadState();
89
+ const outreachFile = join(discoveryDir, 'outreach', 'latest.json');
90
+ if (!existsSync(outreachFile))
91
+ return state.toolAdoptions;
92
+ const outreach = loadJSON(outreachFile, { projects: [] });
93
+ const projects = outreach.projects || [];
94
+ if (projects.length === 0)
95
+ return state.toolAdoptions;
96
+ // Load reflections to find failure patterns
97
+ const reflections = loadJSON(REFLECTIONS_FILE, []);
98
+ // Extract failure keywords from reflections
99
+ const failureKeywords = new Set();
100
+ for (const r of reflections) {
101
+ const words = (r.lesson + ' ' + r.taskMessage).toLowerCase()
102
+ .replace(/[^a-z0-9\s-]/g, ' ')
103
+ .split(/\s+/)
104
+ .filter(w => w.length > 3);
105
+ for (const w of words)
106
+ failureKeywords.add(w);
107
+ }
108
+ // Already evaluated tools (by URL)
109
+ const evaluatedUrls = new Set(state.toolAdoptions.map(t => t.url));
110
+ const newAdoptions = [];
111
+ for (const project of projects) {
112
+ if (evaluatedUrls.has(project.url))
113
+ continue;
114
+ // Score: how many failure keywords appear in the project description?
115
+ const desc = project.description.toLowerCase();
116
+ const descWords = desc.replace(/[^a-z0-9\s-]/g, ' ').split(/\s+/);
117
+ const matchCount = descWords.filter(w => failureKeywords.has(w)).length;
118
+ const matchedWords = descWords.filter(w => failureKeywords.has(w));
119
+ // Also check: does it solve a capability gap? (agent, tool, search, fetch, etc.)
120
+ const capabilityTerms = ['agent', 'tool', 'cli', 'search', 'fetch', 'automation', 'mcp', 'plugin'];
121
+ const capScore = capabilityTerms.filter(t => desc.includes(t)).length;
122
+ const totalScore = matchCount + capScore;
123
+ const adoption = {
124
+ name: project.name,
125
+ url: project.url,
126
+ stars: project.stars,
127
+ reason: totalScore >= 3
128
+ ? `Matches ${matchCount} failure keywords [${matchedWords.slice(0, 3).join(', ')}] + ${capScore} capability terms`
129
+ : totalScore >= 1
130
+ ? `Low relevance: ${matchCount} failure keywords, ${capScore} capability terms`
131
+ : 'No match to current failure patterns',
132
+ status: totalScore >= 3 && project.stars >= 50 ? 'adopted' : 'rejected',
133
+ matchedReflection: matchCount > 0 ? matchedWords.slice(0, 3).join(', ') : undefined,
134
+ evaluatedAt: new Date().toISOString(),
135
+ };
136
+ newAdoptions.push(adoption);
137
+ }
138
+ // Merge and cap at 100
139
+ state.toolAdoptions = [...state.toolAdoptions, ...newAdoptions].slice(-100);
140
+ state.stats.toolsEvaluated += newAdoptions.length;
141
+ state.stats.toolsAdopted += newAdoptions.filter(t => t.status === 'adopted').length;
142
+ state.stats.toolsRejected += newAdoptions.filter(t => t.status === 'rejected').length;
143
+ saveState(state);
144
+ return newAdoptions;
145
+ }
146
+ /**
147
+ * Evaluate proposed agents against Bayesian skill rating gaps.
148
+ * If an agent targets a category with high sigma (uncertainty),
149
+ * start a trial. After enough tasks, keep or dissolve.
150
+ */
151
+ export function instantiateProposedAgents(discoveryDir) {
152
+ const state = loadState();
153
+ const agentsFile = join(discoveryDir, 'proposed-agents.json');
154
+ // Also check outreach for proposed agents
155
+ let proposed = [];
156
+ if (existsSync(agentsFile)) {
157
+ proposed = loadJSON(agentsFile, []);
158
+ }
159
+ if (proposed.length === 0)
160
+ return state.agentTrials;
161
+ // Load skill ratings to find gaps
162
+ const ratings = loadJSON(SKILL_RATINGS_FILE, {});
163
+ // Find categories with high sigma (uncertainty) across all agents
164
+ const categories = ['coding', 'debugging', 'refactoring', 'research', 'analysis',
165
+ 'writing', 'devops', 'security', 'design', 'general', 'data', 'communication'];
166
+ const categoryGaps = {}; // category → avg sigma
167
+ for (const cat of categories) {
168
+ let totalSigma = 0;
169
+ let count = 0;
170
+ for (const agentRatings of Object.values(ratings)) {
171
+ if (agentRatings[cat]) {
172
+ totalSigma += agentRatings[cat].sigma;
173
+ count++;
174
+ }
175
+ }
176
+ // If no agent has been tested in this category, sigma is max (8.33)
177
+ categoryGaps[cat] = count > 0 ? totalSigma / count : 8.33;
178
+ }
179
+ // Already trialing agents (by name)
180
+ const trialingNames = new Set(state.agentTrials.map(t => t.name));
181
+ const newTrials = [];
182
+ for (const agent of proposed) {
183
+ if (trialingNames.has(agent.name))
184
+ continue;
185
+ // Find the best matching category for this agent
186
+ const nameWords = agent.name.toLowerCase().split(/[\s-_]+/);
187
+ const promptWords = (agent.systemPrompt || agent.reasoning || '').toLowerCase()
188
+ .replace(/[^a-z0-9\s]/g, ' ').split(/\s+/);
189
+ const allWords = [...nameWords, ...promptWords];
190
+ let bestCategory = 'general';
191
+ let bestScore = 0;
192
+ for (const cat of categories) {
193
+ const catWords = cat.split(/[\s-_]+/);
194
+ const matches = catWords.filter(w => allWords.includes(w)).length;
195
+ if (matches > bestScore) {
196
+ bestScore = matches;
197
+ bestCategory = cat;
198
+ }
199
+ }
200
+ // Only trial if the target category has high uncertainty
201
+ const gap = categoryGaps[bestCategory] || 8.33;
202
+ if (gap < 4)
203
+ continue; // Category already well-covered
204
+ const trial = {
205
+ name: agent.name,
206
+ systemPrompt: (agent.systemPrompt || '').slice(0, 500),
207
+ targetCategory: bestCategory,
208
+ status: 'trialing',
209
+ taskCount: 0,
210
+ mu: 25,
211
+ sigma: 8.33,
212
+ createdAt: new Date().toISOString(),
213
+ };
214
+ newTrials.push(trial);
215
+ }
216
+ state.agentTrials = [...state.agentTrials, ...newTrials].slice(-50);
217
+ state.stats.agentsTrialed += newTrials.length;
218
+ saveState(state);
219
+ return newTrials;
220
+ }
221
+ /**
222
+ * Match academic paper techniques against existing tool patterns.
223
+ * If a paper describes an optimization that maps to a known pattern,
224
+ * propose it as an improvement.
225
+ */
226
+ export function extractPaperInsights(discoveryDir) {
227
+ const state = loadState();
228
+ const outreachFile = join(discoveryDir, 'outreach', 'latest.json');
229
+ if (!existsSync(outreachFile))
230
+ return state.paperInsights;
231
+ const outreach = loadJSON(outreachFile, {});
232
+ const papers = [];
233
+ if (outreach.latestPaper)
234
+ papers.push(outreach.latestPaper);
235
+ if (outreach.papers)
236
+ papers.push(...outreach.papers);
237
+ if (papers.length === 0)
238
+ return state.paperInsights;
239
+ const patterns = loadJSON(PATTERNS_FILE, []);
240
+ // Extract technique keywords from papers
241
+ const analyzedTitles = new Set(state.paperInsights.map(p => p.title));
242
+ const newInsights = [];
243
+ // Technique detection patterns
244
+ const techniquePatterns = [
245
+ { regex: /seek|search.*guided|tool.*guided/i, technique: 'Guided seeking instead of exhaustive parsing', applicableTo: 'read_file + grep patterns' },
246
+ { regex: /retrieval.*augment|rag/i, technique: 'Retrieval-augmented generation', applicableTo: 'research and fact-checking patterns' },
247
+ { regex: /plan.*then.*act|planning.*execution/i, technique: 'Plan-then-act decomposition', applicableTo: 'multi-step task patterns' },
248
+ { regex: /self.*correct|self.*refin/i, technique: 'Self-correction loop', applicableTo: 'error-correction patterns' },
249
+ { regex: /tool.*use|tool.*call|function.*call/i, technique: 'Improved tool selection', applicableTo: 'tool routing patterns' },
250
+ { regex: /multi.*agent|agent.*collab/i, technique: 'Multi-agent collaboration', applicableTo: 'swarm and routing patterns' },
251
+ { regex: /memory|context.*window|long.*context/i, technique: 'Memory optimization', applicableTo: 'context management patterns' },
252
+ { regex: /benchmark|eval/i, technique: 'Evaluation methodology', applicableTo: 'self-eval and skill rating patterns' },
253
+ { regex: /efficient|lightweight|compress/i, technique: 'Efficiency optimization', applicableTo: 'token reduction patterns' },
254
+ { regex: /autonomous|self.*improv/i, technique: 'Autonomous improvement', applicableTo: 'evolution and autopoiesis patterns' },
255
+ ];
256
+ for (const paper of papers) {
257
+ if (analyzedTitles.has(paper.title))
258
+ continue;
259
+ const text = `${paper.title} ${paper.summary || ''}`;
260
+ for (const tp of techniquePatterns) {
261
+ if (tp.regex.test(text)) {
262
+ // Check if we have patterns in the applicable area
263
+ const applicablePatterns = patterns.filter(p => tp.applicableTo.split(/\s+/).some(w => p.toolSequence.some(t => t.includes(w)) || p.intent.includes(w)));
264
+ newInsights.push({
265
+ title: paper.title,
266
+ url: paper.url || '',
267
+ technique: tp.technique,
268
+ applicableTo: tp.applicableTo + (applicablePatterns.length > 0
269
+ ? ` (${applicablePatterns.length} matching patterns found)`
270
+ : ' (no matching patterns yet)'),
271
+ status: applicablePatterns.length > 0 ? 'proposed' : 'rejected',
272
+ extractedAt: new Date().toISOString(),
273
+ });
274
+ break; // One insight per paper
275
+ }
276
+ }
277
+ }
278
+ state.paperInsights = [...state.paperInsights, ...newInsights].slice(-100);
279
+ state.stats.papersAnalyzed += newInsights.length;
280
+ saveState(state);
281
+ return newInsights;
282
+ }
283
+ // ══════════════════════════════════════════════════════════════════════
284
+ // 4. BUILD ACTIVE CORRECTIONS
285
+ // ══════════════════════════════════════════════════════════════════════
286
+ /**
287
+ * Extract actionable corrections from:
288
+ * - Explicit corrections in corrections.json
289
+ * - Implicit corrections from reflection failure patterns
290
+ * - Pattern failures (low success rate patterns)
291
+ *
292
+ * Produces active-corrections.json for prompt injection.
293
+ */
294
+ export function buildActiveCorrections() {
295
+ const state = loadState();
296
+ const corrections = loadJSON(CORRECTIONS_LEARNING_FILE, []);
297
+ const active = [];
298
+ for (const c of corrections) {
299
+ if (c.rule && c.occurrences >= 1) {
300
+ active.push({
301
+ rule: c.rule,
302
+ source: 'explicit',
303
+ severity: c.occurrences >= 3 ? 'high' : c.occurrences >= 2 ? 'medium' : 'low',
304
+ occurrences: c.occurrences,
305
+ extractedAt: new Date().toISOString(),
306
+ });
307
+ }
308
+ }
309
+ const reflections = loadJSON(REFLECTIONS_FILE, []);
310
+ // Group reflection lessons by theme
311
+ const lessonThemes = {};
312
+ for (const r of reflections) {
313
+ if (!r.lesson)
314
+ continue;
315
+ // Normalize: extract the core rule from the lesson
316
+ const normalized = r.lesson.toLowerCase()
317
+ .replace(/[^a-z0-9\s]/g, ' ')
318
+ .replace(/\s+/g, ' ')
319
+ .trim()
320
+ .slice(0, 100);
321
+ lessonThemes[normalized] = (lessonThemes[normalized] || 0) + 1;
322
+ }
323
+ // Top recurring themes become corrections
324
+ const sortedThemes = Object.entries(lessonThemes)
325
+ .sort((a, b) => b[1] - a[1])
326
+ .slice(0, 10);
327
+ for (const [theme, count] of sortedThemes) {
328
+ if (count >= 2) {
329
+ active.push({
330
+ rule: theme,
331
+ source: 'reflection',
332
+ severity: count >= 5 ? 'high' : count >= 3 ? 'medium' : 'low',
333
+ occurrences: count,
334
+ extractedAt: new Date().toISOString(),
335
+ });
336
+ }
337
+ }
338
+ const patterns = loadJSON(PATTERNS_FILE, []);
339
+ const failingPatterns = patterns.filter(p => p.hits >= 3 && p.successRate < 0.5);
340
+ for (const p of failingPatterns) {
341
+ active.push({
342
+ rule: `Tool sequence [${p.toolSequence.join(' → ')}] for "${p.intent}" fails ${Math.round((1 - p.successRate) * 100)}% of the time — consider alternative approach`,
343
+ source: 'pattern_failure',
344
+ severity: p.successRate < 0.3 ? 'high' : 'medium',
345
+ occurrences: p.hits,
346
+ extractedAt: new Date().toISOString(),
347
+ });
348
+ }
349
+ // Sort by severity then occurrences, cap at 10
350
+ const severityOrder = { high: 3, medium: 2, low: 1 };
351
+ active.sort((a, b) => (severityOrder[b.severity] || 0) - (severityOrder[a.severity] || 0)
352
+ || b.occurrences - a.occurrences);
353
+ const finalCorrections = active.slice(0, 10);
354
+ // Save for prompt injection
355
+ ensureDir(MEMORY_DIR);
356
+ saveJSON(CORRECTIONS_FILE, finalCorrections);
357
+ state.stats.correctionsActive = finalCorrections.length;
358
+ saveState(state);
359
+ return finalCorrections;
360
+ }
361
+ /**
362
+ * Format active corrections for system prompt injection.
363
+ * Called by agent.ts to append corrections to the system prompt.
364
+ */
365
+ export function getActiveCorrectionsPrompt() {
366
+ const corrections = loadJSON(CORRECTIONS_FILE, []);
367
+ if (corrections.length === 0)
368
+ return '';
369
+ const lines = ['[Active Corrections — avoid repeating these mistakes]'];
370
+ for (const c of corrections) {
371
+ const icon = c.severity === 'high' ? '!!' : c.severity === 'medium' ? '!' : '-';
372
+ lines.push(`${icon} ${c.rule} (${c.occurrences}x, from ${c.source})`);
373
+ }
374
+ return lines.join('\n');
375
+ }
376
+ // ══════════════════════════════════════════════════════════════════════
377
+ // 5. CLOSE REFLECTION → ROUTING LOOP
378
+ // ══════════════════════════════════════════════════════════════════════
379
+ /**
380
+ * Analyze reflections to adjust skill ratings.
381
+ * If an agent consistently fails at a task category,
382
+ * downgrade its mu in that category.
383
+ */
384
+ export function closeReflectionLoop() {
385
+ const state = loadState();
386
+ const reflections = loadJSON(REFLECTIONS_FILE, []);
387
+ if (reflections.length === 0)
388
+ return 0;
389
+ const routingHistory = loadJSON(ROUTING_HISTORY_FILE, []);
390
+ // Load current skill ratings
391
+ const ratings = loadJSON(SKILL_RATINGS_FILE, {});
392
+ let adjustments = 0;
393
+ // For each reflection, find the responsible agent and category
394
+ for (const r of reflections) {
395
+ const taskWords = r.taskMessage.toLowerCase().split(/\s+/);
396
+ // Find matching routing entry
397
+ const matchingRoute = routingHistory.find(h => {
398
+ const intentWords = h.intent.toLowerCase().split(/\s+/);
399
+ const overlap = taskWords.filter(w => intentWords.includes(w)).length;
400
+ return overlap >= Math.min(3, taskWords.length * 0.5);
401
+ });
402
+ if (!matchingRoute)
403
+ continue;
404
+ const agent = matchingRoute.agent;
405
+ if (!agent || agent === 'auto')
406
+ continue;
407
+ // Detect category from task message
408
+ const categoryKeywords = {
409
+ coding: ['code', 'function', 'implement', 'build', 'component'],
410
+ debugging: ['fix', 'bug', 'error', 'crash', 'debug'],
411
+ security: ['security', 'vulnerability', 'exploit', 'auth', 'encrypt'],
412
+ research: ['research', 'find', 'search', 'compare', 'investigate'],
413
+ writing: ['write', 'document', 'explain', 'describe', 'draft'],
414
+ };
415
+ let detectedCategory = 'general';
416
+ let bestCatScore = 0;
417
+ for (const [cat, keywords] of Object.entries(categoryKeywords)) {
418
+ const score = keywords.filter(k => taskWords.includes(k)).length;
419
+ if (score > bestCatScore) {
420
+ bestCatScore = score;
421
+ detectedCategory = cat;
422
+ }
423
+ }
424
+ // Adjust rating: penalize the agent in this category
425
+ if (!ratings[agent])
426
+ ratings[agent] = { _overall: { mu: 25, sigma: 8.33 } };
427
+ if (!ratings[agent][detectedCategory]) {
428
+ ratings[agent][detectedCategory] = { mu: 25, sigma: 8.33 };
429
+ }
430
+ const current = ratings[agent][detectedCategory];
431
+ // Apply a small penalty (Bradley-Terry loss equivalent)
432
+ const K = current.sigma * current.sigma / Math.sqrt(2 * (current.sigma / 2) ** 2 + current.sigma ** 2);
433
+ current.mu = Math.max(15, current.mu - K * 0.5); // Half-strength penalty
434
+ current.sigma = Math.max(0.5, current.sigma * 0.95); // Reduce uncertainty slightly
435
+ adjustments++;
436
+ }
437
+ if (adjustments > 0) {
438
+ saveJSON(SKILL_RATINGS_FILE, ratings);
439
+ state.stats.reflectionsClosed += adjustments;
440
+ saveState(state);
441
+ }
442
+ return adjustments;
443
+ }
444
+ /**
445
+ * Transfer successful patterns from one project to another.
446
+ * Patterns with high success rates in one project get added
447
+ * to the global pool with reduced confidence.
448
+ */
449
+ export function crossPollinatePatterns() {
450
+ const state = loadState();
451
+ const projects = loadJSON(PROJECTS_FILE, []);
452
+ if (projects.length < 2)
453
+ return 0;
454
+ const patterns = loadJSON(PATTERNS_FILE, []);
455
+ // Find patterns that are project-specific (high success, mention project files)
456
+ const globalIntents = new Set(patterns.map(p => p.intent));
457
+ let transferred = 0;
458
+ // Look for patterns that use tools common across projects
459
+ const universalTools = ['read_file', 'write_file', 'bash', 'grep', 'glob', 'edit_file'];
460
+ const universalPatterns = patterns.filter(p => p.hits >= 3 &&
461
+ p.successRate >= 0.7 &&
462
+ p.toolSequence.every(t => universalTools.includes(t)) &&
463
+ !p.origin);
464
+ // For each universal pattern, check if a variant exists
465
+ for (const p of universalPatterns) {
466
+ // Create a generic version with reduced confidence
467
+ const genericIntent = p.intent
468
+ .replace(/\b(src|lib|app|packages|components)\b/g, '')
469
+ .replace(/\s+/g, ' ')
470
+ .trim();
471
+ if (genericIntent.length < 5)
472
+ continue;
473
+ if (globalIntents.has(genericIntent))
474
+ continue;
475
+ // Add as cross-pollinated pattern with lower confidence
476
+ patterns.push({
477
+ intent: genericIntent,
478
+ keywords: p.keywords,
479
+ toolSequence: p.toolSequence,
480
+ hits: 1,
481
+ successRate: p.successRate * 0.7, // 30% confidence reduction
482
+ avgTokensSaved: p.avgTokensSaved,
483
+ origin: 'cross-project',
484
+ });
485
+ globalIntents.add(genericIntent);
486
+ transferred++;
487
+ if (transferred >= 10)
488
+ break; // Max 10 per cycle
489
+ }
490
+ if (transferred > 0) {
491
+ saveJSON(PATTERNS_FILE, patterns);
492
+ state.crossPollinatedCount += transferred;
493
+ state.stats.patternsTransferred += transferred;
494
+ saveState(state);
495
+ }
496
+ return transferred;
497
+ }
498
+ // ══════════════════════════════════════════════════════════════════════
499
+ // 7. BUILD SKILL MAP
500
+ // ══════════════════════════════════════════════════════════════════════
501
+ const INITIAL_MU = 25.0;
502
+ const INITIAL_SIGMA = INITIAL_MU / 3;
503
+ /**
504
+ * Build a human-readable skill map from Bayesian ratings.
505
+ * Classifies agents as proven (low σ), developing (medium σ), or untested (high σ).
506
+ */
507
+ export function buildSkillMap() {
508
+ const state = loadState();
509
+ const ratings = loadJSON(SKILL_RATINGS_FILE, {});
510
+ const map = [];
511
+ for (const [agent, agentRatings] of Object.entries(ratings)) {
512
+ const overall = agentRatings._overall || { mu: INITIAL_MU, sigma: INITIAL_SIGMA };
513
+ // Classify status
514
+ let status;
515
+ if (overall.sigma <= 2)
516
+ status = 'proven';
517
+ else if (overall.sigma <= 5)
518
+ status = 'developing';
519
+ else
520
+ status = 'untested';
521
+ // Build category map (exclude _overall)
522
+ const categories = {};
523
+ for (const [cat, rating] of Object.entries(agentRatings)) {
524
+ if (cat === '_overall')
525
+ continue;
526
+ categories[cat] = {
527
+ mu: Math.round(rating.mu * 10) / 10,
528
+ sigma: Math.round(rating.sigma * 10) / 10,
529
+ };
530
+ }
531
+ // Confidence label
532
+ const confidence = overall.sigma <= 1 ? 'very high'
533
+ : overall.sigma <= 3 ? 'high'
534
+ : overall.sigma <= 5 ? 'medium'
535
+ : overall.sigma <= 7 ? 'low'
536
+ : 'untested';
537
+ map.push({
538
+ agent,
539
+ overall: {
540
+ mu: Math.round(overall.mu * 10) / 10,
541
+ sigma: Math.round(overall.sigma * 10) / 10,
542
+ confidence,
543
+ },
544
+ categories,
545
+ status,
546
+ });
547
+ }
548
+ // Sort by overall mu descending
549
+ map.sort((a, b) => b.overall.mu - a.overall.mu);
550
+ state.skillMap = map;
551
+ saveJSON(SKILL_MAP_FILE, map);
552
+ saveState(state);
553
+ return map;
554
+ }
555
+ /**
556
+ * Format skill map for terminal display.
557
+ */
558
+ export function formatSkillMap(map) {
559
+ if (map.length === 0)
560
+ return 'No skill data yet.';
561
+ const lines = [
562
+ '┌────────────────────────────────────────────────────────────┐',
563
+ '│ AGENT SKILL MAP (Bayesian μ ± 2σ) │',
564
+ '├──────────────────┬────────┬──────┬────────────┬───────────┤',
565
+ '│ Agent │ μ │ σ │ Confidence │ Status │',
566
+ '├──────────────────┼────────┼──────┼────────────┼───────────┤',
567
+ ];
568
+ let proven = 0;
569
+ let developing = 0;
570
+ let untested = 0;
571
+ for (const entry of map) {
572
+ const agentPad = entry.agent.padEnd(16).slice(0, 16);
573
+ const muPad = entry.overall.mu.toFixed(1).padStart(6);
574
+ const sigmaPad = entry.overall.sigma.toFixed(1).padStart(4);
575
+ const confPad = entry.overall.confidence.padEnd(10).slice(0, 10);
576
+ const statusIcon = entry.status === 'proven' ? '★' : entry.status === 'developing' ? '◆' : '○';
577
+ const statusPad = `${statusIcon} ${entry.status}`.padEnd(9);
578
+ lines.push(`│ ${agentPad} │ ${muPad} │ ${sigmaPad} │ ${confPad} │ ${statusPad} │`);
579
+ if (entry.status === 'proven')
580
+ proven++;
581
+ else if (entry.status === 'developing')
582
+ developing++;
583
+ else
584
+ untested++;
585
+ // Show top category specializations
586
+ const cats = Object.entries(entry.categories)
587
+ .sort((a, b) => b[1].mu - a[1].mu)
588
+ .slice(0, 2);
589
+ if (cats.length > 0) {
590
+ const catStr = cats.map(([c, r]) => `${c}:${r.mu.toFixed(1)}`).join(', ');
591
+ lines.push(`│ └─ ${catStr.padEnd(50).slice(0, 50)} │`);
592
+ }
593
+ }
594
+ lines.push('├──────────────────────────────────────────────────────────┤');
595
+ lines.push(`│ ★ Proven: ${proven} ◆ Developing: ${developing} ○ Untested: ${untested}`.padEnd(59) + '│');
596
+ lines.push('└────────────────────────────────────────────────────────────┘');
597
+ return lines.join('\n');
598
+ }
599
+ /**
600
+ * Analyze engagement outcomes from discovery actions
601
+ * and produce topic weights for the next opportunity cycle.
602
+ */
603
+ export function feedEngagementBack(discoveryDir) {
604
+ const state = loadState();
605
+ const postedFile = join(discoveryDir, 'actions', 'posted.json');
606
+ if (!existsSync(postedFile))
607
+ return state.topicWeights;
608
+ const posted = loadJSON(postedFile, []);
609
+ if (posted.length === 0)
610
+ return state.topicWeights;
611
+ // Count engagement by topic
612
+ const topicEngagement = {};
613
+ for (const entry of posted) {
614
+ const topic = entry.topic || 'general';
615
+ if (!topicEngagement[topic])
616
+ topicEngagement[topic] = { engaged: 0, ignored: 0 };
617
+ if (entry.engagement === 'engaged' || (entry.currentScore || 0) > (entry.scoreAtPost || 0)) {
618
+ topicEngagement[topic].engaged++;
619
+ }
620
+ else {
621
+ topicEngagement[topic].ignored++;
622
+ }
623
+ }
624
+ // Calculate weights
625
+ const weights = [];
626
+ for (const [topic, stats] of Object.entries(topicEngagement)) {
627
+ const total = stats.engaged + stats.ignored;
628
+ const weight = total > 0 ? stats.engaged / total : 0.5;
629
+ weights.push({
630
+ topic,
631
+ weight: Math.round(weight * 100) / 100,
632
+ engaged: stats.engaged,
633
+ ignored: stats.ignored,
634
+ updatedAt: new Date().toISOString(),
635
+ });
636
+ }
637
+ // Sort by weight descending
638
+ weights.sort((a, b) => b.weight - a.weight);
639
+ // Save for discovery daemon to consume
640
+ const topicWeightsFile = join(discoveryDir, 'topic-weights.json');
641
+ if (existsSync(discoveryDir)) {
642
+ saveJSON(topicWeightsFile, weights);
643
+ }
644
+ state.topicWeights = weights;
645
+ state.stats.engagementsFedBack += weights.length;
646
+ saveState(state);
647
+ return weights;
648
+ }
649
+ /**
650
+ * Run the full synthesis cycle:
651
+ * 1. Consume discovered tools
652
+ * 2. Instantiate proposed agents
653
+ * 3. Extract paper insights
654
+ * 4. Build active corrections
655
+ * 5. Close reflection loop
656
+ * 6. Cross-pollinate patterns
657
+ * 7. Build skill map
658
+ * 8. Feed engagement back
659
+ *
660
+ * All operations are heuristic — no LLM calls.
661
+ * Safe to call frequently — each operation is idempotent.
662
+ */
663
+ export function synthesize(discoveryDir) {
664
+ const state = loadState();
665
+ state.totalCycles++;
666
+ state.lastCycleAt = new Date().toISOString();
667
+ saveState(state);
668
+ // Auto-detect discovery dir
669
+ const discDir = discoveryDir || join(process.cwd(), '.kbot-discovery');
670
+ // Run all 8 operations
671
+ const toolAdoptions = existsSync(discDir) ? consumeDiscoveredTools(discDir) : [];
672
+ const agentTrials = existsSync(discDir) ? instantiateProposedAgents(discDir) : [];
673
+ const paperInsights = existsSync(discDir) ? extractPaperInsights(discDir) : [];
674
+ const activeCorrections = buildActiveCorrections();
675
+ const reflectionsClosed = closeReflectionLoop();
676
+ const patternsTransferred = crossPollinatePatterns();
677
+ const skillMap = buildSkillMap();
678
+ const topicWeights = existsSync(discDir) ? feedEngagementBack(discDir) : [];
679
+ return {
680
+ toolAdoptions,
681
+ agentTrials,
682
+ paperInsights,
683
+ activeCorrections,
684
+ reflectionsClosed,
685
+ patternsTransferred,
686
+ skillMap,
687
+ topicWeights,
688
+ cycleNumber: state.totalCycles,
689
+ };
690
+ }
691
+ // ══════════════════════════════════════════════════════════════════════
692
+ // Stats & Display
693
+ // ══════════════════════════════════════════════════════════════════════
694
+ /**
695
+ * Get synthesis engine stats for kbot status display.
696
+ */
697
+ export function getSynthesisEngineStats() {
698
+ const state = loadState();
699
+ return {
700
+ ...state.stats,
701
+ totalCycles: state.totalCycles,
702
+ lastCycleAt: state.lastCycleAt,
703
+ };
704
+ }
705
+ /**
706
+ * Format synthesis results for terminal display.
707
+ */
708
+ export function formatSynthesisResult(result) {
709
+ const lines = [
710
+ '═══════════════════════════════════════════════════════════════',
711
+ ` SYNTHESIS CYCLE #${result.cycleNumber}`,
712
+ '═══════════════════════════════════════════════════════════════',
713
+ '',
714
+ ];
715
+ // Tools
716
+ if (result.toolAdoptions.length > 0) {
717
+ lines.push(`## Discovered Tools (${result.toolAdoptions.length} evaluated)`);
718
+ const adopted = result.toolAdoptions.filter(t => t.status === 'adopted');
719
+ const rejected = result.toolAdoptions.filter(t => t.status === 'rejected');
720
+ if (adopted.length > 0) {
721
+ for (const t of adopted) {
722
+ lines.push(` + ADOPT: ${t.name} (${t.stars}★) — ${t.reason}`);
723
+ }
724
+ }
725
+ if (rejected.length > 0) {
726
+ lines.push(` - Rejected: ${rejected.length} tools (no match to failure patterns)`);
727
+ }
728
+ lines.push('');
729
+ }
730
+ // Agents
731
+ if (result.agentTrials.length > 0) {
732
+ lines.push(`## Agent Trials (${result.agentTrials.length} started)`);
733
+ for (const a of result.agentTrials) {
734
+ lines.push(` ◆ TRIAL: ${a.name} → ${a.targetCategory} (σ gap detected)`);
735
+ }
736
+ lines.push('');
737
+ }
738
+ // Papers
739
+ if (result.paperInsights.length > 0) {
740
+ lines.push(`## Paper Insights (${result.paperInsights.length} extracted)`);
741
+ for (const p of result.paperInsights) {
742
+ const icon = p.status === 'proposed' ? '→' : '×';
743
+ lines.push(` ${icon} "${p.technique}" from "${p.title.slice(0, 50)}..."`);
744
+ lines.push(` Applies to: ${p.applicableTo}`);
745
+ }
746
+ lines.push('');
747
+ }
748
+ // Corrections
749
+ if (result.activeCorrections.length > 0) {
750
+ lines.push(`## Active Corrections (${result.activeCorrections.length} injected into prompts)`);
751
+ for (const c of result.activeCorrections.slice(0, 5)) {
752
+ const icon = c.severity === 'high' ? '!!' : c.severity === 'medium' ? ' !' : ' -';
753
+ lines.push(` ${icon} ${c.rule.slice(0, 70)}...`);
754
+ }
755
+ lines.push('');
756
+ }
757
+ // Reflections
758
+ if (result.reflectionsClosed > 0) {
759
+ lines.push(`## Reflection → Routing: ${result.reflectionsClosed} skill ratings adjusted`);
760
+ lines.push('');
761
+ }
762
+ // Cross-pollination
763
+ if (result.patternsTransferred > 0) {
764
+ lines.push(`## Cross-Pollination: ${result.patternsTransferred} patterns transferred across projects`);
765
+ lines.push('');
766
+ }
767
+ // Skill Map
768
+ lines.push('## Skill Map');
769
+ lines.push(formatSkillMap(result.skillMap));
770
+ lines.push('');
771
+ // Topic Weights
772
+ if (result.topicWeights.length > 0) {
773
+ lines.push(`## Topic Weights (${result.topicWeights.length} topics scored)`);
774
+ for (const tw of result.topicWeights.slice(0, 5)) {
775
+ const bar = '█'.repeat(Math.round(tw.weight * 10)) + '░'.repeat(10 - Math.round(tw.weight * 10));
776
+ lines.push(` ${bar} ${tw.topic} (${tw.engaged} engaged / ${tw.ignored} ignored)`);
777
+ }
778
+ lines.push('');
779
+ }
780
+ lines.push('═══════════════════════════════════════════════════════════════');
781
+ return lines.join('\n');
782
+ }
783
+ //# sourceMappingURL=synthesis-engine.js.map