25 KiB
25 KiB
SONA Memory Dreams: Offline Consolidation Engine
Creativity Through Neural Replay and Recombination
1. Biological Inspiration
Why Dreams Matter for Learning
HUMAN SLEEP-BASED LEARNING
══════════════════════════
Awake: Sleep (REM): Next Day:
───────────────── ───────────────── ─────────────────
• New experiences • Replay memories • Consolidated knowledge
• Pattern matching • Recombine ideas • Novel insights
• Working memory • Strengthen important • Creative connections
• Prune unimportant
Research shows that:
- Memory consolidation happens during sleep
- Creative insights emerge from random memory replay
- Neural pruning removes low-value connections
- Analogical reasoning connects distant concepts
SONA's Dream Engine replicates these mechanisms for AI self-improvement.
2. Dream Engine Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ DREAM ENGINE ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────┐ │
│ │ MEMORY GRAPH │──────┐ │
│ └───────────────┘ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ DREAM GENERATOR │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Random │ │Weighted │ │ │
│ │ │ Walks │ │ Sampling│ │ │
│ │ └────┬────┘ └────┬────┘ │ │
│ │ │ │ │ │
│ │ ▼ ▼ │ │
│ │ ┌──────────────────────┐ │ │
│ │ │ Dream Sequence │ │ │
│ │ │ [M₁→M₂→M₃→...→Mₙ] │ │ │
│ │ └──────────┬───────────┘ │ │
│ └─────────────┼───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ DREAM EVALUATOR │ │
│ │ │ │
│ │ • Novelty Score (new connections?) │ │
│ │ • Coherence Score (makes sense?) │ │
│ │ • Utility Score (useful insight?) │ │
│ └─────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ DREAM INTEGRATOR │ │
│ │ │ │
│ │ • Add weak creative edges │ │
│ │ • Update pattern associations │ │
│ │ • Generate novel hypotheses │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
3. Dream Generation
Random Walk Memory Replay
/// Dream generator using random walks on memory graph
pub struct DreamGenerator {
/// Temperature for random walk (higher = more random)
temperature: f32,
/// Maximum dream length
max_length: usize,
/// Minimum coherence threshold
min_coherence: f32,
/// Creativity bias (prefer novel connections)
creativity_bias: f32,
}
impl DreamGenerator {
/// Generate a single dream sequence
pub fn generate_dream(
&self,
memory: &MemoryGraph,
start_node: Option<NodeId>,
) -> Dream {
let mut sequence = Vec::new();
let mut visited = HashSet::new();
// Start from random high-activation node if not specified
let current = start_node.unwrap_or_else(|| {
memory.sample_by_activation()
});
sequence.push(current);
visited.insert(current);
// Random walk with creativity-weighted transitions
for _ in 0..self.max_length {
let neighbors = memory.get_neighbors(current);
if neighbors.is_empty() {
break;
}
// Compute transition probabilities
let probs: Vec<f32> = neighbors.iter()
.map(|&(neighbor, edge_weight)| {
let novelty_bonus = if visited.contains(&neighbor) {
0.1 // Discourage revisits
} else {
1.0 + self.creativity_bias * (1.0 - memory.get_access_frequency(neighbor))
};
(edge_weight * novelty_bonus).powf(1.0 / self.temperature)
})
.collect();
// Sample next node
let next = sample_weighted(&neighbors, &probs);
if let Some((next_node, _)) = next {
sequence.push(next_node);
visited.insert(next_node);
} else {
break;
}
}
Dream {
sequence,
temperature: self.temperature,
timestamp: chrono::Utc::now().timestamp(),
}
}
/// Generate creative jump dream (non-local connections)
pub fn generate_creative_dream(
&self,
memory: &MemoryGraph,
num_jumps: usize,
) -> Dream {
let mut sequence = Vec::new();
// Sample diverse starting points
let anchors = memory.sample_diverse(num_jumps, 0.3);
for anchor in anchors {
sequence.push(anchor);
// Short local walk from each anchor
let local_walk = self.generate_dream(memory, Some(anchor));
sequence.extend(local_walk.sequence.iter().skip(1).take(3));
}
Dream {
sequence,
temperature: self.temperature * 2.0, // Higher temperature for creative dreams
timestamp: chrono::Utc::now().timestamp(),
}
}
}
/// A dream sequence
pub struct Dream {
/// Sequence of visited memory nodes
pub sequence: Vec<NodeId>,
/// Temperature used for generation
pub temperature: f32,
/// Generation timestamp
pub timestamp: i64,
}
4. Dream Evaluation
Measuring Dream Quality
/// Evaluator for dream quality
pub struct DreamEvaluator {
/// Memory graph reference
memory: Arc<MemoryGraph>,
/// Novelty detection threshold
novelty_threshold: f32,
}
impl DreamEvaluator {
/// Evaluate dream quality across multiple dimensions
pub fn evaluate(&self, dream: &Dream) -> DreamQuality {
DreamQuality {
novelty: self.compute_novelty(dream),
coherence: self.compute_coherence(dream),
utility: self.compute_utility(dream),
diversity: self.compute_diversity(dream),
}
}
/// Novelty: How many new connections are suggested?
fn compute_novelty(&self, dream: &Dream) -> f32 {
let mut novel_pairs = 0;
let mut total_pairs = 0;
for i in 0..dream.sequence.len() {
for j in (i+1)..dream.sequence.len() {
total_pairs += 1;
let node_a = dream.sequence[i];
let node_b = dream.sequence[j];
// Check if edge exists
if !self.memory.has_edge(node_a, node_b) {
// Check semantic similarity
let emb_a = self.memory.get_embedding(node_a);
let emb_b = self.memory.get_embedding(node_b);
let sim = cosine_similarity(&emb_a, &emb_b);
// Novel = no edge but moderate similarity
if sim > 0.3 && sim < 0.8 {
novel_pairs += 1;
}
}
}
}
novel_pairs as f32 / total_pairs.max(1) as f32
}
/// Coherence: Does the dream sequence make semantic sense?
fn compute_coherence(&self, dream: &Dream) -> f32 {
if dream.sequence.len() < 2 {
return 1.0;
}
let mut coherence_sum = 0.0f32;
for window in dream.sequence.windows(2) {
let emb_a = self.memory.get_embedding(window[0]);
let emb_b = self.memory.get_embedding(window[1]);
coherence_sum += cosine_similarity(&emb_a, &emb_b);
}
coherence_sum / (dream.sequence.len() - 1) as f32
}
/// Utility: Are the suggested connections potentially useful?
fn compute_utility(&self, dream: &Dream) -> f32 {
// Based on node quality scores and access patterns
let avg_quality: f32 = dream.sequence.iter()
.map(|&id| self.memory.get_node_quality(id))
.sum::<f32>() / dream.sequence.len() as f32;
// Higher utility if connecting high-quality nodes
avg_quality
}
/// Diversity: How diverse are the visited nodes?
fn compute_diversity(&self, dream: &Dream) -> f32 {
// Average pairwise distance in embedding space
let embeddings: Vec<_> = dream.sequence.iter()
.map(|&id| self.memory.get_embedding(id))
.collect();
let mut total_dist = 0.0f32;
let mut count = 0;
for i in 0..embeddings.len() {
for j in (i+1)..embeddings.len() {
total_dist += 1.0 - cosine_similarity(&embeddings[i], &embeddings[j]);
count += 1;
}
}
total_dist / count.max(1) as f32
}
}
#[derive(Debug, Clone)]
pub struct DreamQuality {
/// How many novel connections suggested (0-1)
pub novelty: f32,
/// How semantically coherent (0-1)
pub coherence: f32,
/// How useful the connections might be (0-1)
pub utility: f32,
/// How diverse the dream content (0-1)
pub diversity: f32,
}
impl DreamQuality {
/// Overall quality score
pub fn overall(&self) -> f32 {
// Weighted combination favoring novelty and coherence
0.4 * self.novelty + 0.3 * self.coherence + 0.2 * self.utility + 0.1 * self.diversity
}
/// Is this dream worth integrating?
pub fn is_valuable(&self, threshold: f32) -> bool {
self.novelty > 0.3 && self.coherence > 0.4 && self.overall() > threshold
}
}
5. Dream Integration
Applying Dream Insights to Memory
/// Integrates valuable dreams into memory graph
pub struct DreamIntegrator {
/// Memory graph to update
memory: Arc<RwLock<MemoryGraph>>,
/// Strength of new creative edges
creative_edge_strength: f32,
/// Decay factor for dream-derived edges
dream_edge_decay: f32,
}
impl DreamIntegrator {
/// Integrate a valuable dream into memory
pub fn integrate(&self, dream: &Dream, quality: &DreamQuality) -> IntegrationResult {
let mut result = IntegrationResult::default();
if !quality.is_valuable(0.5) {
return result; // Skip low-quality dreams
}
let mut memory = self.memory.write();
// Extract novel connections from dream
let novel_connections = self.extract_novel_connections(dream, &memory);
for (node_a, node_b, strength) in novel_connections {
// Add weak creative edge
let edge_strength = self.creative_edge_strength * strength * quality.overall();
memory.add_edge(
node_a,
node_b,
EdgeType::Creative,
edge_strength,
);
result.edges_added += 1;
}
// Update node associations based on dream co-occurrence
for window in dream.sequence.windows(3) {
memory.update_association(window[0], window[2], 0.01);
}
result.dream_quality = quality.overall();
result
}
fn extract_novel_connections(
&self,
dream: &Dream,
memory: &MemoryGraph,
) -> Vec<(NodeId, NodeId, f32)> {
let mut connections = Vec::new();
for i in 0..dream.sequence.len() {
for j in (i+1)..dream.sequence.len().min(i+5) { // Only nearby in sequence
let node_a = dream.sequence[i];
let node_b = dream.sequence[j];
if !memory.has_edge(node_a, node_b) {
let emb_a = memory.get_embedding(node_a);
let emb_b = memory.get_embedding(node_b);
let sim = cosine_similarity(&emb_a, &emb_b);
if sim > 0.3 {
// Connection strength based on similarity and sequence proximity
let proximity_factor = 1.0 / (j - i) as f32;
let strength = sim * proximity_factor;
connections.push((node_a, node_b, strength));
}
}
}
}
connections
}
}
#[derive(Default)]
pub struct IntegrationResult {
pub edges_added: usize,
pub associations_updated: usize,
pub dream_quality: f32,
}
6. Memory Consolidation
Strengthening Important Memories
/// Consolidation engine for memory pruning and strengthening
pub struct ConsolidationEngine {
/// Memory graph reference
memory: Arc<RwLock<MemoryGraph>>,
/// Minimum access frequency for retention
min_access_frequency: f32,
/// Age decay factor (older = more decay)
age_decay: f32,
/// Quality threshold for preservation
quality_threshold: f32,
}
impl ConsolidationEngine {
/// Run full consolidation pass
pub fn consolidate(&self) -> ConsolidationReport {
let mut report = ConsolidationReport::default();
// Phase 1: Identify memories by value
let (high_value, medium_value, low_value) = self.categorize_memories();
report.high_value_count = high_value.len();
report.medium_value_count = medium_value.len();
report.low_value_count = low_value.len();
// Phase 2: Strengthen high-value memories
for &node_id in &high_value {
self.strengthen_memory(node_id);
report.memories_strengthened += 1;
}
// Phase 3: Decay low-value memories
for &node_id in &low_value {
let retained = self.decay_memory(node_id);
if retained {
report.memories_decayed += 1;
} else {
report.memories_removed += 1;
}
}
// Phase 4: Prune weak edges
let pruned = self.prune_weak_edges();
report.edges_pruned = pruned;
// Phase 5: Merge similar memories
let merged = self.merge_similar_memories();
report.memories_merged = merged;
report
}
fn categorize_memories(&self) -> (Vec<NodeId>, Vec<NodeId>, Vec<NodeId>) {
let memory = self.memory.read();
let mut high = Vec::new();
let mut medium = Vec::new();
let mut low = Vec::new();
for node in memory.iter_nodes() {
let value_score = self.compute_value_score(node);
if value_score > 0.7 {
high.push(node.id);
} else if value_score > 0.3 {
medium.push(node.id);
} else {
low.push(node.id);
}
}
(high, medium, low)
}
fn compute_value_score(&self, node: &MemoryNode) -> f32 {
let memory = self.memory.read();
// Factors:
// 1. Access frequency (more access = more valuable)
let freq_score = (node.access_count as f32 / 100.0).min(1.0);
// 2. Recency (recent = more valuable)
let age_days = (chrono::Utc::now().timestamp() - node.last_accessed) / 86400;
let recency_score = (-self.age_decay * age_days as f32).exp();
// 3. Quality (explicit quality score)
let quality_score = node.quality_score;
// 4. Connectivity (well-connected = more valuable)
let degree = memory.node_degree(node.id);
let connectivity_score = (degree as f32 / 10.0).min(1.0);
// Weighted combination
0.3 * freq_score + 0.2 * recency_score + 0.3 * quality_score + 0.2 * connectivity_score
}
fn strengthen_memory(&self, node_id: NodeId) {
let mut memory = self.memory.write();
// Increase edge weights to this node
for edge in memory.get_edges_to(node_id) {
memory.update_edge_weight(edge.from, node_id, EdgeUpdate::Multiply(1.1));
}
// Mark as consolidated
if let Some(node) = memory.get_node_mut(node_id) {
node.consolidation_count += 1;
node.last_consolidated = chrono::Utc::now().timestamp();
}
}
fn decay_memory(&self, node_id: NodeId) -> bool {
let mut memory = self.memory.write();
// Reduce edge weights
for edge in memory.get_edges_to(node_id) {
memory.update_edge_weight(edge.from, node_id, EdgeUpdate::Multiply(0.5));
}
// Check if node should be removed entirely
let total_incoming_weight: f32 = memory.get_edges_to(node_id)
.iter()
.map(|e| e.weight)
.sum();
if total_incoming_weight < 0.01 {
// Remove isolated or nearly-isolated node
memory.remove_node(node_id);
false // Not retained
} else {
true // Retained but weakened
}
}
fn prune_weak_edges(&self) -> usize {
let mut memory = self.memory.write();
let weak_edges: Vec<_> = memory.iter_edges()
.filter(|e| e.weight < 0.01)
.map(|e| e.id)
.collect();
for edge_id in &weak_edges {
memory.remove_edge(*edge_id);
}
weak_edges.len()
}
fn merge_similar_memories(&self) -> usize {
let mut memory = self.memory.write();
let mut merged_count = 0;
// Find highly similar node pairs
let nodes: Vec<_> = memory.iter_nodes().collect();
for i in 0..nodes.len() {
for j in (i+1)..nodes.len() {
let sim = cosine_similarity(&nodes[i].embedding, &nodes[j].embedding);
if sim > 0.98 {
// Merge j into i
memory.merge_nodes(nodes[i].id, nodes[j].id);
merged_count += 1;
}
}
}
merged_count
}
}
#[derive(Default)]
pub struct ConsolidationReport {
pub high_value_count: usize,
pub medium_value_count: usize,
pub low_value_count: usize,
pub memories_strengthened: usize,
pub memories_decayed: usize,
pub memories_removed: usize,
pub memories_merged: usize,
pub edges_pruned: usize,
}
7. Full Dream Cycle
Orchestrating the Dream Process
/// Complete dream cycle orchestrator
pub struct DreamCycle {
generator: DreamGenerator,
evaluator: DreamEvaluator,
integrator: DreamIntegrator,
consolidator: ConsolidationEngine,
config: DreamCycleConfig,
}
impl DreamCycle {
/// Run complete dream cycle (weekly maintenance)
pub async fn run(&self) -> DreamCycleReport {
let start = Instant::now();
let mut report = DreamCycleReport::default();
// Phase 1: Generate dreams
tracing::info!("Starting dream generation phase");
let dreams = self.generate_dreams();
report.dreams_generated = dreams.len();
// Phase 2: Evaluate dreams
tracing::info!("Evaluating {} dreams", dreams.len());
let evaluated: Vec<_> = dreams.iter()
.map(|d| (d, self.evaluator.evaluate(d)))
.collect();
// Phase 3: Integrate valuable dreams
tracing::info!("Integrating valuable dreams");
for (dream, quality) in &evaluated {
if quality.is_valuable(self.config.dream_threshold) {
let result = self.integrator.integrate(dream, quality);
report.edges_added += result.edges_added;
report.dreams_integrated += 1;
}
}
// Phase 4: Memory consolidation
tracing::info!("Running memory consolidation");
report.consolidation = self.consolidator.consolidate();
report.elapsed_ms = start.elapsed().as_millis() as u64;
report.timestamp = chrono::Utc::now().timestamp();
tracing::info!(
dreams = report.dreams_generated,
integrated = report.dreams_integrated,
edges = report.edges_added,
elapsed_ms = report.elapsed_ms,
"Dream cycle completed"
);
report
}
fn generate_dreams(&self) -> Vec<Dream> {
let mut dreams = Vec::new();
// Regular random walk dreams
for _ in 0..self.config.num_regular_dreams {
let dream = self.generator.generate_dream(&self.memory, None);
dreams.push(dream);
}
// Creative jump dreams
for _ in 0..self.config.num_creative_dreams {
let dream = self.generator.generate_creative_dream(
&self.memory,
self.config.creative_jump_count,
);
dreams.push(dream);
}
dreams
}
}
#[derive(Default)]
pub struct DreamCycleReport {
pub dreams_generated: usize,
pub dreams_integrated: usize,
pub edges_added: usize,
pub consolidation: ConsolidationReport,
pub elapsed_ms: u64,
pub timestamp: i64,
}
8. Integration with exo-exotic Dreams Module
SONA integrates with the exo-ai-2025 dream experiments:
// From exo-exotic crate
use exo_exotic::experiments::dreams::{
DreamExperiment,
DreamConfig,
NoveltyMeasure,
};
impl DreamCycle {
/// Run advanced dream experiments from exo-exotic
pub async fn run_exotic_dreams(&self) -> ExoticDreamReport {
let dream_experiment = DreamExperiment::new(DreamConfig {
memory_count: self.memory.node_count(),
replay_probability: 0.7,
recombination_rate: 0.3,
novelty_threshold: 0.5,
});
let result = dream_experiment.run(&self.memory).await;
ExoticDreamReport {
novelty_score: result.novelty,
coherence_score: result.coherence,
creative_insights: result.insights.len(),
new_hypotheses: result.hypotheses,
}
}
}
Summary
SONA's Dream Engine enables:
| Feature | Mechanism | Outcome |
|---|---|---|
| Memory Replay | Random walks on memory graph | Strengthens important connections |
| Creative Recombination | High-temperature sampling | Discovers novel associations |
| Quality Filtering | Novelty + coherence metrics | Only valuable dreams integrated |
| Weak Edge Creation | Dream-derived connections | Enables creative retrieval |
| Memory Consolidation | Value-based pruning | Efficient memory usage |
Dreams allow SONA to:
- Discover connections it wouldn't find through normal operation
- Explore the hypothesis space without user cost
- Consolidate valuable knowledge
- Prune low-value information
- Remain creative while staying grounded