@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.
@@ -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.