@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.
- package/README.md +35 -6
- package/dist/agent.js +8 -0
- package/dist/cli.js +230 -31
- package/dist/collective-dreams.d.ts +36 -0
- package/dist/collective-dreams.js +182 -0
- package/dist/dream.d.ts +20 -1
- package/dist/dream.js +279 -10
- package/dist/learning.d.ts +6 -0
- package/dist/learning.js +41 -0
- package/dist/prompt-evolution.d.ts +9 -0
- package/dist/prompt-evolution.js +28 -0
- package/dist/tools/audit.d.ts +2 -1
- package/dist/tools/audit.js +127 -4
- package/dist/tools/collective-dream-tools.d.ts +2 -0
- package/dist/tools/collective-dream-tools.js +109 -0
- package/dist/tools/index.js +1 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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++;
|
package/dist/learning.d.ts
CHANGED
|
@@ -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
|
*/
|