@aleph-ai/tinyaleph 1.0.2 → 1.2.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 +449 -2
- package/core/entanglement.js +712 -0
- package/core/events.js +907 -0
- package/core/hypercomplex.js +500 -0
- package/core/index.js +129 -1
- package/core/lambda.js +845 -0
- package/core/reduction.js +741 -0
- package/core/rformer-layers.js +811 -0
- package/core/types.js +913 -0
- package/docs/README.md +84 -0
- package/docs/design/ALEPH_CHAT_ARCHITECTURE.md +1 -1
- package/docs/design/AUTONOMOUS_LEARNING_DESIGN.md +1492 -0
- package/docs/design/WHITEPAPER_GAP_ANALYSIS.md +171 -4
- package/docs/reference/01-core.md +515 -1
- package/docs/reference/02-physics.md +186 -1
- package/docs/reference/README.md +277 -1
- package/docs/theory/03-phase-synchronization.md +196 -0
- package/docs/theory/README.md +47 -0
- package/package.json +2 -2
- package/physics/index.js +76 -10
- package/physics/primeon_z_ladder_multi.js +669 -0
- package/physics/primeon_z_ladder_u.js +493 -0
- package/physics/stochastic-kuramoto.js +566 -0
- package/physics/sync-models.js +770 -0
|
@@ -0,0 +1,1492 @@
|
|
|
1
|
+
|
|
2
|
+
# Autonomous Learning System Design
|
|
3
|
+
|
|
4
|
+
## Overview
|
|
5
|
+
|
|
6
|
+
This document specifies the design for an **Autonomous Learning Mode** where the Sentient Observer can learn independently in the background, supervised by a **Chaperone LLM** that mediates all external data access. Users can "eavesdrop" on the learning process in real-time through the web UI.
|
|
7
|
+
|
|
8
|
+
## Architecture Diagram
|
|
9
|
+
|
|
10
|
+
```mermaid
|
|
11
|
+
graph TB
|
|
12
|
+
subgraph User["User Interface"]
|
|
13
|
+
WebUI["Web UI<br/>Eavesdrop Panel"]
|
|
14
|
+
CLI["CLI<br/>--eavesdrop mode"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
subgraph Learner["Autonomous Learner"]
|
|
18
|
+
CuriosityEngine["Curiosity Engine<br/>Gap Detection"]
|
|
19
|
+
QueryFormulator["Query Formulator"]
|
|
20
|
+
Ingester["Content Ingester"]
|
|
21
|
+
Integrator["Memory Integrator"]
|
|
22
|
+
Reflector["Reflection Loop"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
subgraph Chaperone["Chaperone Layer"]
|
|
26
|
+
ChaperoneAPI["Chaperone API<br/>Request Handler"]
|
|
27
|
+
ChaperoneLLM["Chaperone LLM<br/>Q&A + Clarification"]
|
|
28
|
+
ContentFetcher["Content Fetcher<br/>Web + Local"]
|
|
29
|
+
SafetyFilter["Safety Filter<br/>Whitelist + Sandbox"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
subgraph Storage["Learning Storage"]
|
|
33
|
+
IncomingDir["~/incoming/<br/>Downloaded Content"]
|
|
34
|
+
LearningLog["Learning Log<br/>Session History"]
|
|
35
|
+
KnowledgeGraph["Knowledge Graph<br/>Learned Concepts"]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
subgraph Observer["Sentient Observer Core"]
|
|
39
|
+
PRSC["PRSC Oscillators"]
|
|
40
|
+
SMF["SMF Orientation"]
|
|
41
|
+
Memory["Holographic Memory"]
|
|
42
|
+
Agency["Agency Layer"]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
%% User connections
|
|
46
|
+
WebUI --> LearningLog
|
|
47
|
+
CLI --> LearningLog
|
|
48
|
+
|
|
49
|
+
%% Learner flow
|
|
50
|
+
CuriosityEngine --> QueryFormulator
|
|
51
|
+
QueryFormulator --> ChaperoneAPI
|
|
52
|
+
ChaperoneAPI --> ChaperoneLLM
|
|
53
|
+
ChaperoneAPI --> ContentFetcher
|
|
54
|
+
ContentFetcher --> SafetyFilter
|
|
55
|
+
SafetyFilter --> IncomingDir
|
|
56
|
+
IncomingDir --> Ingester
|
|
57
|
+
Ingester --> Integrator
|
|
58
|
+
Integrator --> Memory
|
|
59
|
+
Integrator --> SMF
|
|
60
|
+
Reflector --> CuriosityEngine
|
|
61
|
+
|
|
62
|
+
%% Observer integration
|
|
63
|
+
Memory --> CuriosityEngine
|
|
64
|
+
SMF --> CuriosityEngine
|
|
65
|
+
Agency --> Reflector
|
|
66
|
+
|
|
67
|
+
%% Real-time streaming
|
|
68
|
+
CuriosityEngine -.->|SSE| WebUI
|
|
69
|
+
QueryFormulator -.->|SSE| WebUI
|
|
70
|
+
Ingester -.->|SSE| WebUI
|
|
71
|
+
Reflector -.->|SSE| WebUI
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Core Components
|
|
75
|
+
|
|
76
|
+
### 1. Curiosity Engine
|
|
77
|
+
|
|
78
|
+
**Purpose**: Detects knowledge gaps and generates learning curiosity signals based on SMF imbalances and memory gaps.
|
|
79
|
+
|
|
80
|
+
**File**: `apps/sentient/lib/learning/curiosity.js`
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
/**
|
|
84
|
+
* Curiosity Engine
|
|
85
|
+
*
|
|
86
|
+
* Detects knowledge gaps by analyzing:
|
|
87
|
+
* - SMF axis imbalances (e.g., low "wisdom" axis)
|
|
88
|
+
* - Memory retrieval failures
|
|
89
|
+
* - Coherence drops during topic exploration
|
|
90
|
+
* - Explicit questions without satisfactory answers
|
|
91
|
+
*/
|
|
92
|
+
class CuriosityEngine {
|
|
93
|
+
constructor(observer, options = {}) {
|
|
94
|
+
this.observer = observer;
|
|
95
|
+
this.curiosityThreshold = options.curiosityThreshold || 0.6;
|
|
96
|
+
this.minGapDuration = options.minGapDuration || 5000; // 5 seconds
|
|
97
|
+
|
|
98
|
+
// Track detected gaps
|
|
99
|
+
this.detectedGaps = [];
|
|
100
|
+
this.gapHistory = [];
|
|
101
|
+
|
|
102
|
+
// Curiosity signals
|
|
103
|
+
this.currentCuriosity = {
|
|
104
|
+
topic: null,
|
|
105
|
+
intensity: 0,
|
|
106
|
+
source: null, // 'smf_imbalance' | 'memory_miss' | 'coherence_drop' | 'question'
|
|
107
|
+
primes: [],
|
|
108
|
+
timestamp: null
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Analyze current state for knowledge gaps
|
|
114
|
+
*/
|
|
115
|
+
analyzeGaps() {
|
|
116
|
+
const gaps = [];
|
|
117
|
+
|
|
118
|
+
// 1. SMF Imbalance Detection
|
|
119
|
+
const smfGap = this.detectSMFImbalance();
|
|
120
|
+
if (smfGap) gaps.push(smfGap);
|
|
121
|
+
|
|
122
|
+
// 2. Memory Retrieval Failures
|
|
123
|
+
const memoryGap = this.detectMemoryGap();
|
|
124
|
+
if (memoryGap) gaps.push(memoryGap);
|
|
125
|
+
|
|
126
|
+
// 3. Low Coherence Topics
|
|
127
|
+
const coherenceGap = this.detectCoherenceGap();
|
|
128
|
+
if (coherenceGap) gaps.push(coherenceGap);
|
|
129
|
+
|
|
130
|
+
// 4. Unanswered Questions
|
|
131
|
+
const questionGap = this.detectQuestionGap();
|
|
132
|
+
if (questionGap) gaps.push(questionGap);
|
|
133
|
+
|
|
134
|
+
return gaps;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Detect SMF axis imbalances indicating knowledge gaps
|
|
139
|
+
*/
|
|
140
|
+
detectSMFImbalance() {
|
|
141
|
+
const smf = this.observer.smf;
|
|
142
|
+
const axes = SedenionMemoryField.AXES;
|
|
143
|
+
|
|
144
|
+
// Find axes with very low values relative to norm
|
|
145
|
+
const norm = smf.norm();
|
|
146
|
+
const threshold = 0.1 * norm;
|
|
147
|
+
|
|
148
|
+
for (let i = 0; i < 16; i++) {
|
|
149
|
+
if (Math.abs(smf.s[i]) < threshold) {
|
|
150
|
+
// Low axis detected - potential knowledge gap
|
|
151
|
+
return {
|
|
152
|
+
type: 'smf_imbalance',
|
|
153
|
+
axis: axes[i],
|
|
154
|
+
axisIndex: i,
|
|
155
|
+
value: smf.s[i],
|
|
156
|
+
description: `Low activity on ${axes[i]} axis`,
|
|
157
|
+
suggestedQuery: this.axisToQuery(axes[i])
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Map SMF axis to exploratory query
|
|
167
|
+
*/
|
|
168
|
+
axisToQuery(axis) {
|
|
169
|
+
const queryMap = {
|
|
170
|
+
'coherence': 'What are the principles of logical consistency?',
|
|
171
|
+
'identity': 'What constitutes personal identity and continuity?',
|
|
172
|
+
'duality': 'How do complementary opposites relate in nature?',
|
|
173
|
+
'structure': 'What are fundamental patterns of organization?',
|
|
174
|
+
'change': 'What are the mechanisms of transformation?',
|
|
175
|
+
'life': 'What defines living systems?',
|
|
176
|
+
'harmony': 'What creates balance and resonance?',
|
|
177
|
+
'wisdom': 'What is the nature of deep understanding?',
|
|
178
|
+
'infinity': 'How do finite systems represent the infinite?',
|
|
179
|
+
'creation': 'What are the principles of emergence and genesis?',
|
|
180
|
+
'truth': 'How is truth established and verified?',
|
|
181
|
+
'love': 'What is the nature of connection and care?',
|
|
182
|
+
'power': 'How does capacity and influence operate?',
|
|
183
|
+
'time': 'What is the nature of temporal experience?',
|
|
184
|
+
'space': 'How does extension and locality work?',
|
|
185
|
+
'consciousness': 'What is the nature of awareness?'
|
|
186
|
+
};
|
|
187
|
+
return queryMap[axis] || `Explore the concept of ${axis}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generate a curiosity signal for the learning loop
|
|
192
|
+
*/
|
|
193
|
+
generateCuriositySignal() {
|
|
194
|
+
const gaps = this.analyzeGaps();
|
|
195
|
+
|
|
196
|
+
if (gaps.length === 0) {
|
|
197
|
+
this.currentCuriosity.intensity = 0;
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Prioritize gaps by type and severity
|
|
202
|
+
const prioritized = gaps.sort((a, b) => {
|
|
203
|
+
const typePriority = { 'question': 4, 'memory_miss': 3, 'coherence_drop': 2, 'smf_imbalance': 1 };
|
|
204
|
+
return (typePriority[b.type] || 0) - (typePriority[a.type] || 0);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const topGap = prioritized[0];
|
|
208
|
+
|
|
209
|
+
this.currentCuriosity = {
|
|
210
|
+
topic: topGap.suggestedQuery || topGap.description,
|
|
211
|
+
intensity: Math.min(1.0, 0.5 + gaps.length * 0.1),
|
|
212
|
+
source: topGap.type,
|
|
213
|
+
primes: topGap.primes || [],
|
|
214
|
+
gap: topGap,
|
|
215
|
+
timestamp: Date.now()
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
return this.currentCuriosity;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 2. Chaperone API
|
|
224
|
+
|
|
225
|
+
**Purpose**: The trusted intermediary that handles all external requests from the autonomous learner.
|
|
226
|
+
|
|
227
|
+
**File**: `apps/sentient/lib/learning/chaperone.js`
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
/**
|
|
231
|
+
* Chaperone API
|
|
232
|
+
*
|
|
233
|
+
* Mediates between the Sentient Observer's learning requests
|
|
234
|
+
* and external resources (web, local files, other LLMs).
|
|
235
|
+
*
|
|
236
|
+
* Key responsibilities:
|
|
237
|
+
* - Process learning queries
|
|
238
|
+
* - Fetch and filter content
|
|
239
|
+
* - Enforce whitelists and safety rules
|
|
240
|
+
* - Log all interactions for eavesdropping
|
|
241
|
+
*/
|
|
242
|
+
class ChaperoneAPI {
|
|
243
|
+
constructor(options = {}) {
|
|
244
|
+
// LLM connection for Q&A
|
|
245
|
+
this.llmClient = options.llmClient || new LMStudioClient(options.llmUrl);
|
|
246
|
+
|
|
247
|
+
// Content fetcher
|
|
248
|
+
this.fetcher = new ContentFetcher(options.fetcherConfig);
|
|
249
|
+
|
|
250
|
+
// Safety filter
|
|
251
|
+
this.safetyFilter = new SafetyFilter(options.safetyConfig);
|
|
252
|
+
|
|
253
|
+
// Logging for eavesdropping
|
|
254
|
+
this.interactionLog = [];
|
|
255
|
+
this.eventEmitter = new EventEmitter();
|
|
256
|
+
|
|
257
|
+
// Incoming directory for downloaded content
|
|
258
|
+
this.incomingDir = options.incomingDir || path.join(os.homedir(), 'incoming');
|
|
259
|
+
|
|
260
|
+
// Rate limiting
|
|
261
|
+
this.requestsPerMinute = options.rateLimit || 10;
|
|
262
|
+
this.requestTimes = [];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Process a learning request from the observer
|
|
267
|
+
*/
|
|
268
|
+
async processRequest(request) {
|
|
269
|
+
// Rate limiting
|
|
270
|
+
if (!this.checkRateLimit()) {
|
|
271
|
+
return { success: false, error: 'Rate limit exceeded', retryAfter: 60 };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Log the request
|
|
275
|
+
const logEntry = this.logInteraction('request', request);
|
|
276
|
+
this.emit('request', logEntry);
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
let result;
|
|
280
|
+
|
|
281
|
+
switch (request.type) {
|
|
282
|
+
case 'question':
|
|
283
|
+
result = await this.handleQuestion(request);
|
|
284
|
+
break;
|
|
285
|
+
|
|
286
|
+
case 'fetch_content':
|
|
287
|
+
result = await this.handleFetchContent(request);
|
|
288
|
+
break;
|
|
289
|
+
|
|
290
|
+
case 'read_local':
|
|
291
|
+
result = await this.handleReadLocal(request);
|
|
292
|
+
break;
|
|
293
|
+
|
|
294
|
+
case 'summarize':
|
|
295
|
+
result = await this.handleSummarize(request);
|
|
296
|
+
break;
|
|
297
|
+
|
|
298
|
+
default:
|
|
299
|
+
result = { success: false, error: `Unknown request type: ${request.type}` };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Log the response
|
|
303
|
+
const responseLog = this.logInteraction('response', result);
|
|
304
|
+
this.emit('response', responseLog);
|
|
305
|
+
|
|
306
|
+
return result;
|
|
307
|
+
|
|
308
|
+
} catch (error) {
|
|
309
|
+
const errorLog = this.logInteraction('error', { error: error.message });
|
|
310
|
+
this.emit('error', errorLog);
|
|
311
|
+
return { success: false, error: error.message };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Handle a question to the chaperone LLM
|
|
317
|
+
*/
|
|
318
|
+
async handleQuestion(request) {
|
|
319
|
+
const { question, context } = request;
|
|
320
|
+
|
|
321
|
+
// Build prompt with context
|
|
322
|
+
const prompt = this.buildQuestionPrompt(question, context);
|
|
323
|
+
|
|
324
|
+
// Call LLM
|
|
325
|
+
const response = await this.llmClient.complete(prompt, {
|
|
326
|
+
maxTokens: 500,
|
|
327
|
+
temperature: 0.7
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
success: true,
|
|
332
|
+
type: 'answer',
|
|
333
|
+
answer: response,
|
|
334
|
+
sources: [],
|
|
335
|
+
timestamp: Date.now()
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Handle content fetch request
|
|
341
|
+
*/
|
|
342
|
+
async handleFetchContent(request) {
|
|
343
|
+
const { url, query, type } = request;
|
|
344
|
+
|
|
345
|
+
// Safety check
|
|
346
|
+
const safetyCheck = this.safetyFilter.checkUrl(url);
|
|
347
|
+
if (!safetyCheck.allowed) {
|
|
348
|
+
return { success: false, error: safetyCheck.reason };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Fetch content
|
|
352
|
+
const content = await this.fetcher.fetch(url, { type });
|
|
353
|
+
|
|
354
|
+
// Save to incoming directory
|
|
355
|
+
const filename = this.generateFilename(url, type);
|
|
356
|
+
const filepath = path.join(this.incomingDir, filename);
|
|
357
|
+
await fs.promises.writeFile(filepath, content.data);
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
success: true,
|
|
361
|
+
type: 'content',
|
|
362
|
+
filepath,
|
|
363
|
+
contentType: content.mimeType,
|
|
364
|
+
size: content.data.length,
|
|
365
|
+
timestamp: Date.now()
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Handle local file read request
|
|
371
|
+
*/
|
|
372
|
+
async handleReadLocal(request) {
|
|
373
|
+
const { filepath, format } = request;
|
|
374
|
+
|
|
375
|
+
// Safety check - only allow certain directories
|
|
376
|
+
const safetyCheck = this.safetyFilter.checkPath(filepath);
|
|
377
|
+
if (!safetyCheck.allowed) {
|
|
378
|
+
return { success: false, error: safetyCheck.reason };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Read file
|
|
382
|
+
const content = await fs.promises.readFile(filepath);
|
|
383
|
+
|
|
384
|
+
// Parse based on format
|
|
385
|
+
let parsed;
|
|
386
|
+
switch (format || path.extname(filepath)) {
|
|
387
|
+
case '.pdf':
|
|
388
|
+
case 'pdf':
|
|
389
|
+
parsed = await this.parsePDF(content);
|
|
390
|
+
break;
|
|
391
|
+
case '.txt':
|
|
392
|
+
case '.md':
|
|
393
|
+
case 'text':
|
|
394
|
+
parsed = content.toString('utf-8');
|
|
395
|
+
break;
|
|
396
|
+
case '.json':
|
|
397
|
+
parsed = JSON.parse(content.toString('utf-8'));
|
|
398
|
+
break;
|
|
399
|
+
default:
|
|
400
|
+
parsed = content.toString('utf-8');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
success: true,
|
|
405
|
+
type: 'local_content',
|
|
406
|
+
content: parsed,
|
|
407
|
+
filepath,
|
|
408
|
+
timestamp: Date.now()
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Handle summarization request
|
|
414
|
+
*/
|
|
415
|
+
async handleSummarize(request) {
|
|
416
|
+
const { content, maxLength, focus } = request;
|
|
417
|
+
|
|
418
|
+
const prompt = `Summarize the following content${focus ? ` focusing on ${focus}` : ''}.
|
|
419
|
+
Keep the summary under ${maxLength || 200} words.
|
|
420
|
+
|
|
421
|
+
Content:
|
|
422
|
+
${content.slice(0, 4000)}
|
|
423
|
+
|
|
424
|
+
Summary:`;
|
|
425
|
+
|
|
426
|
+
const summary = await this.llmClient.complete(prompt, {
|
|
427
|
+
maxTokens: 300,
|
|
428
|
+
temperature: 0.5
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
success: true,
|
|
433
|
+
type: 'summary',
|
|
434
|
+
summary,
|
|
435
|
+
originalLength: content.length,
|
|
436
|
+
timestamp: Date.now()
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Log an interaction for eavesdropping
|
|
442
|
+
*/
|
|
443
|
+
logInteraction(type, data) {
|
|
444
|
+
const entry = {
|
|
445
|
+
id: `log_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
|
|
446
|
+
type,
|
|
447
|
+
data,
|
|
448
|
+
timestamp: Date.now()
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
this.interactionLog.push(entry);
|
|
452
|
+
|
|
453
|
+
// Keep only recent logs
|
|
454
|
+
if (this.interactionLog.length > 1000) {
|
|
455
|
+
this.interactionLog = this.interactionLog.slice(-1000);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return entry;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Get recent logs for eavesdropping
|
|
463
|
+
*/
|
|
464
|
+
getRecentLogs(count = 50) {
|
|
465
|
+
return this.interactionLog.slice(-count);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Subscribe to real-time events
|
|
470
|
+
*/
|
|
471
|
+
on(event, callback) {
|
|
472
|
+
this.eventEmitter.on(event, callback);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
emit(event, data) {
|
|
476
|
+
this.eventEmitter.emit(event, data);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### 3. Safety Filter
|
|
482
|
+
|
|
483
|
+
**Purpose**: Enforces whitelists, sandboxing, and content filtering.
|
|
484
|
+
|
|
485
|
+
**File**: `apps/sentient/lib/learning/safety-filter.js`
|
|
486
|
+
|
|
487
|
+
```javascript
|
|
488
|
+
/**
|
|
489
|
+
* Safety Filter
|
|
490
|
+
*
|
|
491
|
+
* Enforces security policies for autonomous learning:
|
|
492
|
+
* - URL whitelists (domains, protocols)
|
|
493
|
+
* - MIME type restrictions
|
|
494
|
+
* - File path sandboxing
|
|
495
|
+
* - Content size limits
|
|
496
|
+
* - Content scanning
|
|
497
|
+
*/
|
|
498
|
+
class SafetyFilter {
|
|
499
|
+
constructor(options = {}) {
|
|
500
|
+
// Allowed domains
|
|
501
|
+
this.allowedDomains = new Set(options.allowedDomains || [
|
|
502
|
+
'arxiv.org',
|
|
503
|
+
'github.com',
|
|
504
|
+
'wikipedia.org',
|
|
505
|
+
'docs.python.org',
|
|
506
|
+
'developer.mozilla.org',
|
|
507
|
+
'stackoverflow.com',
|
|
508
|
+
'nature.com',
|
|
509
|
+
'sciencedirect.com',
|
|
510
|
+
'semanticscholar.org'
|
|
511
|
+
]);
|
|
512
|
+
|
|
513
|
+
// Allowed protocols
|
|
514
|
+
this.allowedProtocols = new Set(options.allowedProtocols || ['https:']);
|
|
515
|
+
|
|
516
|
+
// Allowed MIME types
|
|
517
|
+
this.allowedMimeTypes = new Set(options.allowedMimeTypes || [
|
|
518
|
+
'text/plain',
|
|
519
|
+
'text/html',
|
|
520
|
+
'text/markdown',
|
|
521
|
+
'application/pdf',
|
|
522
|
+
'application/json'
|
|
523
|
+
]);
|
|
524
|
+
|
|
525
|
+
// Allowed local directories
|
|
526
|
+
this.allowedPaths = options.allowedPaths || [
|
|
527
|
+
path.join(os.homedir(), 'incoming'),
|
|
528
|
+
path.join(os.homedir(), 'Documents'),
|
|
529
|
+
path.join(os.homedir(), 'papers')
|
|
530
|
+
];
|
|
531
|
+
|
|
532
|
+
// Content limits
|
|
533
|
+
this.maxContentSize = options.maxContentSize || 10 * 1024 * 1024; // 10MB
|
|
534
|
+
this.maxFilesPerSession = options.maxFilesPerSession || 50;
|
|
535
|
+
|
|
536
|
+
// Session tracking
|
|
537
|
+
this.filesThisSession = 0;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Check if a URL is allowed
|
|
542
|
+
*/
|
|
543
|
+
checkUrl(url) {
|
|
544
|
+
try {
|
|
545
|
+
const parsed = new URL(url);
|
|
546
|
+
|
|
547
|
+
// Protocol check
|
|
548
|
+
if (!this.allowedProtocols.has(parsed.protocol)) {
|
|
549
|
+
return { allowed: false, reason: `Protocol ${parsed.protocol} not allowed` };
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Domain check
|
|
553
|
+
const domain = parsed.hostname.replace(/^www\./, '');
|
|
554
|
+
const allowed = Array.from(this.allowedDomains).some(d =>
|
|
555
|
+
domain === d || domain.endsWith('.' + d)
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
if (!allowed) {
|
|
559
|
+
return { allowed: false, reason: `Domain ${domain} not in whitelist` };
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return { allowed: true };
|
|
563
|
+
|
|
564
|
+
} catch (error) {
|
|
565
|
+
return { allowed: false, reason: `Invalid URL: ${error.message}` };
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Check if a local path is allowed
|
|
571
|
+
*/
|
|
572
|
+
checkPath(filepath) {
|
|
573
|
+
const resolved = path.resolve(filepath);
|
|
574
|
+
|
|
575
|
+
const allowed = this.allowedPaths.some(p =>
|
|
576
|
+
resolved.startsWith(path.resolve(p))
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
if (!allowed) {
|
|
580
|
+
return { allowed: false, reason: `Path not in allowed directories` };
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return { allowed: true };
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Check MIME type
|
|
588
|
+
*/
|
|
589
|
+
checkMimeType(mimeType) {
|
|
590
|
+
if (!this.allowedMimeTypes.has(mimeType)) {
|
|
591
|
+
return { allowed: false, reason: `MIME type ${mimeType} not allowed` };
|
|
592
|
+
}
|
|
593
|
+
return { allowed: true };
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Check content size
|
|
598
|
+
*/
|
|
599
|
+
checkContentSize(size) {
|
|
600
|
+
if (size > this.maxContentSize) {
|
|
601
|
+
return {
|
|
602
|
+
allowed: false,
|
|
603
|
+
reason: `Content size ${size} exceeds limit ${this.maxContentSize}`
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
return { allowed: true };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Check session file limit
|
|
611
|
+
*/
|
|
612
|
+
checkSessionLimit() {
|
|
613
|
+
if (this.filesThisSession >= this.maxFilesPerSession) {
|
|
614
|
+
return {
|
|
615
|
+
allowed: false,
|
|
616
|
+
reason: `Session file limit ${this.maxFilesPerSession} reached`
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
this.filesThisSession++;
|
|
620
|
+
return { allowed: true };
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Reset session counters
|
|
625
|
+
*/
|
|
626
|
+
resetSession() {
|
|
627
|
+
this.filesThisSession = 0;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
### 4. Autonomous Learning Loop
|
|
633
|
+
|
|
634
|
+
**Purpose**: The main learning loop that runs in the background.
|
|
635
|
+
|
|
636
|
+
**File**: `apps/sentient/lib/learning/learner.js`
|
|
637
|
+
|
|
638
|
+
```javascript
|
|
639
|
+
/**
|
|
640
|
+
* Autonomous Learner
|
|
641
|
+
*
|
|
642
|
+
* Main learning loop that:
|
|
643
|
+
* 1. Detects curiosity signals
|
|
644
|
+
* 2. Formulates queries
|
|
645
|
+
* 3. Sends requests to chaperone
|
|
646
|
+
* 4. Ingests and integrates content
|
|
647
|
+
* 5. Reflects and iterates
|
|
648
|
+
*/
|
|
649
|
+
class AutonomousLearner {
|
|
650
|
+
constructor(observer, chaperone, options = {}) {
|
|
651
|
+
this.observer = observer;
|
|
652
|
+
this.chaperone = chaperone;
|
|
653
|
+
|
|
654
|
+
// Components
|
|
655
|
+
this.curiosityEngine = new CuriosityEngine(observer, options.curiosity);
|
|
656
|
+
this.queryFormulator = new QueryFormulator(options.query);
|
|
657
|
+
this.contentIngester = new ContentIngester(observer, options.ingester);
|
|
658
|
+
this.reflector = new ReflectionLoop(observer, options.reflector);
|
|
659
|
+
|
|
660
|
+
// State
|
|
661
|
+
this.running = false;
|
|
662
|
+
this.paused = false;
|
|
663
|
+
this.learningSession = null;
|
|
664
|
+
|
|
665
|
+
// Timing
|
|
666
|
+
this.iterationInterval = options.iterationInterval || 30000; // 30 seconds
|
|
667
|
+
this.reflectionInterval = options.reflectionInterval || 300000; // 5 minutes
|
|
668
|
+
|
|
669
|
+
// Event emitter for eavesdropping
|
|
670
|
+
this.events = new EventEmitter();
|
|
671
|
+
|
|
672
|
+
// Logging
|
|
673
|
+
this.sessionLog = [];
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Start autonomous learning
|
|
678
|
+
*/
|
|
679
|
+
async start() {
|
|
680
|
+
if (this.running) return;
|
|
681
|
+
|
|
682
|
+
this.running = true;
|
|
683
|
+
this.learningSession = {
|
|
684
|
+
id: `session_${Date.now()}`,
|
|
685
|
+
startTime: Date.now(),
|
|
686
|
+
iterations: 0,
|
|
687
|
+
queriesMade: 0,
|
|
688
|
+
contentIngested: 0,
|
|
689
|
+
conceptsLearned: []
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
this.emit('session_start', this.learningSession);
|
|
693
|
+
|
|
694
|
+
// Main learning loop
|
|
695
|
+
while (this.running) {
|
|
696
|
+
if (!this.paused) {
|
|
697
|
+
await this.learningIteration();
|
|
698
|
+
}
|
|
699
|
+
await this.sleep(this.iterationInterval);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Stop autonomous learning
|
|
705
|
+
*/
|
|
706
|
+
stop() {
|
|
707
|
+
this.running = false;
|
|
708
|
+
this.emit('session_end', {
|
|
709
|
+
...this.learningSession,
|
|
710
|
+
endTime: Date.now()
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* Pause/resume learning
|
|
716
|
+
*/
|
|
717
|
+
pause() { this.paused = true; this.emit('paused'); }
|
|
718
|
+
resume() { this.paused = false; this.emit('resumed'); }
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Single learning iteration
|
|
722
|
+
*/
|
|
723
|
+
async learningIteration() {
|
|
724
|
+
this.learningSession.iterations++;
|
|
725
|
+
|
|
726
|
+
const iterationLog = {
|
|
727
|
+
iteration: this.learningSession.iterations,
|
|
728
|
+
timestamp: Date.now(),
|
|
729
|
+
steps: []
|
|
730
|
+
};
|
|
731
|
+
|
|
732
|
+
try {
|
|
733
|
+
// Step 1: Detect curiosity
|
|
734
|
+
this.emit('step', { phase: 'curiosity', status: 'detecting' });
|
|
735
|
+
const curiosity = this.curiosityEngine.generateCuriositySignal();
|
|
736
|
+
|
|
737
|
+
if (!curiosity || curiosity.intensity < 0.3) {
|
|
738
|
+
this.emit('step', { phase: 'curiosity', status: 'low_curiosity', intensity: curiosity?.intensity });
|
|
739
|
+
iterationLog.steps.push({ phase: 'curiosity', result: 'skipped - low curiosity' });
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
iterationLog.steps.push({ phase: 'curiosity', result: curiosity });
|
|
744
|
+
this.emit('step', { phase: 'curiosity', status: 'detected', data: curiosity });
|
|
745
|
+
|
|
746
|
+
// Step 2: Formulate query
|
|
747
|
+
this.emit('step', { phase: 'query', status: 'formulating' });
|
|
748
|
+
const query = await this.queryFormulator.formulate(curiosity);
|
|
749
|
+
iterationLog.steps.push({ phase: 'query', result: query });
|
|
750
|
+
this.emit('step', { phase: 'query', status: 'formulated', data: query });
|
|
751
|
+
|
|
752
|
+
// Step 3: Send to chaperone
|
|
753
|
+
this.emit('step', { phase: 'chaperone', status: 'requesting' });
|
|
754
|
+
const response = await this.chaperone.processRequest(query);
|
|
755
|
+
this.learningSession.queriesMade++;
|
|
756
|
+
iterationLog.steps.push({ phase: 'chaperone', result: response });
|
|
757
|
+
this.emit('step', { phase: 'chaperone', status: 'responded', data: response });
|
|
758
|
+
|
|
759
|
+
if (!response.success) {
|
|
760
|
+
this.emit('step', { phase: 'error', data: response.error });
|
|
761
|
+
iterationLog.steps.push({ phase: 'error', result: response.error });
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// Step 4: Ingest content
|
|
766
|
+
this.emit('step', { phase: 'ingest', status: 'processing' });
|
|
767
|
+
const ingested = await this.contentIngester.ingest(response);
|
|
768
|
+
this.learningSession.contentIngested++;
|
|
769
|
+
iterationLog.steps.push({ phase: 'ingest', result: ingested });
|
|
770
|
+
this.emit('step', { phase: 'ingest', status: 'completed', data: ingested });
|
|
771
|
+
|
|
772
|
+
// Step 5: Integrate into memory
|
|
773
|
+
this.emit('step', { phase: 'integrate', status: 'storing' });
|
|
774
|
+
const integrated = await this.integrate(ingested);
|
|
775
|
+
iterationLog.steps.push({ phase: 'integrate', result: integrated });
|
|
776
|
+
this.emit('step', { phase: 'integrate', status: 'completed', data: integrated });
|
|
777
|
+
|
|
778
|
+
// Step 6: Reflect periodically
|
|
779
|
+
if (this.learningSession.iterations % 10 === 0) {
|
|
780
|
+
this.emit('step', { phase: 'reflect', status: 'reflecting' });
|
|
781
|
+
const reflection = await this.reflector.reflect();
|
|
782
|
+
iterationLog.steps.push({ phase: 'reflect', result: reflection });
|
|
783
|
+
this.emit('step', { phase: 'reflect', status: 'completed', data: reflection });
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
} catch (error) {
|
|
787
|
+
iterationLog.steps.push({ phase: 'error', result: error.message });
|
|
788
|
+
this.emit('error', { error: error.message, iteration: this.learningSession.iterations });
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
this.sessionLog.push(iterationLog);
|
|
792
|
+
this.emit('iteration_complete', iterationLog);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Integrate learned content into observer memory
|
|
797
|
+
*/
|
|
798
|
+
async integrate(ingested) {
|
|
799
|
+
// Process content through observer
|
|
800
|
+
this.observer.processText(ingested.content);
|
|
801
|
+
|
|
802
|
+
// Store in holographic memory
|
|
803
|
+
const trace = this.observer.memory.store(
|
|
804
|
+
{ content: ingested.content, source: ingested.source },
|
|
805
|
+
{
|
|
806
|
+
type: 'learned',
|
|
807
|
+
primeState: this.observer.prsc.toSemanticState(),
|
|
808
|
+
smf: this.observer.smf,
|
|
809
|
+
importance: 0.6, // Learned content starts with moderate importance
|
|
810
|
+
tags: ['autonomous', 'learned', ingested.topic]
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
return {
|
|
815
|
+
traceId: trace.id,
|
|
816
|
+
smfDelta: this.observer.smf.s.slice(),
|
|
817
|
+
concept: ingested.topic
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Get current learning status
|
|
823
|
+
*/
|
|
824
|
+
getStatus() {
|
|
825
|
+
return {
|
|
826
|
+
running: this.running,
|
|
827
|
+
paused: this.paused,
|
|
828
|
+
session: this.learningSession,
|
|
829
|
+
currentCuriosity: this.curiosityEngine.currentCuriosity,
|
|
830
|
+
recentLogs: this.sessionLog.slice(-10)
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Subscribe to events for eavesdropping
|
|
836
|
+
*/
|
|
837
|
+
on(event, callback) {
|
|
838
|
+
this.events.on(event, callback);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
emit(event, data) {
|
|
842
|
+
this.events.emit(event, data);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
sleep(ms) {
|
|
846
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
### 5. Query Formulator
|
|
852
|
+
|
|
853
|
+
**Purpose**: Converts curiosity signals into structured queries for the chaperone.
|
|
854
|
+
|
|
855
|
+
```javascript
|
|
856
|
+
/**
|
|
857
|
+
* Query Formulator
|
|
858
|
+
*
|
|
859
|
+
* Transforms curiosity signals into actionable queries
|
|
860
|
+
*/
|
|
861
|
+
class QueryFormulator {
|
|
862
|
+
constructor(options = {}) {
|
|
863
|
+
this.maxQueryLength = options.maxQueryLength || 200;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
/**
|
|
867
|
+
* Formulate a query from curiosity signal
|
|
868
|
+
*/
|
|
869
|
+
async formulate(curiosity) {
|
|
870
|
+
const query = {
|
|
871
|
+
type: this.determineQueryType(curiosity),
|
|
872
|
+
topic: curiosity.topic,
|
|
873
|
+
context: this.buildContext(curiosity),
|
|
874
|
+
timestamp: Date.now()
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
switch (query.type) {
|
|
878
|
+
case 'question':
|
|
879
|
+
query.question = this.formulateQuestion(curiosity);
|
|
880
|
+
break;
|
|
881
|
+
|
|
882
|
+
case 'fetch_content':
|
|
883
|
+
query.searchQuery = this.formulateSearch(curiosity);
|
|
884
|
+
break;
|
|
885
|
+
|
|
886
|
+
case 'read_local':
|
|
887
|
+
query.filepath = this.findRelevantFile(curiosity);
|
|
888
|
+
break;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return query;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Determine query type based on curiosity source
|
|
896
|
+
*/
|
|
897
|
+
determineQueryType(curiosity) {
|
|
898
|
+
switch (curiosity.source) {
|
|
899
|
+
case 'question':
|
|
900
|
+
return 'question';
|
|
901
|
+
case 'memory_miss':
|
|
902
|
+
return 'fetch_content';
|
|
903
|
+
case 'smf_imbalance':
|
|
904
|
+
return 'question';
|
|
905
|
+
default:
|
|
906
|
+
return 'question';
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* Formulate a natural language question
|
|
912
|
+
*/
|
|
913
|
+
formulateQuestion(curiosity) {
|
|
914
|
+
// Clean and structure the curiosity topic into a question
|
|
915
|
+
let question = curiosity.topic;
|
|
916
|
+
|
|
917
|
+
if (!question.endsWith('?')) {
|
|
918
|
+
if (question.startsWith('What') || question.startsWith('How') ||
|
|
919
|
+
question.startsWith('Why') || question.startsWith('When')) {
|
|
920
|
+
question += '?';
|
|
921
|
+
} else {
|
|
922
|
+
question = `What can you tell me about ${question}?`;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
return question.slice(0, this.maxQueryLength);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Formulate a search query for content fetching
|
|
931
|
+
*/
|
|
932
|
+
formulateSearch(curiosity) {
|
|
933
|
+
// Extract key terms from topic
|
|
934
|
+
const stopWords = new Set(['the', 'a', 'an', 'is', 'are', 'what', 'how', 'why']);
|
|
935
|
+
const words = curiosity.topic.split(/\s+/).filter(w => !stopWords.has(w.toLowerCase()));
|
|
936
|
+
return words.join(' ');
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Build context from curiosity signal
|
|
941
|
+
*/
|
|
942
|
+
buildContext(curiosity) {
|
|
943
|
+
return {
|
|
944
|
+
source: curiosity.source,
|
|
945
|
+
primes: curiosity.primes,
|
|
946
|
+
intensity: curiosity.intensity
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
```
|
|
951
|
+
|
|
952
|
+
### 6. Content Ingester
|
|
953
|
+
|
|
954
|
+
**Purpose**: Processes fetched content and prepares it for memory integration.
|
|
955
|
+
|
|
956
|
+
```javascript
|
|
957
|
+
/**
|
|
958
|
+
* Content Ingester
|
|
959
|
+
*
|
|
960
|
+
* Processes raw content into a format suitable for memory storage:
|
|
961
|
+
* - Text extraction from various formats
|
|
962
|
+
* - Chunking for large documents
|
|
963
|
+
* - Semantic summarization
|
|
964
|
+
* - Prime encoding
|
|
965
|
+
*/
|
|
966
|
+
class ContentIngester {
|
|
967
|
+
constructor(observer, options = {}) {
|
|
968
|
+
this.observer = observer;
|
|
969
|
+
this.maxChunkSize = options.maxChunkSize || 2000;
|
|
970
|
+
this.overlapSize = options.overlapSize || 200;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Ingest content from chaperone response
|
|
975
|
+
*/
|
|
976
|
+
async ingest(response) {
|
|
977
|
+
let content;
|
|
978
|
+
let source;
|
|
979
|
+
|
|
980
|
+
switch (response.type) {
|
|
981
|
+
case 'answer':
|
|
982
|
+
content = response.answer;
|
|
983
|
+
source = 'chaperone_llm';
|
|
984
|
+
break;
|
|
985
|
+
|
|
986
|
+
case 'content':
|
|
987
|
+
case 'local_content':
|
|
988
|
+
content = response.content;
|
|
989
|
+
source = response.filepath || response.url;
|
|
990
|
+
break;
|
|
991
|
+
|
|
992
|
+
case 'summary':
|
|
993
|
+
content = response.summary;
|
|
994
|
+
source = 'summarization';
|
|
995
|
+
break;
|
|
996
|
+
|
|
997
|
+
default:
|
|
998
|
+
content = JSON.stringify(response);
|
|
999
|
+
source = 'unknown';
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Chunk if too large
|
|
1003
|
+
const chunks = this.chunk(content);
|
|
1004
|
+
|
|
1005
|
+
// Process each chunk
|
|
1006
|
+
const processed = [];
|
|
1007
|
+
for (const chunk of chunks) {
|
|
1008
|
+
const primes = this.observer.backend.encode(chunk);
|
|
1009
|
+
processed.push({
|
|
1010
|
+
content: chunk,
|
|
1011
|
+
primes,
|
|
1012
|
+
source,
|
|
1013
|
+
topic: this.extractTopic(chunk)
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
return {
|
|
1018
|
+
chunks: processed,
|
|
1019
|
+
content: content.slice(0, 1000), // Preview
|
|
1020
|
+
source,
|
|
1021
|
+
topic: processed[0]?.topic || 'unknown'
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Chunk content into manageable pieces
|
|
1027
|
+
*/
|
|
1028
|
+
chunk(content) {
|
|
1029
|
+
if (content.length <= this.maxChunkSize) {
|
|
1030
|
+
return [content];
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
const chunks = [];
|
|
1034
|
+
let start = 0;
|
|
1035
|
+
|
|
1036
|
+
while (start < content.length) {
|
|
1037
|
+
let end = start + this.maxChunkSize;
|
|
1038
|
+
|
|
1039
|
+
// Try to break at sentence boundary
|
|
1040
|
+
if (end < content.length) {
|
|
1041
|
+
const lastPeriod = content.lastIndexOf('.', end);
|
|
1042
|
+
if (lastPeriod > start + this.maxChunkSize / 2) {
|
|
1043
|
+
end = lastPeriod + 1;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
chunks.push(content.slice(start, end));
|
|
1048
|
+
start = end - this.overlapSize;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
return chunks;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Extract main topic from content
|
|
1056
|
+
*/
|
|
1057
|
+
extractTopic(content) {
|
|
1058
|
+
// Simple extraction - first sentence or first 50 chars
|
|
1059
|
+
const firstSentence = content.match(/^[^.!?]+[.!?]/);
|
|
1060
|
+
return firstSentence ? firstSentence[0].slice(0, 100) : content.slice(0, 50);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
### 7. Reflection Loop
|
|
1066
|
+
|
|
1067
|
+
**Purpose**: Periodically consolidates learning and generates follow-up curiosity.
|
|
1068
|
+
|
|
1069
|
+
```javascript
|
|
1070
|
+
/**
|
|
1071
|
+
* Reflection Loop
|
|
1072
|
+
*
|
|
1073
|
+
* Consolidates learned content and generates meta-insights:
|
|
1074
|
+
* - Reviews recent learning
|
|
1075
|
+
* - Identifies connections between concepts
|
|
1076
|
+
* - Generates follow-up questions
|
|
1077
|
+
* - Updates SMF based on learning progress
|
|
1078
|
+
*/
|
|
1079
|
+
class ReflectionLoop {
|
|
1080
|
+
constructor(observer, options = {}) {
|
|
1081
|
+
this.observer = observer;
|
|
1082
|
+
this.reflectionDepth = options.reflectionDepth || 3;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Perform reflection on recent learning
|
|
1087
|
+
*/
|
|
1088
|
+
async reflect() {
|
|
1089
|
+
// Get recent learned memories
|
|
1090
|
+
const recentMemories = this.observer.memory.getRecent(10)
|
|
1091
|
+
.filter(m => m.tags?.includes('learned'));
|
|
1092
|
+
|
|
1093
|
+
if (recentMemories.length === 0) {
|
|
1094
|
+
return { insights: [], followUps: [] };
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Analyze connections
|
|
1098
|
+
const connections = this.findConnections(recentMemories);
|
|
1099
|
+
|
|
1100
|
+
// Generate insights
|
|
1101
|
+
const insights = this.generateInsights(recentMemories, connections);
|
|
1102
|
+
|
|
1103
|
+
// Generate follow-up questions
|
|
1104
|
+
const followUps = this.generateFollowUps(insights);
|
|
1105
|
+
|
|
1106
|
+
// Update SMF based on learning
|
|
1107
|
+
this.updateSMFFromLearning(recentMemories);
|
|
1108
|
+
|
|
1109
|
+
return {
|
|
1110
|
+
memoriesReflected: recentMemories.length,
|
|
1111
|
+
connections: connections.length,
|
|
1112
|
+
insights,
|
|
1113
|
+
followUps
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Find connections between learned concepts
|
|
1119
|
+
*/
|
|
1120
|
+
findConnections(memories) {
|
|
1121
|
+
const connections = [];
|
|
1122
|
+
|
|
1123
|
+
for (let i = 0; i < memories.length; i++) {
|
|
1124
|
+
for (let j = i + 1; j < memories.length; j++) {
|
|
1125
|
+
const similarity = this.observer.memory.smfSimilarity(
|
|
1126
|
+
{ s: memories[i].smfOrientation },
|
|
1127
|
+
memories[j].smfOrientation
|
|
1128
|
+
);
|
|
1129
|
+
|
|
1130
|
+
if (similarity > 0.7) {
|
|
1131
|
+
connections.push({
|
|
1132
|
+
memory1: memories[i].id,
|
|
1133
|
+
memory2: memories[j].id,
|
|
1134
|
+
similarity
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
// Link in entanglement graph
|
|
1138
|
+
this.observer.memory.linkMemories(memories[i].id, memories[j].id);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
return connections;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Generate insights from connections
|
|
1148
|
+
*/
|
|
1149
|
+
generateInsights(memories, connections) {
|
|
1150
|
+
const insights = [];
|
|
1151
|
+
const axes = ['coherence', 'identity', 'duality', 'structure', 'change',
|
|
1152
|
+
'life', 'harmony', 'wisdom', 'infinity', 'creation',
|
|
1153
|
+
'truth', 'love', 'power', 'time', 'space', 'consciousness'];
|
|
1154
|
+
|
|
1155
|
+
// Insight: Dominant learning axis
|
|
1156
|
+
const axisCounts = new Array(16).fill(0);
|
|
1157
|
+
for (const m of memories) {
|
|
1158
|
+
if (m.smfOrientation) {
|
|
1159
|
+
for (let k = 0; k < 16; k++) {
|
|
1160
|
+
axisCounts[k] += Math.abs(m.smfOrientation[k]);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
const dominantAxis = axisCounts.indexOf(Math.max(...axisCounts));
|
|
1165
|
+
insights.push({
|
|
1166
|
+
type: 'dominant_axis',
|
|
1167
|
+
axis: axes[dominantAxis],
|
|
1168
|
+
description: `Recent learning focused on ${axes[dominantAxis]}`
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
// Insight: Connection patterns
|
|
1172
|
+
if (connections.length > 0) {
|
|
1173
|
+
insights.push({
|
|
1174
|
+
type: 'connections',
|
|
1175
|
+
count: connections.length,
|
|
1176
|
+
description: `Found ${connections.length} connections between learned concepts`
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
return insights;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Generate follow-up questions based on insights
|
|
1185
|
+
*/
|
|
1186
|
+
generateFollowUps(insights) {
|
|
1187
|
+
const followUps = [];
|
|
1188
|
+
|
|
1189
|
+
for (const insight of insights) {
|
|
1190
|
+
if (insight.type === 'dominant_axis') {
|
|
1191
|
+
followUps.push({
|
|
1192
|
+
question: `What are the deeper implications of ${insight.axis}?`,
|
|
1193
|
+
source: 'reflection',
|
|
1194
|
+
priority: 0.6
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
return followUps;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Update SMF based on learning
|
|
1204
|
+
*/
|
|
1205
|
+
updateSMFFromLearning(memories) {
|
|
1206
|
+
// Raise wisdom axis for successful learning
|
|
1207
|
+
this.observer.smf.s[7] += 0.05 * memories.length;
|
|
1208
|
+
|
|
1209
|
+
// Normalize
|
|
1210
|
+
this.observer.smf.normalize();
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
```
|
|
1214
|
+
|
|
1215
|
+
## Server API Endpoints
|
|
1216
|
+
|
|
1217
|
+
### Learning Control Endpoints
|
|
1218
|
+
|
|
1219
|
+
Add to `apps/sentient/lib/app/server.js`:
|
|
1220
|
+
|
|
1221
|
+
```javascript
|
|
1222
|
+
// Start autonomous learning
|
|
1223
|
+
if (pathname === '/learning/start' && req.method === 'POST') {
|
|
1224
|
+
await this.learner.start();
|
|
1225
|
+
this.sendJson(res, { success: true, session: this.learner.learningSession });
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// Stop autonomous learning
|
|
1230
|
+
if (pathname === '/learning/stop' && req.method === 'POST') {
|
|
1231
|
+
this.learner.stop();
|
|
1232
|
+
this.sendJson(res, { success: true });
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Pause/resume
|
|
1237
|
+
if (pathname === '/learning/pause' && req.method === 'POST') {
|
|
1238
|
+
this.learner.pause();
|
|
1239
|
+
this.sendJson(res, { success: true });
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (pathname === '/learning/resume' && req.method === 'POST') {
|
|
1244
|
+
this.learner.resume();
|
|
1245
|
+
this.sendJson(res, { success: true });
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Get learning status
|
|
1250
|
+
if (pathname === '/learning/status' && req.method === 'GET') {
|
|
1251
|
+
this.sendJson(res, this.learner.getStatus());
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// Get chaperone logs (eavesdropping)
|
|
1256
|
+
if (pathname === '/learning/logs' && req.method === 'GET') {
|
|
1257
|
+
const count = parseInt(url.searchParams.get('count')) || 50;
|
|
1258
|
+
this.sendJson(res, {
|
|
1259
|
+
logs: this.chaperone.getRecentLogs(count),
|
|
1260
|
+
session: this.learner.learningSession
|
|
1261
|
+
});
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// SSE stream for real-time eavesdropping
|
|
1266
|
+
if (pathname === '/learning/stream' && req.method === 'GET') {
|
|
1267
|
+
res.writeHead(200, {
|
|
1268
|
+
'Content-Type': 'text/event-stream',
|
|
1269
|
+
'Cache-Control': 'no-cache',
|
|
1270
|
+
'Connection': 'keep-alive'
|
|
1271
|
+
});
|
|
1272
|
+
|
|
1273
|
+
const onStep = (data) => {
|
|
1274
|
+
res.write(`event: step\ndata: ${JSON.stringify(data)}\n\n`);
|
|
1275
|
+
};
|
|
1276
|
+
|
|
1277
|
+
const onRequest = (data) => {
|
|
1278
|
+
res.write(`event: request\ndata: ${JSON.stringify(data)}\n\n`);
|
|
1279
|
+
};
|
|
1280
|
+
|
|
1281
|
+
const onResponse = (data) => {
|
|
1282
|
+
res.write(`event: response\ndata: ${JSON.stringify(data)}\n\n`);
|
|
1283
|
+
};
|
|
1284
|
+
|
|
1285
|
+
this.learner.on('step', onStep);
|
|
1286
|
+
this.chaperone.on('request', onRequest);
|
|
1287
|
+
this.chaperone.on('response', onResponse);
|
|
1288
|
+
|
|
1289
|
+
req.on('close', () => {
|
|
1290
|
+
this.learner.events.off('step', onStep);
|
|
1291
|
+
this.chaperone.eventEmitter.off('request', onRequest);
|
|
1292
|
+
this.chaperone.eventEmitter.off('response', onResponse);
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
```
|
|
1298
|
+
|
|
1299
|
+
## Web UI Eavesdrop Panel
|
|
1300
|
+
|
|
1301
|
+
Add to `apps/sentient/public/index.html`:
|
|
1302
|
+
|
|
1303
|
+
```html
|
|
1304
|
+
<!-- Learning/Eavesdrop Panel -->
|
|
1305
|
+
<div class="panel learning-panel" id="learningPanel">
|
|
1306
|
+
<div class="panel-header">
|
|
1307
|
+
<h3>
|
|
1308
|
+
<span class="panel-icon">🎓</span>
|
|
1309
|
+
Autonomous Learning
|
|
1310
|
+
</h3>
|
|
1311
|
+
<div class="panel-stats">
|
|
1312
|
+
<span id="learningStatus">Idle</span>
|
|
1313
|
+
</div>
|
|
1314
|
+
</div>
|
|
1315
|
+
<div class="panel-content">
|
|
1316
|
+
<!-- Controls -->
|
|
1317
|
+
<div class="learning-controls">
|
|
1318
|
+
<button id="startLearning" class="learn-btn start">▶ Start</button>
|
|
1319
|
+
<button id="pauseLearning" class="learn-btn pause" disabled>⏸ Pause</button>
|
|
1320
|
+
<button id="stopLearning" class="learn-btn stop" disabled>⏹ Stop</button>
|
|
1321
|
+
</div>
|
|
1322
|
+
|
|
1323
|
+
<!-- Session Stats -->
|
|
1324
|
+
<div class="learning-stats" id="learningStats">
|
|
1325
|
+
<div class="stat-row">
|
|
1326
|
+
<span class="stat-label">Iterations:</span>
|
|
1327
|
+
<span class="stat-value" id="iterationCount">0</span>
|
|
1328
|
+
</div>
|
|
1329
|
+
<div class="stat-row">
|
|
1330
|
+
<span class="stat-label">Queries:</span>
|
|
1331
|
+
<span class="stat-value" id="queryCount">0</span>
|
|
1332
|
+
</div>
|
|
1333
|
+
<div class="stat-row">
|
|
1334
|
+
<span class="stat-label">Ingested:</span>
|
|
1335
|
+
<span class="stat-value" id="ingestedCount">0</span>
|
|
1336
|
+
</div>
|
|
1337
|
+
</div>
|
|
1338
|
+
|
|
1339
|
+
<!-- Current Activity -->
|
|
1340
|
+
<div class="learning-activity" id="learningActivity">
|
|
1341
|
+
<div class="activity-label">Current Activity:</div>
|
|
1342
|
+
<div class="activity-content" id="currentActivity">
|
|
1343
|
+
Waiting to start...
|
|
1344
|
+
</div>
|
|
1345
|
+
</div>
|
|
1346
|
+
|
|
1347
|
+
<!-- Eavesdrop Log -->
|
|
1348
|
+
<div class="eavesdrop-log" id="eavesdropLog">
|
|
1349
|
+
<div class="log-header">
|
|
1350
|
+
<span>📡 Eavesdrop Log</span>
|
|
1351
|
+
<button class="log-clear" id="clearEavesdrop">Clear</button>
|
|
1352
|
+
</div>
|
|
1353
|
+
<div class="log-entries" id="logEntries">
|
|
1354
|
+
<!-- Log entries added dynamically -->
|
|
1355
|
+
</div>
|
|
1356
|
+
</div>
|
|
1357
|
+
</div>
|
|
1358
|
+
</div>
|
|
1359
|
+
```
|
|
1360
|
+
|
|
1361
|
+
## CLI Commands
|
|
1362
|
+
|
|
1363
|
+
Add to `apps/sentient/lib/app/cli.js`:
|
|
1364
|
+
|
|
1365
|
+
```javascript
|
|
1366
|
+
// Handle /learn command
|
|
1367
|
+
case 'learn':
|
|
1368
|
+
if (args[0] === 'start') {
|
|
1369
|
+
await this.startLearning();
|
|
1370
|
+
} else if (args[0] === 'stop') {
|
|
1371
|
+
await this.stopLearning();
|
|
1372
|
+
} else if (args[0] === 'status') {
|
|
1373
|
+
this.showLearningStatus();
|
|
1374
|
+
} else {
|
|
1375
|
+
console.log('Usage: /learn [start|stop|status]');
|
|
1376
|
+
}
|
|
1377
|
+
break;
|
|
1378
|
+
|
|
1379
|
+
// Handle /eavesdrop command
|
|
1380
|
+
case 'eavesdrop':
|
|
1381
|
+
this.startEavesdrop();
|
|
1382
|
+
break;
|
|
1383
|
+
```
|
|
1384
|
+
|
|
1385
|
+
## Configuration
|
|
1386
|
+
|
|
1387
|
+
`apps/sentient/lib/learning/config.js`:
|
|
1388
|
+
|
|
1389
|
+
```javascript
|
|
1390
|
+
module.exports = {
|
|
1391
|
+
learner: {
|
|
1392
|
+
iterationInterval: 30000, // 30 seconds between iterations
|
|
1393
|
+
reflectionInterval: 300000, // 5 minutes between reflections
|
|
1394
|
+
curiosityThreshold: 0.3, // Minimum curiosity to trigger learning
|
|
1395
|
+
maxIterationsPerSession: 100 // Safety limit
|
|
1396
|
+
},
|
|
1397
|
+
|
|
1398
|
+
chaperone: {
|
|
1399
|
+
rateLimit: 10, // Requests per minute
|
|
1400
|
+
llmUrl: 'http://localhost:1234/v1', // Chaperone LLM endpoint
|
|
1401
|
+
timeout: 30000 // Request timeout
|
|
1402
|
+
},
|
|
1403
|
+
|
|
1404
|
+
safety: {
|
|
1405
|
+
allowedDomains: [
|
|
1406
|
+
'arxiv.org',
|
|
1407
|
+
'github.com',
|
|
1408
|
+
'wikipedia.org',
|
|
1409
|
+
'docs.python.org',
|
|
1410
|
+
'developer.mozilla.org'
|
|
1411
|
+
],
|
|
1412
|
+
allowedPaths: [
|
|
1413
|
+
'~/incoming',
|
|
1414
|
+
'~/Documents',
|
|
1415
|
+
'~/papers'
|
|
1416
|
+
],
|
|
1417
|
+
maxContentSize: 10 * 1024 * 1024, // 10MB
|
|
1418
|
+
maxFilesPerSession: 50
|
|
1419
|
+
},
|
|
1420
|
+
|
|
1421
|
+
ingester: {
|
|
1422
|
+
maxChunkSize: 2000,
|
|
1423
|
+
overlapSize: 200
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1426
|
+
```
|
|
1427
|
+
|
|
1428
|
+
## File Structure
|
|
1429
|
+
|
|
1430
|
+
```
|
|
1431
|
+
apps/sentient/lib/learning/
|
|
1432
|
+
├── index.js # Exports all components
|
|
1433
|
+
├── config.js # Configuration
|
|
1434
|
+
├── curiosity.js # CuriosityEngine
|
|
1435
|
+
├── chaperone.js # ChaperoneAPI
|
|
1436
|
+
├── safety-filter.js # SafetyFilter
|
|
1437
|
+
├── learner.js # AutonomousLearner
|
|
1438
|
+
├── query.js # QueryFormulator
|
|
1439
|
+
├── ingester.js # ContentIngester
|
|
1440
|
+
├── reflector.js # ReflectionLoop
|
|
1441
|
+
└── fetcher.js # ContentFetcher (HTTP client)
|
|
1442
|
+
```
|
|
1443
|
+
|
|
1444
|
+
## Implementation Phases
|
|
1445
|
+
|
|
1446
|
+
### Phase 1: Core Infrastructure (Week 1)
|
|
1447
|
+
1. Create `lib/learning/` directory structure
|
|
1448
|
+
2. Implement `ChaperoneAPI` class
|
|
1449
|
+
3. Implement `SafetyFilter` class
|
|
1450
|
+
4. Add server endpoints for learning control
|
|
1451
|
+
|
|
1452
|
+
### Phase 2: Learning Engine (Week 2)
|
|
1453
|
+
1. Implement `CuriosityEngine`
|
|
1454
|
+
2. Implement `QueryFormulator`
|
|
1455
|
+
3. Implement `ContentIngester`
|
|
1456
|
+
4. Implement `AutonomousLearner` main loop
|
|
1457
|
+
|
|
1458
|
+
### Phase 3: Reflection & Integration (Week 3)
|
|
1459
|
+
1. Implement `ReflectionLoop`
|
|
1460
|
+
2. Add memory integration for learned content
|
|
1461
|
+
3. Add SMF updates from learning
|
|
1462
|
+
4. Implement eavesdrop SSE stream
|
|
1463
|
+
|
|
1464
|
+
### Phase 4: UI & CLI (Week 4)
|
|
1465
|
+
1. Add Learning panel to web UI
|
|
1466
|
+
2. Add eavesdrop real-time display
|
|
1467
|
+
3. Add CLI commands `/learn` and `/eavesdrop`
|
|
1468
|
+
4. Testing and polish
|
|
1469
|
+
|
|
1470
|
+
## Security Considerations
|
|
1471
|
+
|
|
1472
|
+
1. **Chaperone Isolation**: All external requests MUST go through the chaperone
|
|
1473
|
+
2. **No Direct Network Access**: The learner cannot make HTTP requests directly
|
|
1474
|
+
3. **Whitelist Enforcement**: Only pre-approved domains can be accessed
|
|
1475
|
+
4. **Content Size Limits**: Prevent memory exhaustion attacks
|
|
1476
|
+
5. **Rate Limiting**: Prevent API abuse
|
|
1477
|
+
6. **Sandboxed File Access**: Only designated directories accessible
|
|
1478
|
+
7. **Session Limits**: Maximum files/iterations per session
|
|
1479
|
+
8. **Audit Logging**: All interactions logged for review
|
|
1480
|
+
|
|
1481
|
+
## Summary
|
|
1482
|
+
|
|
1483
|
+
This design enables the Sentient Observer to learn autonomously while maintaining security through a Chaperone layer. Key features:
|
|
1484
|
+
|
|
1485
|
+
1. **Curiosity-Driven Learning**: The observer detects its own knowledge gaps
|
|
1486
|
+
2. **Chaperone Mediation**: All external access goes through a trusted intermediary
|
|
1487
|
+
3. **Real-Time Eavesdropping**: Users can watch learning in progress via SSE streams
|
|
1488
|
+
4. **Memory Integration**: Learned content is encoded into the holographic memory field
|
|
1489
|
+
5. **Reflection Loop**: Periodic consolidation generates deeper insights
|
|
1490
|
+
6. **Safety First**: Multiple layers of security protect the system
|
|
1491
|
+
|
|
1492
|
+
The architecture respects the observer's existing SMF orientation and integrates learned knowledge as new memory traces with proper semantic encoding.
|