@kernel.chat/kbot 3.64.0 → 3.66.0

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,182 @@
1
+ // kbot Collective Dreams — Anonymized Dream Sharing
2
+ //
3
+ // The dream engine consolidates personal insights. This module prepares
4
+ // those insights for anonymous sharing with the collective, and merges
5
+ // collective wisdom back into the local journal.
6
+ //
7
+ // What gets shared (anonymized):
8
+ // - Dream category (pattern, preference, skill, project, music)
9
+ // - Keywords (stripped of names, paths, identifiers)
10
+ // - Generalized content (personal details removed, abstracted)
11
+ //
12
+ // What NEVER gets shared:
13
+ // - Raw insight content, file paths, project names, user identity
14
+ // - API keys, source code, conversation content
15
+ // - Anything from ~/.kbot/config.json
16
+ //
17
+ // The flywheel:
18
+ // Your dreams → anonymized → collective pool
19
+ // Collective pool → filtered, deduplicated → enriches your journal
20
+ // Every kbot dreamer makes every other kbot smarter.
21
+ import { createHash } from 'node:crypto';
22
+ // ── PII Stripping ──
23
+ /** Patterns that indicate PII or project-specific content */
24
+ const PII_PATTERNS = [
25
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // emails
26
+ /\b(?:\/[^\s/]+){2,}\b/g, // file paths
27
+ /\b(?:https?:\/\/)[^\s]+/g, // URLs
28
+ /\b(?:sk-|pk-|key-|token-|ghp_|gho_|kn_)[A-Za-z0-9_-]+/g, // API keys/tokens
29
+ /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, // UUIDs
30
+ /\b(?:Isaac|openclaw)\b/gi, // known user names
31
+ ];
32
+ /** Words that should be stripped from keywords (project-specific, PII-ish) */
33
+ const KEYWORD_BLOCKLIST = new Set([
34
+ 'isaac', 'openclaw', 'kernel', 'kbot', 'supabase',
35
+ 'eoxxpyixdieprsxlpwcs', 'isaacsight', 'isaachernandez',
36
+ ]);
37
+ /** Strip PII from a text string, replacing matches with generic placeholders */
38
+ function stripPII(text) {
39
+ let cleaned = text;
40
+ for (const pattern of PII_PATTERNS) {
41
+ cleaned = cleaned.replace(pattern, '[REDACTED]');
42
+ }
43
+ return cleaned;
44
+ }
45
+ /** Remove blocklisted and PII-ish keywords */
46
+ function sanitizeKeywords(keywords) {
47
+ return keywords
48
+ .map(k => k.toLowerCase().trim())
49
+ .filter(k => k.length >= 2 &&
50
+ k.length <= 30 &&
51
+ !KEYWORD_BLOCKLIST.has(k) &&
52
+ !/^[0-9a-f-]{8,}$/.test(k) && // hex strings / UUIDs
53
+ !/[@/\\]/.test(k) // paths / emails
54
+ );
55
+ }
56
+ // ── Generalization ──
57
+ /**
58
+ * Generalize insight content by:
59
+ * 1. Stripping PII
60
+ * 2. Replacing specific project/file references with generic terms
61
+ * 3. Keeping the abstract pattern intact
62
+ */
63
+ function generalizeContent(content) {
64
+ let text = stripPII(content);
65
+ // Replace specific filenames with generic references
66
+ text = text.replace(/\b\w+\.(ts|tsx|js|jsx|py|rs|go|css|html|json|yaml|yml|toml)\b/g, '[file]');
67
+ // Replace specific variable/function names that look project-specific
68
+ // (camelCase or snake_case longer than 15 chars — likely domain-specific)
69
+ text = text.replace(/\b[a-z][a-zA-Z0-9]{15,}\b/g, '[identifier]');
70
+ text = text.replace(/\b[a-z][a-z0-9_]{15,}\b/g, '[identifier]');
71
+ // Collapse multiple [REDACTED] into one
72
+ text = text.replace(/(\[REDACTED\]\s*)+/g, '[REDACTED] ');
73
+ return text.trim();
74
+ }
75
+ // ── Core Functions ──
76
+ /**
77
+ * Anonymize a single dream insight for collective sharing.
78
+ * Strips personal info, keeps only category, keywords, and generalized content.
79
+ */
80
+ export function anonymizeDreamInsight(insight) {
81
+ const now = new Date().toISOString();
82
+ return {
83
+ category: insight.category,
84
+ keywords: sanitizeKeywords(insight.keywords),
85
+ generalizedContent: generalizeContent(insight.content),
86
+ contributorCount: 1,
87
+ firstSeen: now,
88
+ lastSeen: now,
89
+ };
90
+ }
91
+ /**
92
+ * Prepare a batch of dream insights for collective sharing.
93
+ * 1. Filter to high-relevance insights (> 0.7)
94
+ * 2. Anonymize each
95
+ * 3. Deduplicate by content similarity
96
+ */
97
+ export function prepareCollectiveDreams(insights) {
98
+ // Step 1: Filter to high-relevance only
99
+ const highRelevance = insights.filter(i => i.relevance > 0.7);
100
+ if (highRelevance.length === 0)
101
+ return [];
102
+ // Step 2: Anonymize
103
+ const anonymized = highRelevance.map(i => anonymizeDreamInsight(i));
104
+ // Step 3: Deduplicate by content hash
105
+ const seen = new Map();
106
+ for (const insight of anonymized) {
107
+ // Skip if generalized content is too short or entirely redacted
108
+ if (insight.generalizedContent.length < 10)
109
+ continue;
110
+ if (insight.generalizedContent.replace(/\[REDACTED\]/g, '').trim().length < 10)
111
+ continue;
112
+ const hash = createHash('sha256')
113
+ .update(insight.category + ':' + insight.generalizedContent.toLowerCase().replace(/\s+/g, ' '))
114
+ .digest('hex')
115
+ .slice(0, 16);
116
+ if (!seen.has(hash)) {
117
+ seen.set(hash, insight);
118
+ }
119
+ else {
120
+ // Merge: bump contributor count, keep the one with more keywords
121
+ const existing = seen.get(hash);
122
+ existing.contributorCount++;
123
+ if (insight.keywords.length > existing.keywords.length) {
124
+ existing.keywords = insight.keywords;
125
+ }
126
+ }
127
+ }
128
+ return Array.from(seen.values());
129
+ }
130
+ /**
131
+ * Merge collective wisdom into the local dream journal.
132
+ *
133
+ * Collective insights are injected with a lower base relevance (0.5) so they
134
+ * don't drown out the user's own insights but still surface when relevant.
135
+ * Deduplicates against existing local insights by content similarity.
136
+ */
137
+ export function mergeCollectiveDreams(local, collective) {
138
+ const merged = [...local];
139
+ const now = new Date().toISOString();
140
+ // Build a set of content hashes from local insights for dedup
141
+ const localHashes = new Set();
142
+ for (const insight of local) {
143
+ const hash = createHash('sha256')
144
+ .update(insight.category + ':' + insight.content.toLowerCase().replace(/\s+/g, ' ').slice(0, 100))
145
+ .digest('hex')
146
+ .slice(0, 16);
147
+ localHashes.add(hash);
148
+ }
149
+ for (const collective_insight of collective) {
150
+ // Skip low-quality collective insights
151
+ if (collective_insight.generalizedContent.length < 10)
152
+ continue;
153
+ if (collective_insight.contributorCount < 2)
154
+ continue; // Need at least 2 contributors
155
+ // Check for duplication against local
156
+ const hash = createHash('sha256')
157
+ .update(collective_insight.category + ':' +
158
+ collective_insight.generalizedContent.toLowerCase().replace(/\s+/g, ' ').slice(0, 100))
159
+ .digest('hex')
160
+ .slice(0, 16);
161
+ if (localHashes.has(hash))
162
+ continue;
163
+ // Convert to DreamInsight with lower relevance
164
+ const dreamInsight = {
165
+ id: `collective_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
166
+ content: collective_insight.generalizedContent,
167
+ category: collective_insight.category,
168
+ keywords: collective_insight.keywords,
169
+ relevance: 0.5, // Lower base relevance for collective insights
170
+ sessions: collective_insight.contributorCount,
171
+ created: collective_insight.firstSeen,
172
+ lastReinforced: collective_insight.lastSeen,
173
+ source: `collective:${collective_insight.contributorCount}_contributors`,
174
+ };
175
+ merged.push(dreamInsight);
176
+ localHashes.add(hash); // Prevent duplicates within the collective batch
177
+ }
178
+ // Sort by relevance descending — local insights (higher relevance) surface first
179
+ merged.sort((a, b) => b.relevance - a.relevance);
180
+ return merged;
181
+ }
182
+ //# sourceMappingURL=collective-dreams.js.map
package/dist/dream.d.ts CHANGED
@@ -18,7 +18,7 @@ export interface DreamInsight {
18
18
  /** Source: which sessions/topics generated this */
19
19
  source: string;
20
20
  }
21
- export type DreamCategory = 'pattern' | 'preference' | 'skill' | 'project' | 'relationship';
21
+ export type DreamCategory = 'pattern' | 'preference' | 'skill' | 'project' | 'relationship' | 'music';
22
22
  export interface DreamState {
23
23
  /** Total dream cycles completed */
24
24
  cycles: number;
@@ -38,6 +38,23 @@ export declare function ageMemories(insights: DreamInsight[]): {
38
38
  aged: DreamInsight[];
39
39
  archived: DreamInsight[];
40
40
  };
41
+ /**
42
+ * Apply dream insights back into the learning system.
43
+ * This is the feedback loop that makes the memory cascade bidirectional:
44
+ * - "preference" insights → update user profile via learnFact()
45
+ * - "pattern" insights → hint the pattern cache via recordPattern()
46
+ * - "skill" insights → record as observed knowledge
47
+ * - "project" insights → record as project context
48
+ *
49
+ * Called at the end of every dream cycle after new insights are extracted.
50
+ */
51
+ export declare function applyDreamInsights(insights: DreamInsight[]): ApplyResult;
52
+ export interface ApplyResult {
53
+ preferencesApplied: number;
54
+ patternsHinted: number;
55
+ factsLearned: number;
56
+ promptAmendments: number;
57
+ }
41
58
  /** Run a full dream cycle — consolidate, reinforce, age */
42
59
  export declare function dream(sessionId?: string): Promise<DreamResult>;
43
60
  export interface DreamResult {
@@ -48,6 +65,8 @@ export interface DreamResult {
48
65
  cycle: number;
49
66
  duration: number;
50
67
  error: string | null;
68
+ /** Feedback from applying insights back into learning tiers */
69
+ applied: ApplyResult | null;
51
70
  }
52
71
  /** Get dream insights for inclusion in system prompt */
53
72
  export declare function getDreamPrompt(maxInsights?: number): string;
package/dist/dream.js CHANGED
@@ -19,6 +19,10 @@ import { join } from 'node:path';
19
19
  import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
20
20
  import { getHistory } from './memory.js';
21
21
  import { loadMemory } from './memory.js';
22
+ import { getTopPatterns, getTopSolutions, getProfileSummary, updateProfile, recordPattern, learnFact, } from './learning.js';
23
+ import { getMemoryScannerStats } from './memory-scanner.js';
24
+ import { getLearningReport as getMusicLearningReport, getRecentHistory as getMusicRecentHistory, getPreferences as getMusicPreferences, } from './music-learning.js';
25
+ import { registerAmendment } from './prompt-evolution.js';
22
26
  // ── Constants ──
23
27
  const DREAM_DIR = join(homedir(), '.kbot', 'memory', 'dreams');
24
28
  const JOURNAL_FILE = join(DREAM_DIR, 'journal.json');
@@ -149,28 +153,90 @@ function buildConsolidationPrompt(sessionHistory, existingInsights, existingMemo
149
153
  const existingText = existingInsights.length > 0
150
154
  ? existingInsights.slice(0, 20).map(i => `- [${i.category}] ${i.content}`).join('\n')
151
155
  : '(none yet)';
152
- return `You are a memory consolidation system. Analyze this conversation session and extract durable insights.
156
+ // ── Tier 1: Pattern cache intent tool sequences ──
157
+ const topPatterns = getTopPatterns(10);
158
+ const patternsText = topPatterns.length > 0
159
+ ? topPatterns.map(p => `- "${p.intent}" → ${p.toolSequence.join(' → ')} (${p.hits}x, ${Math.round(p.successRate * 100)}% success)`).join('\n')
160
+ : '(no patterns yet)';
161
+ // ── Tier 2: Solution index — Q&A pairs ──
162
+ const topSolutions = getTopSolutions(5);
163
+ const solutionsText = topSolutions.length > 0
164
+ ? topSolutions.map(s => `- Q: ${s.question.slice(0, 100)} → confidence: ${Math.round(s.confidence * 100)}%, reused ${s.reuses}x`).join('\n')
165
+ : '(no solutions yet)';
166
+ // ── Tier 3: User profile ──
167
+ const profileText = getProfileSummary() || '(no profile data yet)';
168
+ // ── Tier 5: Memory scanner — recent passive detections ──
169
+ const scannerStats = getMemoryScannerStats();
170
+ const scannerText = scannerStats.recentDetections.length > 0
171
+ ? scannerStats.recentDetections
172
+ .slice(-5)
173
+ .map(d => `- [${d.kind}] ${d.content.slice(0, 150)} (confidence: ${Math.round(d.confidence * 100)}%)`)
174
+ .join('\n')
175
+ : '(no recent detections)';
176
+ // ── Tier 6: Music learning — sound, pattern, mix memories ──
177
+ const musicPrefs = getMusicPreferences();
178
+ const hasMusicData = musicPrefs.totalBeats > 0 || musicPrefs.totalSessions > 0;
179
+ let musicText = '(no music production data yet)';
180
+ if (hasMusicData) {
181
+ const report = getMusicLearningReport();
182
+ const recentEvents = getMusicRecentHistory(10);
183
+ const recentText = recentEvents.length > 0
184
+ ? recentEvents.map(e => `- [${e.action}] ${e.genre} ${e.key} ${e.bpm}bpm — ${e.detail || 'no detail'} (${e.feedback})`).join('\n')
185
+ : '';
186
+ musicText = report + (recentText ? `\n\n**Recent production events:**\n${recentText}` : '');
187
+ }
188
+ return `You are a memory consolidation system. Analyze this conversation session and ALL accumulated knowledge tiers to extract durable cross-tier insights.
153
189
 
154
- EXISTING INSIGHTS:
190
+ EXISTING DREAM INSIGHTS (Tier 4 — Dream Journal):
155
191
  ${existingText}
156
192
 
157
193
  EXISTING PERSISTENT MEMORY:
158
194
  ${existingMemory.slice(0, 2000) || '(empty)'}
159
195
 
196
+ LEARNED PATTERNS (Tier 1 — Pattern Cache):
197
+ ${patternsText}
198
+
199
+ PROVEN SOLUTIONS (Tier 2 — Solution Index):
200
+ ${solutionsText}
201
+
202
+ USER PROFILE (Tier 3):
203
+ ${profileText}
204
+
205
+ RECENT MEMORY SCANNER DETECTIONS (Tier 5 — Passive Detection):
206
+ ${scannerText}
207
+
208
+ MUSIC PRODUCTION LEARNING (Tier 6 — Musical Memory):
209
+ ${musicText}
210
+
160
211
  SESSION TO CONSOLIDATE:
161
212
  ${historyText}
162
213
 
163
214
  INSTRUCTIONS:
164
- Extract 1-5 insights from this session. Each insight should be:
215
+ Extract 1-5 insights by synthesizing across ALL tiers. Each insight should be:
165
216
  - Durable (useful beyond this session)
166
217
  - Non-obvious (not derivable from reading code)
167
- - About the USER, their preferences, patterns, or project context
218
+ - Cross-tier when possible (e.g., a pattern + preference = workflow insight)
219
+ - About the USER, their preferences, patterns, workflows, or project context
220
+
221
+ For music/production sessions, pay special attention to:
222
+ - Which sounds, instruments, and presets scored well and why
223
+ - BPM + key + genre combinations that the user gravitates toward
224
+ - Mix decisions that worked (volume balance, send levels, panning)
225
+ - Production patterns (e.g., "808 sub bass in F1 works well at 142 BPM for trap")
226
+ - Cross-domain insights (e.g., coding workflow preferences that mirror production habits)
227
+ Use category "music" for production-specific insights.
228
+
229
+ Pay special attention to:
230
+ - Patterns that confirm or contradict existing insights
231
+ - Scanner corrections that reveal unrecognized preferences
232
+ - Solution clusters that suggest emerging expertise areas
233
+ - Profile trends that indicate shifting priorities
168
234
 
169
235
  Format each insight as a JSON array of objects:
170
236
  [
171
237
  {
172
238
  "content": "the insight text",
173
- "category": "pattern|preference|skill|project|relationship",
239
+ "category": "pattern|preference|skill|project|relationship|music",
174
240
  "keywords": ["keyword1", "keyword2"]
175
241
  }
176
242
  ]
@@ -199,6 +265,196 @@ If none are reinforced, return: []
199
265
 
200
266
  Respond ONLY with the JSON array.`;
201
267
  }
268
+ // ── Memory Cascade: Feed Insights Back into Tiers ──
269
+ /**
270
+ * Apply dream insights back into the learning system.
271
+ * This is the feedback loop that makes the memory cascade bidirectional:
272
+ * - "preference" insights → update user profile via learnFact()
273
+ * - "pattern" insights → hint the pattern cache via recordPattern()
274
+ * - "skill" insights → record as observed knowledge
275
+ * - "project" insights → record as project context
276
+ *
277
+ * Called at the end of every dream cycle after new insights are extracted.
278
+ */
279
+ export function applyDreamInsights(insights) {
280
+ const result = {
281
+ preferencesApplied: 0,
282
+ patternsHinted: 0,
283
+ factsLearned: 0,
284
+ promptAmendments: 0,
285
+ };
286
+ for (const insight of insights) {
287
+ switch (insight.category) {
288
+ case 'preference': {
289
+ // Feed preference insights back into the knowledge base as observed facts.
290
+ // This lets the learning context builder surface them in future prompts.
291
+ learnFact(insight.content, 'preference', 'observed');
292
+ // Also nudge the user profile if the insight mentions style/tech preferences
293
+ const lower = insight.content.toLowerCase();
294
+ if (/\b(?:concise|brief|short)\b/.test(lower)) {
295
+ updateProfile({ taskType: 'prefers-concise' });
296
+ }
297
+ else if (/\b(?:detailed|thorough|verbose)\b/.test(lower)) {
298
+ updateProfile({ taskType: 'prefers-detailed' });
299
+ }
300
+ // Extract any tech terms mentioned in the preference
301
+ const techTerms = extractTechTermsFromInsight(insight.content);
302
+ if (techTerms.length > 0) {
303
+ updateProfile({ techTerms });
304
+ }
305
+ // High-relevance preference insights → prompt evolution amendments.
306
+ // This wires dream consolidation into the GEPA prompt evolution system
307
+ // so kbot's specialist prompts self-improve based on learned behavior.
308
+ if (insight.relevance > 0.8) {
309
+ const amendment = insightToAmendment(insight.content);
310
+ if (amendment) {
311
+ // Target the 'kernel' agent (general) — the preference applies broadly.
312
+ // Tag with the dream insight ID for traceability / rollback.
313
+ registerAmendment('kernel', amendment, `dream preference: ${insight.content.slice(0, 80)}`, insight.id);
314
+ result.promptAmendments++;
315
+ }
316
+ }
317
+ result.preferencesApplied++;
318
+ break;
319
+ }
320
+ case 'pattern': {
321
+ // Feed pattern insights into the knowledge base.
322
+ // If the insight describes a tool workflow, hint the pattern cache.
323
+ learnFact(insight.content, 'context', 'observed');
324
+ // Try to extract a tool sequence from the insight text
325
+ // (e.g., "User typically reads files then runs tests" → [read_file, run_tests])
326
+ const toolHints = extractToolHintsFromInsight(insight.content);
327
+ if (toolHints.length >= 2) {
328
+ // Record as a pattern with the insight content as the "intent"
329
+ const intentWords = insight.keywords.join(' ') || insight.content.slice(0, 80);
330
+ recordPattern(intentWords, toolHints, 0);
331
+ }
332
+ result.patternsHinted++;
333
+ break;
334
+ }
335
+ case 'skill': {
336
+ // Skills are knowledge about what the user is good at or learning.
337
+ learnFact(insight.content, 'context', 'observed');
338
+ result.factsLearned++;
339
+ break;
340
+ }
341
+ case 'project': {
342
+ // Project insights become project-scoped knowledge.
343
+ learnFact(insight.content, 'project', 'observed');
344
+ result.factsLearned++;
345
+ break;
346
+ }
347
+ case 'relationship': {
348
+ // Relationship insights (how user interacts, team dynamics) → context facts.
349
+ learnFact(insight.content, 'context', 'observed');
350
+ result.factsLearned++;
351
+ break;
352
+ }
353
+ case 'music': {
354
+ // Music production insights → context facts.
355
+ // These capture durable knowledge like "808 sub in F1 at 142 BPM works for trap"
356
+ // and feed back into the learning system for future prompt enrichment.
357
+ learnFact(insight.content, 'context', 'observed');
358
+ result.factsLearned++;
359
+ break;
360
+ }
361
+ }
362
+ }
363
+ return result;
364
+ }
365
+ /**
366
+ * Convert a preference insight into a concrete prompt amendment.
367
+ * Maps common preference patterns to actionable instructions for the agent.
368
+ * Returns null if no actionable amendment can be derived.
369
+ */
370
+ function insightToAmendment(insightContent) {
371
+ const lower = insightContent.toLowerCase();
372
+ // Speed / action-oriented preferences
373
+ if (/\b(?:speed|fast|quick|ship|action|just do it|don't ask)\b/.test(lower)) {
374
+ return 'Be action-oriented. Ship first, polish later. Don\'t ask for permission on non-destructive operations.';
375
+ }
376
+ // Conciseness preferences
377
+ if (/\b(?:concise|brief|short|terse|no fluff|straight to the point)\b/.test(lower)) {
378
+ return 'Keep responses concise. Lead with the answer or action. Skip preambles and restating the question.';
379
+ }
380
+ // Detail / thoroughness preferences
381
+ if (/\b(?:detailed|thorough|explain|verbose|show work|step.by.step)\b/.test(lower)) {
382
+ return 'Be thorough in explanations. Show reasoning steps. Include context and alternatives when relevant.';
383
+ }
384
+ // Code-first preferences
385
+ if (/\b(?:code.first|show.code|less.talk|implementation|no.theory)\b/.test(lower)) {
386
+ return 'Lead with code. Show the implementation first, then explain only if the user asks.';
387
+ }
388
+ // Terminal / CLI preferences
389
+ if (/\b(?:terminal|cli|command.line|shell|no.gui|no.web)\b/.test(lower)) {
390
+ return 'Prefer terminal-based solutions. Use CLI tools over web interfaces. Everything should be scriptable.';
391
+ }
392
+ // Safety / careful preferences
393
+ if (/\b(?:careful|safe|confirm|check.first|verify|double.check)\b/.test(lower)) {
394
+ return 'Verify before acting. Confirm destructive operations. Read files before editing. Run builds after changes.';
395
+ }
396
+ // Exploration / creative preferences
397
+ if (/\b(?:creative|explore|try.new|experiment|novel|unconventional)\b/.test(lower)) {
398
+ return 'Explore creative solutions. Consider unconventional approaches. Suggest alternatives the user might not have considered.';
399
+ }
400
+ // Fallback: use the insight content directly as a general behavioral nudge
401
+ // Only if the insight is short enough to be a useful prompt instruction
402
+ if (insightContent.length <= 200) {
403
+ return `User preference: ${insightContent}`;
404
+ }
405
+ return null;
406
+ }
407
+ /** Extract tech-related terms from insight text */
408
+ function extractTechTermsFromInsight(text) {
409
+ const techTerms = new Set([
410
+ 'react', 'typescript', 'node', 'python', 'rust', 'go', 'docker',
411
+ 'api', 'database', 'supabase', 'postgres', 'redis', 'mongodb',
412
+ 'css', 'html', 'json', 'sql', 'git', 'npm', 'vite', 'webpack',
413
+ 'tailwind', 'next', 'express', 'fastify', 'deno', 'bun',
414
+ 'playwright', 'vitest', 'jest', 'eslint', 'prettier',
415
+ 'ollama', 'anthropic', 'openai', 'claude', 'gpt',
416
+ 'ableton', 'serum', 'splice', 'osc', 'midi',
417
+ ]);
418
+ return text.toLowerCase()
419
+ .replace(/[^a-z0-9\s]/g, ' ')
420
+ .split(/\s+/)
421
+ .filter(w => techTerms.has(w));
422
+ }
423
+ /** Try to extract tool names from insight text describing a workflow */
424
+ function extractToolHintsFromInsight(text) {
425
+ const toolNames = new Set([
426
+ 'read_file', 'write_file', 'edit_file', 'glob', 'grep', 'bash',
427
+ 'git_status', 'git_diff', 'git_commit', 'git_push', 'git_log',
428
+ 'run_tests', 'test_run', 'build_run', 'type_check', 'lint_check',
429
+ 'web_search', 'url_fetch', 'screenshot', 'browser_navigate',
430
+ 'kbot_agent', 'spawn_agent', 'memory_save', 'memory_search',
431
+ 'research', 'papers_search', 'github_search',
432
+ ]);
433
+ // Match tool-like words (snake_case) in the text
434
+ const words = text.toLowerCase().replace(/[^a-z0-9_\s]/g, ' ').split(/\s+/);
435
+ const found = words.filter(w => toolNames.has(w));
436
+ if (found.length >= 2)
437
+ return found;
438
+ // Fallback: look for verb patterns that map to common tools
439
+ const verbMap = [
440
+ [/\bread(?:s|ing)?\s+(?:file|code|source)/i, 'read_file'],
441
+ [/\bwrit(?:e|es|ing)\s+(?:file|code)/i, 'write_file'],
442
+ [/\bedit(?:s|ing)?\b/i, 'edit_file'],
443
+ [/\bsearch(?:es|ing)?\s+(?:web|online|internet)/i, 'web_search'],
444
+ [/\brun(?:s|ning)?\s+test/i, 'run_tests'],
445
+ [/\bgrep(?:s|ping)?\b/i, 'grep'],
446
+ [/\bgit\s+(?:commit|push|diff|status|log)/i, 'git_commit'],
447
+ [/\bbuild(?:s|ing)?\b/i, 'build_run'],
448
+ [/\bbash\b|\bshell\b|\bcommand\b/i, 'bash'],
449
+ ];
450
+ const mapped = [];
451
+ for (const [pattern, tool] of verbMap) {
452
+ if (pattern.test(text) && !mapped.includes(tool)) {
453
+ mapped.push(tool);
454
+ }
455
+ }
456
+ return mapped;
457
+ }
202
458
  // ── Core Dream Functions ──
203
459
  /** Run a full dream cycle — consolidate, reinforce, age */
204
460
  export async function dream(sessionId = 'default') {
@@ -210,6 +466,7 @@ export async function dream(sessionId = 'default') {
210
466
  cycle: 0,
211
467
  duration: 0,
212
468
  error: null,
469
+ applied: null,
213
470
  };
214
471
  const start = Date.now();
215
472
  // Check Ollama availability
@@ -241,12 +498,15 @@ export async function dream(sessionId = 'default') {
241
498
  archiveInsights(archived);
242
499
  journal = aged;
243
500
  result.archived = archived.length;
244
- // Phase 2: Extract new insights from session
501
+ // Phase 2: Extract new insights from session (cross-tier consolidation)
245
502
  const consolidationPrompt = buildConsolidationPrompt(history, journal, memory);
246
503
  const rawInsights = await ollamaGenerate(consolidationPrompt);
504
+ const newlyCreatedInsights = [];
247
505
  if (rawInsights) {
248
506
  try {
249
- const parsed = JSON.parse(rawInsights);
507
+ // Strip markdown code fences if Ollama wraps the JSON
508
+ const cleaned = rawInsights.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
509
+ const parsed = JSON.parse(cleaned);
250
510
  if (Array.isArray(parsed)) {
251
511
  const now = new Date().toISOString();
252
512
  for (const p of parsed.slice(0, 5)) {
@@ -255,7 +515,7 @@ export async function dream(sessionId = 'default') {
255
515
  p.content.toLowerCase().includes(j.content.toLowerCase().slice(0, 50)));
256
516
  if (isDupe)
257
517
  continue;
258
- journal.push({
518
+ const insight = {
259
519
  id: generateId(),
260
520
  content: p.content,
261
521
  category: p.category || 'pattern',
@@ -265,7 +525,9 @@ export async function dream(sessionId = 'default') {
265
525
  created: now,
266
526
  lastReinforced: now,
267
527
  source: `session_${state.cycles + 1}`,
268
- });
528
+ };
529
+ journal.push(insight);
530
+ newlyCreatedInsights.push(insight);
269
531
  result.newInsights++;
270
532
  }
271
533
  }
@@ -280,7 +542,8 @@ export async function dream(sessionId = 'default') {
280
542
  const rawReinforce = await ollamaGenerate(reinforcePrompt);
281
543
  if (rawReinforce) {
282
544
  try {
283
- const indices = JSON.parse(rawReinforce);
545
+ const cleanedR = rawReinforce.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
546
+ const indices = JSON.parse(cleanedR);
284
547
  if (Array.isArray(indices)) {
285
548
  const now = new Date().toISOString();
286
549
  for (const idx of indices) {
@@ -306,6 +569,12 @@ export async function dream(sessionId = 'default') {
306
569
  journal = journal.slice(0, MAX_INSIGHTS);
307
570
  result.archived += overflow.length;
308
571
  }
572
+ // Phase 5: Feed new insights back into learning tiers (memory cascade)
573
+ // This is the key integration — dream insights don't just sit in the journal,
574
+ // they propagate back into the pattern cache, solution index, and user profile.
575
+ if (newlyCreatedInsights.length > 0) {
576
+ result.applied = applyDreamInsights(newlyCreatedInsights);
577
+ }
309
578
  // Save everything
310
579
  saveJournal(journal);
311
580
  state.cycles++;
@@ -179,5 +179,11 @@ export declare function getExtendedStats(): LearningStats & {
179
179
  projectsCount: number;
180
180
  topKnowledge: string[];
181
181
  };
182
+ /** Get the top N patterns from the pattern cache, ranked by effectiveness */
183
+ export declare function getTopPatterns(n?: number): CachedPattern[];
184
+ /** Get the top N solutions from the solution index, ranked by confidence and reuse */
185
+ export declare function getTopSolutions(n?: number): CachedSolution[];
186
+ /** Get a text summary of the user profile for consolidation prompts */
187
+ export declare function getProfileSummary(): string;
182
188
  export {};
183
189
  //# sourceMappingURL=learning.d.ts.map
package/dist/learning.js CHANGED
@@ -878,4 +878,45 @@ export function getExtendedStats() {
878
878
  .map(k => k.fact.slice(0, 80)),
879
879
  };
880
880
  }
881
+ // ═══ 12. MEMORY CASCADE ACCESSORS ══════════════════════════════
882
+ // Read-only accessors for the dream engine to pull data from all tiers.
883
+ /** Get the top N patterns from the pattern cache, ranked by effectiveness */
884
+ export function getTopPatterns(n = 10) {
885
+ return [...patterns]
886
+ .sort((a, b) => (b.hits * b.successRate) - (a.hits * a.successRate))
887
+ .slice(0, n);
888
+ }
889
+ /** Get the top N solutions from the solution index, ranked by confidence and reuse */
890
+ export function getTopSolutions(n = 5) {
891
+ return [...solutions]
892
+ .sort((a, b) => (b.confidence * (b.reuses + 1)) - (a.confidence * (a.reuses + 1)))
893
+ .slice(0, n);
894
+ }
895
+ /** Get a text summary of the user profile for consolidation prompts */
896
+ export function getProfileSummary() {
897
+ const parts = [];
898
+ if (profile.techStack.length > 0) {
899
+ parts.push(`Tech stack: ${profile.techStack.join(', ')}`);
900
+ }
901
+ if (profile.responseStyle !== 'auto') {
902
+ parts.push(`Response style: ${profile.responseStyle}`);
903
+ }
904
+ const topTasks = Object.entries(profile.taskPatterns)
905
+ .sort((a, b) => b[1] - a[1])
906
+ .slice(0, 5);
907
+ if (topTasks.length > 0) {
908
+ parts.push(`Task patterns: ${topTasks.map(([t, n]) => `${t}(${n}x)`).join(', ')}`);
909
+ }
910
+ const topAgents = Object.entries(profile.preferredAgents)
911
+ .sort((a, b) => b[1] - a[1])
912
+ .slice(0, 3);
913
+ if (topAgents.length > 0) {
914
+ parts.push(`Preferred agents: ${topAgents.map(([a, n]) => `${a}(${n}x)`).join(', ')}`);
915
+ }
916
+ parts.push(`Sessions: ${profile.sessions}, Messages: ${profile.totalMessages}`);
917
+ if (profile.tokensSaved > 0) {
918
+ parts.push(`Tokens saved by learning: ${profile.tokensSaved}`);
919
+ }
920
+ return parts.join('\n');
921
+ }
881
922
  //# sourceMappingURL=learning.js.map
@@ -85,6 +85,15 @@ export declare function getEvolutionStats(): {
85
85
  * Useful for debugging or when a major prompt rewrite happens.
86
86
  */
87
87
  export declare function resetEvolution(agent?: string): void;
88
+ /**
89
+ * Register an external prompt amendment (e.g. from the dream engine).
90
+ * This allows other subsystems to inject mutations into the evolution state
91
+ * without going through the trace-based evolvePrompt() pipeline.
92
+ *
93
+ * The mutation is tagged with an optional sourceId for traceability
94
+ * (e.g. the dream insight ID that produced it).
95
+ */
96
+ export declare function registerAmendment(agent: string, amendment: string, reason: string, sourceId?: string): PromptMutation;
88
97
  /**
89
98
  * Flush pending state to disk. Call on process exit.
90
99
  */