Skip to main content

shodh_memory/memory/
introspection.rs

1//! Memory Consolidation Introspection
2//!
3//! Provides visibility into what the memory system is learning:
4//! - Which memories are strengthening/decaying
5//! - What associations are forming
6//! - When consolidation events occur
7//!
8//! This makes the "brain" transparent rather than a black box.
9
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12use std::collections::VecDeque;
13
14/// Maximum number of events to keep in the event buffer
15const MAX_EVENT_BUFFER_SIZE: usize = 1000;
16
17/// Types of consolidation events that can occur
18#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(tag = "type", rename_all = "snake_case")]
20pub enum ConsolidationEvent {
21    /// Memory activation was strengthened (accessed/boosted)
22    MemoryStrengthened {
23        memory_id: String,
24        content_preview: String,
25        activation_before: f32,
26        activation_after: f32,
27        reason: StrengtheningReason,
28        timestamp: DateTime<Utc>,
29    },
30
31    /// Memory activation decayed during maintenance
32    MemoryDecayed {
33        memory_id: String,
34        content_preview: String,
35        activation_before: f32,
36        activation_after: f32,
37        at_risk: bool, // Below threshold soon
38        timestamp: DateTime<Utc>,
39    },
40
41    /// Hebbian edge was formed between memories
42    EdgeFormed {
43        from_memory_id: String,
44        to_memory_id: String,
45        initial_strength: f32,
46        reason: EdgeFormationReason,
47        timestamp: DateTime<Utc>,
48    },
49
50    /// Hebbian edge was strengthened (co-activation)
51    EdgeStrengthened {
52        from_memory_id: String,
53        to_memory_id: String,
54        strength_before: f32,
55        strength_after: f32,
56        co_activations: u32,
57        timestamp: DateTime<Utc>,
58    },
59
60    /// Edge became potentiated (permanent through LTP)
61    EdgePotentiated {
62        from_memory_id: String,
63        to_memory_id: String,
64        final_strength: f32,
65        total_co_activations: u32,
66        timestamp: DateTime<Utc>,
67    },
68
69    /// Edge was pruned (decayed below threshold)
70    EdgePruned {
71        from_memory_id: String,
72        to_memory_id: String,
73        final_strength: f32,
74        reason: PruningReason,
75        timestamp: DateTime<Utc>,
76    },
77
78    /// Semantic fact was extracted from episodic memories
79    FactExtracted {
80        fact_id: String,
81        fact_content: String,
82        confidence: f32,
83        source_memory_count: usize,
84        fact_type: String,
85        timestamp: DateTime<Utc>,
86    },
87
88    /// Existing fact was reinforced with new evidence
89    FactReinforced {
90        fact_id: String,
91        fact_content: String,
92        confidence_before: f32,
93        confidence_after: f32,
94        new_support_count: usize,
95        timestamp: DateTime<Utc>,
96    },
97
98    /// Fact confidence decayed due to lack of reinforcement
99    FactDecayed {
100        fact_id: String,
101        fact_content: String,
102        confidence_before: f32,
103        confidence_after: f32,
104        days_since_reinforcement: i64,
105        timestamp: DateTime<Utc>,
106    },
107
108    /// Fact was deleted (confidence fell below threshold)
109    FactDeleted {
110        fact_id: String,
111        fact_content: String,
112        final_confidence: f32,
113        support_count: usize,
114        reason: String,
115        timestamp: DateTime<Utc>,
116    },
117
118    /// Memory was promoted to a higher tier
119    MemoryPromoted {
120        memory_id: String,
121        content_preview: String,
122        from_tier: String,
123        to_tier: String,
124        timestamp: DateTime<Utc>,
125    },
126
127    /// Maintenance cycle completed
128    MaintenanceCycleCompleted {
129        memories_processed: usize,
130        memories_decayed: usize,
131        edges_pruned: usize,
132        duration_ms: u64,
133        timestamp: DateTime<Utc>,
134    },
135
136    // SHO-105: Memory Replay Events
137    // Based on Rasch & Born (2013) - sleep consolidation through replay
138    /// Memory was replayed during consolidation cycle
139    MemoryReplayed {
140        memory_id: String,
141        content_preview: String,
142        activation_before: f32,
143        activation_after: f32,
144        replay_priority: f32,
145        connected_memories_replayed: usize,
146        timestamp: DateTime<Utc>,
147    },
148
149    /// Replay cycle completed (batch of memories replayed)
150    ReplayCycleCompleted {
151        memories_replayed: usize,
152        edges_strengthened: usize,
153        total_priority_score: f32,
154        duration_ms: u64,
155        timestamp: DateTime<Utc>,
156    },
157
158    // SHO-106: Memory Interference Events
159    // Based on Anderson & Neely (1996) - retrieval competition
160    /// Interference detected between memories
161    InterferenceDetected {
162        new_memory_id: String,
163        old_memory_id: String,
164        similarity: f32,
165        interference_type: InterferenceType,
166        timestamp: DateTime<Utc>,
167    },
168
169    /// Memory weakened due to interference
170    MemoryWeakened {
171        memory_id: String,
172        content_preview: String,
173        activation_before: f32,
174        activation_after: f32,
175        interfering_memory_id: String,
176        interference_type: InterferenceType,
177        timestamp: DateTime<Utc>,
178    },
179
180    /// Retrieval competition occurred (similar memories competed)
181    RetrievalCompetition {
182        query_preview: String,
183        winner_memory_id: String,
184        suppressed_memory_ids: Vec<String>,
185        competition_factor: f32,
186        timestamp: DateTime<Utc>,
187    },
188
189    // PIPE-2: Pattern-Triggered Replay Events
190    // Based on hippocampal sharp-wave ripple research (Rasch & Born 2013)
191    /// Pattern-triggered replay initiated (not timer-based)
192    PatternTriggeredReplay {
193        trigger_type: String,
194        memory_ids: Vec<String>,
195        pattern_confidence: f32,
196        trigger_description: String,
197        timestamp: DateTime<Utc>,
198    },
199
200    /// Entity co-occurrence pattern detected
201    EntityPatternDetected {
202        entity_group: Vec<String>,
203        memory_ids: Vec<String>,
204        overlap_score: f32,
205        confidence: f32,
206        timestamp: DateTime<Utc>,
207    },
208
209    /// Semantic cluster formed (dense similarity group)
210    SemanticClusterFormed {
211        memory_ids: Vec<String>,
212        cluster_size: usize,
213        avg_similarity: f32,
214        centroid_id: String,
215        timestamp: DateTime<Utc>,
216    },
217
218    /// Temporal cluster (session) detected
219    TemporalClusterFormed {
220        memory_ids: Vec<String>,
221        session_duration_secs: i64,
222        session_id: Option<String>,
223        timestamp: DateTime<Utc>,
224    },
225
226    /// Salience spike detected (high importance/arousal memory)
227    SalienceSpikeDetected {
228        memory_id: String,
229        content_preview: String,
230        importance: f32,
231        arousal: f32,
232        surprise_factor: f32,
233        timestamp: DateTime<Utc>,
234    },
235
236    /// Behavioral pattern change triggered replay
237    BehavioralChangeDetected {
238        change_type: String,
239        affected_memory_ids: Vec<String>,
240        context: String,
241        timestamp: DateTime<Utc>,
242    },
243
244    /// Generic pattern detected (covers any trigger type)
245    PatternDetected {
246        trigger_type: String,
247        description: String,
248        memory_ids: Vec<String>,
249        timestamp: DateTime<Utc>,
250    },
251
252    // Memory-Edge Tier Coupling Events
253    // Direction 1: Edge tier promotion → Memory importance boost
254    /// Edge tier promotion boosted a memory's importance
255    EdgePromotionBoostApplied {
256        memory_id: String,
257        entity_name: String,
258        old_tier: String,
259        new_tier: String,
260        importance_boost: f64,
261        new_importance: f64,
262        timestamp: DateTime<Utc>,
263    },
264
265    // Direction 2: Edge pruning → Orphan detection
266    /// Memory became a graph orphan (lost all edges)
267    GraphOrphanDetected {
268        memory_id: String,
269        entity_count: usize,
270        compensatory_boost: f64,
271        timestamp: DateTime<Utc>,
272    },
273
274    // Direction 3: Graph health → Promotion threshold adjustment
275    /// Memory tier promotion threshold adjusted by graph health
276    GraphAdjustedPromotion {
277        memory_id: String,
278        base_threshold: f64,
279        adjusted_threshold: f64,
280        l2_plus_edge_count: usize,
281        promoted: bool,
282        timestamp: DateTime<Utc>,
283    },
284
285    /// Graph decay consolidated into single call site (double-decay fix diagnostic)
286    GraphDecayConsolidated {
287        pruned_count: usize,
288        orphaned_entities: usize,
289        timestamp: DateTime<Utc>,
290    },
291}
292
293/// Types of memory interference (SHO-106)
294#[derive(Debug, Clone, Serialize, Deserialize)]
295#[serde(rename_all = "snake_case")]
296pub enum InterferenceType {
297    /// New learning disrupts old memories
298    Retroactive,
299    /// Old memories interfere with new learning
300    Proactive,
301    /// Similar memories compete during retrieval
302    RetrievalCompetition,
303}
304
305/// Reasons for memory strengthening
306#[derive(Debug, Clone, Serialize, Deserialize)]
307#[serde(rename_all = "snake_case")]
308pub enum StrengtheningReason {
309    /// Accessed during recall
310    Recalled,
311    /// Part of spreading activation
312    SpreadingActivation,
313    /// Explicitly boosted by user
314    ExplicitBoost,
315    /// Co-retrieved with another memory
316    CoRetrieval,
317    /// Potentiated during maintenance (Hebbian LTP for frequently accessed memories)
318    MaintenancePotentiation,
319}
320
321/// Reasons for edge formation
322#[derive(Debug, Clone, Serialize, Deserialize)]
323#[serde(rename_all = "snake_case")]
324pub enum EdgeFormationReason {
325    /// Co-retrieved during recall
326    CoRetrieval,
327    /// Shared entities
328    SharedEntities,
329    /// Semantic similarity above threshold
330    SemanticSimilarity,
331    /// Temporal proximity
332    TemporalProximity,
333}
334
335/// Reasons for edge pruning
336#[derive(Debug, Clone, Serialize, Deserialize)]
337#[serde(rename_all = "snake_case")]
338pub enum PruningReason {
339    /// Decayed below minimum threshold
340    DecayedBelowThreshold,
341    /// Not accessed for too long
342    Inactivity,
343    /// Explicitly invalidated
344    Invalidated,
345}
346
347/// Aggregated consolidation report for a time period
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct ConsolidationReport {
350    /// Time period covered
351    pub period: ReportPeriod,
352
353    /// Memories that got stronger
354    pub strengthened_memories: Vec<MemoryChange>,
355
356    /// Memories that decayed
357    pub decayed_memories: Vec<MemoryChange>,
358
359    /// New associations formed
360    pub formed_associations: Vec<AssociationChange>,
361
362    /// Associations that got stronger
363    pub strengthened_associations: Vec<AssociationChange>,
364
365    /// Associations that became permanent (LTP)
366    pub potentiated_associations: Vec<AssociationChange>,
367
368    /// Associations that were pruned
369    pub pruned_associations: Vec<AssociationChange>,
370
371    /// Facts extracted from episodic memories
372    pub extracted_facts: Vec<FactChange>,
373
374    /// Facts that were reinforced
375    pub reinforced_facts: Vec<FactChange>,
376
377    /// Facts that decayed due to lack of reinforcement
378    pub decayed_facts: Vec<FactChange>,
379
380    /// Facts that were deleted (confidence too low)
381    pub deleted_facts: Vec<FactChange>,
382
383    // SHO-105: Replay events
384    /// Memories that were replayed for consolidation
385    pub replayed_memories: Vec<ReplayEvent>,
386
387    // SHO-106: Interference events
388    /// Interference events detected
389    pub interference_events: Vec<InterferenceEvent>,
390
391    /// Memories weakened due to interference
392    pub weakened_memories: Vec<MemoryChange>,
393
394    /// Aggregate statistics
395    pub statistics: ConsolidationStats,
396}
397
398/// Replay event details (SHO-105)
399#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct ReplayEvent {
401    pub memory_id: String,
402    pub content_preview: String,
403    pub activation_before: f32,
404    pub activation_after: f32,
405    pub replay_priority: f32,
406    pub connected_memories: usize,
407    pub timestamp: DateTime<Utc>,
408}
409
410/// Interference event details (SHO-106)
411#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct InterferenceEvent {
413    pub new_memory_id: String,
414    pub old_memory_id: String,
415    pub similarity: f32,
416    pub interference_type: InterferenceType,
417    pub timestamp: DateTime<Utc>,
418}
419
420/// Time period for a report
421#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct ReportPeriod {
423    pub start: DateTime<Utc>,
424    pub end: DateTime<Utc>,
425}
426
427/// Change in a memory's state
428#[derive(Debug, Clone, Serialize, Deserialize)]
429pub struct MemoryChange {
430    pub memory_id: String,
431    pub content_preview: String,
432    pub activation_before: f32,
433    pub activation_after: f32,
434    pub change_reason: String,
435    pub at_risk: bool,
436    pub timestamp: DateTime<Utc>,
437}
438
439/// Change in an association/edge
440#[derive(Debug, Clone, Serialize, Deserialize)]
441pub struct AssociationChange {
442    pub from_memory_id: String,
443    pub to_memory_id: String,
444    pub strength_before: Option<f32>,
445    pub strength_after: f32,
446    pub co_activations: Option<u32>,
447    pub reason: String,
448    pub timestamp: DateTime<Utc>,
449}
450
451/// Change in a semantic fact
452#[derive(Debug, Clone, Serialize, Deserialize)]
453pub struct FactChange {
454    pub fact_id: String,
455    pub fact_content: String,
456    pub confidence: f32,
457    pub support_count: usize,
458    pub fact_type: String,
459    pub timestamp: DateTime<Utc>,
460}
461
462/// Aggregate statistics for consolidation
463#[derive(Debug, Clone, Default, Serialize, Deserialize)]
464pub struct ConsolidationStats {
465    pub total_memories: usize,
466    pub memories_strengthened: usize,
467    pub memories_decayed: usize,
468    pub memories_at_risk: usize,
469    pub edges_formed: usize,
470    pub edges_strengthened: usize,
471    pub edges_potentiated: usize,
472    pub edges_pruned: usize,
473    pub facts_extracted: usize,
474    pub facts_reinforced: usize,
475    pub facts_decayed: usize,
476    pub facts_deleted: usize,
477    pub maintenance_cycles: usize,
478    pub total_maintenance_duration_ms: u64,
479    // SHO-105: Replay statistics
480    pub memories_replayed: usize,
481    pub replay_cycles: usize,
482    pub total_replay_priority: f32,
483    // SHO-106: Interference statistics
484    pub interference_events: usize,
485    pub memories_weakened: usize,
486    pub retrieval_competitions: usize,
487}
488
489/// Buffer for storing consolidation events
490#[derive(Debug, Default)]
491pub struct ConsolidationEventBuffer {
492    events: VecDeque<ConsolidationEvent>,
493    max_size: usize,
494}
495
496impl ConsolidationEventBuffer {
497    pub fn new() -> Self {
498        Self {
499            events: VecDeque::new(),
500            max_size: MAX_EVENT_BUFFER_SIZE,
501        }
502    }
503
504    pub fn with_capacity(max_size: usize) -> Self {
505        Self {
506            events: VecDeque::with_capacity(max_size),
507            max_size,
508        }
509    }
510
511    /// Push a new event, evicting oldest if at capacity
512    pub fn push(&mut self, event: ConsolidationEvent) {
513        if self.events.len() >= self.max_size {
514            self.events.pop_front();
515        }
516        self.events.push_back(event);
517    }
518
519    /// Get all events since a given timestamp
520    pub fn events_since(&self, since: DateTime<Utc>) -> Vec<ConsolidationEvent> {
521        self.events
522            .iter()
523            .filter(|e| e.timestamp() >= since)
524            .cloned()
525            .collect()
526    }
527
528    /// Get all events
529    pub fn all_events(&self) -> Vec<ConsolidationEvent> {
530        self.events.iter().cloned().collect()
531    }
532
533    /// Clear all events
534    pub fn clear(&mut self) {
535        self.events.clear();
536    }
537
538    /// Number of events in buffer
539    pub fn len(&self) -> usize {
540        self.events.len()
541    }
542
543    /// Check if buffer is empty
544    pub fn is_empty(&self) -> bool {
545        self.events.is_empty()
546    }
547
548    /// Generate a report from events in a time period
549    pub fn generate_report(
550        &self,
551        since: DateTime<Utc>,
552        until: DateTime<Utc>,
553    ) -> ConsolidationReport {
554        let events: Vec<_> = self
555            .events
556            .iter()
557            .filter(|e| {
558                let ts = e.timestamp();
559                ts >= since && ts <= until
560            })
561            .collect();
562
563        let mut report = ConsolidationReport {
564            period: ReportPeriod {
565                start: since,
566                end: until,
567            },
568            strengthened_memories: Vec::new(),
569            decayed_memories: Vec::new(),
570            formed_associations: Vec::new(),
571            strengthened_associations: Vec::new(),
572            potentiated_associations: Vec::new(),
573            pruned_associations: Vec::new(),
574            extracted_facts: Vec::new(),
575            reinforced_facts: Vec::new(),
576            decayed_facts: Vec::new(),
577            deleted_facts: Vec::new(),
578            // SHO-105: Replay events
579            replayed_memories: Vec::new(),
580            // SHO-106: Interference events
581            interference_events: Vec::new(),
582            weakened_memories: Vec::new(),
583            statistics: ConsolidationStats::default(),
584        };
585
586        for event in events {
587            match event {
588                ConsolidationEvent::MemoryStrengthened {
589                    memory_id,
590                    content_preview,
591                    activation_before,
592                    activation_after,
593                    reason,
594                    timestamp,
595                } => {
596                    report.strengthened_memories.push(MemoryChange {
597                        memory_id: memory_id.clone(),
598                        content_preview: content_preview.clone(),
599                        activation_before: *activation_before,
600                        activation_after: *activation_after,
601                        change_reason: format!("{:?}", reason),
602                        at_risk: false,
603                        timestamp: *timestamp,
604                    });
605                    report.statistics.memories_strengthened += 1;
606                }
607
608                ConsolidationEvent::MemoryDecayed {
609                    memory_id,
610                    content_preview,
611                    activation_before,
612                    activation_after,
613                    at_risk,
614                    timestamp,
615                } => {
616                    report.decayed_memories.push(MemoryChange {
617                        memory_id: memory_id.clone(),
618                        content_preview: content_preview.clone(),
619                        activation_before: *activation_before,
620                        activation_after: *activation_after,
621                        change_reason: "decay".to_string(),
622                        at_risk: *at_risk,
623                        timestamp: *timestamp,
624                    });
625                    report.statistics.memories_decayed += 1;
626                    if *at_risk {
627                        report.statistics.memories_at_risk += 1;
628                    }
629                }
630
631                ConsolidationEvent::EdgeFormed {
632                    from_memory_id,
633                    to_memory_id,
634                    initial_strength,
635                    reason,
636                    timestamp,
637                } => {
638                    report.formed_associations.push(AssociationChange {
639                        from_memory_id: from_memory_id.clone(),
640                        to_memory_id: to_memory_id.clone(),
641                        strength_before: None,
642                        strength_after: *initial_strength,
643                        co_activations: Some(1),
644                        reason: format!("{:?}", reason),
645                        timestamp: *timestamp,
646                    });
647                    report.statistics.edges_formed += 1;
648                }
649
650                ConsolidationEvent::EdgeStrengthened {
651                    from_memory_id,
652                    to_memory_id,
653                    strength_before,
654                    strength_after,
655                    co_activations,
656                    timestamp,
657                } => {
658                    report.strengthened_associations.push(AssociationChange {
659                        from_memory_id: from_memory_id.clone(),
660                        to_memory_id: to_memory_id.clone(),
661                        strength_before: Some(*strength_before),
662                        strength_after: *strength_after,
663                        co_activations: Some(*co_activations),
664                        reason: "co_activation".to_string(),
665                        timestamp: *timestamp,
666                    });
667                    report.statistics.edges_strengthened += 1;
668                }
669
670                ConsolidationEvent::EdgePotentiated {
671                    from_memory_id,
672                    to_memory_id,
673                    final_strength,
674                    total_co_activations,
675                    timestamp,
676                } => {
677                    report.potentiated_associations.push(AssociationChange {
678                        from_memory_id: from_memory_id.clone(),
679                        to_memory_id: to_memory_id.clone(),
680                        strength_before: None,
681                        strength_after: *final_strength,
682                        co_activations: Some(*total_co_activations),
683                        reason: "long_term_potentiation".to_string(),
684                        timestamp: *timestamp,
685                    });
686                    report.statistics.edges_potentiated += 1;
687                }
688
689                ConsolidationEvent::EdgePruned {
690                    from_memory_id,
691                    to_memory_id,
692                    final_strength,
693                    reason,
694                    timestamp,
695                } => {
696                    report.pruned_associations.push(AssociationChange {
697                        from_memory_id: from_memory_id.clone(),
698                        to_memory_id: to_memory_id.clone(),
699                        strength_before: Some(*final_strength),
700                        strength_after: 0.0,
701                        co_activations: None,
702                        reason: format!("{:?}", reason),
703                        timestamp: *timestamp,
704                    });
705                    report.statistics.edges_pruned += 1;
706                }
707
708                ConsolidationEvent::FactExtracted {
709                    fact_id,
710                    fact_content,
711                    confidence,
712                    source_memory_count,
713                    fact_type,
714                    timestamp,
715                } => {
716                    report.extracted_facts.push(FactChange {
717                        fact_id: fact_id.clone(),
718                        fact_content: fact_content.clone(),
719                        confidence: *confidence,
720                        support_count: *source_memory_count,
721                        fact_type: fact_type.clone(),
722                        timestamp: *timestamp,
723                    });
724                    report.statistics.facts_extracted += 1;
725                }
726
727                ConsolidationEvent::FactReinforced {
728                    fact_id,
729                    fact_content,
730                    confidence_after,
731                    new_support_count,
732                    timestamp,
733                    ..
734                } => {
735                    report.reinforced_facts.push(FactChange {
736                        fact_id: fact_id.clone(),
737                        fact_content: fact_content.clone(),
738                        confidence: *confidence_after,
739                        support_count: *new_support_count,
740                        fact_type: "reinforced".to_string(),
741                        timestamp: *timestamp,
742                    });
743                    report.statistics.facts_reinforced += 1;
744                }
745
746                ConsolidationEvent::FactDecayed {
747                    fact_id,
748                    fact_content,
749                    confidence_after,
750                    days_since_reinforcement,
751                    timestamp,
752                    ..
753                } => {
754                    report.decayed_facts.push(FactChange {
755                        fact_id: fact_id.clone(),
756                        fact_content: fact_content.clone(),
757                        confidence: *confidence_after,
758                        support_count: *days_since_reinforcement as usize,
759                        fact_type: "decayed".to_string(),
760                        timestamp: *timestamp,
761                    });
762                    report.statistics.facts_decayed += 1;
763                }
764
765                ConsolidationEvent::FactDeleted {
766                    fact_id,
767                    fact_content,
768                    final_confidence,
769                    support_count,
770                    reason,
771                    timestamp,
772                } => {
773                    report.deleted_facts.push(FactChange {
774                        fact_id: fact_id.clone(),
775                        fact_content: fact_content.clone(),
776                        confidence: *final_confidence,
777                        support_count: *support_count,
778                        fact_type: reason.clone(),
779                        timestamp: *timestamp,
780                    });
781                    report.statistics.facts_deleted += 1;
782                }
783
784                ConsolidationEvent::MemoryPromoted { .. } => {
785                    // Track promotions if needed
786                }
787
788                ConsolidationEvent::MaintenanceCycleCompleted { duration_ms, .. } => {
789                    report.statistics.maintenance_cycles += 1;
790                    report.statistics.total_maintenance_duration_ms += duration_ms;
791                }
792
793                // SHO-105: Memory replay events
794                ConsolidationEvent::MemoryReplayed {
795                    memory_id,
796                    content_preview,
797                    activation_before,
798                    activation_after,
799                    replay_priority,
800                    connected_memories_replayed,
801                    timestamp,
802                } => {
803                    report.replayed_memories.push(ReplayEvent {
804                        memory_id: memory_id.clone(),
805                        content_preview: content_preview.clone(),
806                        activation_before: *activation_before,
807                        activation_after: *activation_after,
808                        replay_priority: *replay_priority,
809                        connected_memories: *connected_memories_replayed,
810                        timestamp: *timestamp,
811                    });
812                    report.statistics.memories_replayed += 1;
813                    report.statistics.total_replay_priority += replay_priority;
814                }
815
816                ConsolidationEvent::ReplayCycleCompleted {
817                    memories_replayed,
818                    total_priority_score,
819                    ..
820                } => {
821                    report.statistics.replay_cycles += 1;
822                    report.statistics.memories_replayed += memories_replayed;
823                    report.statistics.total_replay_priority += total_priority_score;
824                }
825
826                // SHO-106: Memory interference events
827                ConsolidationEvent::InterferenceDetected {
828                    new_memory_id,
829                    old_memory_id,
830                    similarity,
831                    interference_type,
832                    timestamp,
833                } => {
834                    report.interference_events.push(InterferenceEvent {
835                        new_memory_id: new_memory_id.clone(),
836                        old_memory_id: old_memory_id.clone(),
837                        similarity: *similarity,
838                        interference_type: interference_type.clone(),
839                        timestamp: *timestamp,
840                    });
841                    report.statistics.interference_events += 1;
842                }
843
844                ConsolidationEvent::MemoryWeakened {
845                    memory_id,
846                    content_preview,
847                    activation_before,
848                    activation_after,
849                    interfering_memory_id,
850                    interference_type,
851                    timestamp,
852                } => {
853                    report.weakened_memories.push(MemoryChange {
854                        memory_id: memory_id.clone(),
855                        content_preview: content_preview.clone(),
856                        activation_before: *activation_before,
857                        activation_after: *activation_after,
858                        change_reason: format!(
859                            "{:?} interference from {}",
860                            interference_type, interfering_memory_id
861                        ),
862                        at_risk: *activation_after < 0.1,
863                        timestamp: *timestamp,
864                    });
865                    report.statistics.memories_weakened += 1;
866                }
867
868                ConsolidationEvent::RetrievalCompetition { .. } => {
869                    report.statistics.retrieval_competitions += 1;
870                }
871
872                // PIPE-2: Pattern-triggered replay events
873                // These are tracked in statistics but not in detailed lists (yet)
874                ConsolidationEvent::PatternTriggeredReplay { .. } => {
875                    // Could add to a pattern_triggers list if needed
876                }
877                ConsolidationEvent::EntityPatternDetected { .. } => {
878                    // Tracked via PatternTriggeredReplay
879                }
880                ConsolidationEvent::SemanticClusterFormed { .. } => {
881                    // Tracked via PatternTriggeredReplay
882                }
883                ConsolidationEvent::TemporalClusterFormed { .. } => {
884                    // Tracked via PatternTriggeredReplay
885                }
886                ConsolidationEvent::SalienceSpikeDetected { .. } => {
887                    // Tracked via PatternTriggeredReplay
888                }
889                ConsolidationEvent::BehavioralChangeDetected { .. } => {
890                    // Tracked via PatternTriggeredReplay
891                }
892                ConsolidationEvent::PatternDetected { .. } => {
893                    // Generic pattern - logged for introspection
894                }
895
896                // Memory-Edge Tier Coupling events — logged for introspection
897                ConsolidationEvent::EdgePromotionBoostApplied { .. } => {}
898                ConsolidationEvent::GraphOrphanDetected { .. } => {}
899                ConsolidationEvent::GraphAdjustedPromotion { .. } => {}
900                ConsolidationEvent::GraphDecayConsolidated { .. } => {}
901            }
902        }
903
904        report
905    }
906
907    /// Generate a report from a slice of events (static method)
908    ///
909    /// This enables generating reports from events that come from multiple sources
910    /// (e.g., persisted learning history + ephemeral buffer).
911    pub fn generate_report_from_events(
912        events: &[ConsolidationEvent],
913        since: DateTime<Utc>,
914        until: DateTime<Utc>,
915    ) -> ConsolidationReport {
916        let mut report = ConsolidationReport {
917            period: ReportPeriod {
918                start: since,
919                end: until,
920            },
921            strengthened_memories: Vec::new(),
922            decayed_memories: Vec::new(),
923            formed_associations: Vec::new(),
924            strengthened_associations: Vec::new(),
925            potentiated_associations: Vec::new(),
926            pruned_associations: Vec::new(),
927            extracted_facts: Vec::new(),
928            reinforced_facts: Vec::new(),
929            decayed_facts: Vec::new(),
930            deleted_facts: Vec::new(),
931            replayed_memories: Vec::new(),
932            interference_events: Vec::new(),
933            weakened_memories: Vec::new(),
934            statistics: ConsolidationStats::default(),
935        };
936
937        for event in events {
938            match event {
939                ConsolidationEvent::MemoryStrengthened {
940                    memory_id,
941                    content_preview,
942                    activation_before,
943                    activation_after,
944                    reason,
945                    timestamp,
946                } => {
947                    report.strengthened_memories.push(MemoryChange {
948                        memory_id: memory_id.clone(),
949                        content_preview: content_preview.clone(),
950                        activation_before: *activation_before,
951                        activation_after: *activation_after,
952                        change_reason: format!("{:?}", reason),
953                        at_risk: false,
954                        timestamp: *timestamp,
955                    });
956                    report.statistics.memories_strengthened += 1;
957                }
958
959                ConsolidationEvent::MemoryDecayed {
960                    memory_id,
961                    content_preview,
962                    activation_before,
963                    activation_after,
964                    at_risk,
965                    timestamp,
966                } => {
967                    report.decayed_memories.push(MemoryChange {
968                        memory_id: memory_id.clone(),
969                        content_preview: content_preview.clone(),
970                        activation_before: *activation_before,
971                        activation_after: *activation_after,
972                        change_reason: "decay".to_string(),
973                        at_risk: *at_risk,
974                        timestamp: *timestamp,
975                    });
976                    report.statistics.memories_decayed += 1;
977                    if *at_risk {
978                        report.statistics.memories_at_risk += 1;
979                    }
980                }
981
982                ConsolidationEvent::EdgeFormed {
983                    from_memory_id,
984                    to_memory_id,
985                    initial_strength,
986                    reason,
987                    timestamp,
988                } => {
989                    report.formed_associations.push(AssociationChange {
990                        from_memory_id: from_memory_id.clone(),
991                        to_memory_id: to_memory_id.clone(),
992                        strength_before: None,
993                        strength_after: *initial_strength,
994                        co_activations: Some(1),
995                        reason: format!("{:?}", reason),
996                        timestamp: *timestamp,
997                    });
998                    report.statistics.edges_formed += 1;
999                }
1000
1001                ConsolidationEvent::EdgeStrengthened {
1002                    from_memory_id,
1003                    to_memory_id,
1004                    strength_before,
1005                    strength_after,
1006                    co_activations,
1007                    timestamp,
1008                } => {
1009                    report.strengthened_associations.push(AssociationChange {
1010                        from_memory_id: from_memory_id.clone(),
1011                        to_memory_id: to_memory_id.clone(),
1012                        strength_before: Some(*strength_before),
1013                        strength_after: *strength_after,
1014                        co_activations: Some(*co_activations),
1015                        reason: "co_activation".to_string(),
1016                        timestamp: *timestamp,
1017                    });
1018                    report.statistics.edges_strengthened += 1;
1019                }
1020
1021                ConsolidationEvent::EdgePotentiated {
1022                    from_memory_id,
1023                    to_memory_id,
1024                    final_strength,
1025                    total_co_activations,
1026                    timestamp,
1027                } => {
1028                    report.potentiated_associations.push(AssociationChange {
1029                        from_memory_id: from_memory_id.clone(),
1030                        to_memory_id: to_memory_id.clone(),
1031                        strength_before: None,
1032                        strength_after: *final_strength,
1033                        co_activations: Some(*total_co_activations),
1034                        reason: "long_term_potentiation".to_string(),
1035                        timestamp: *timestamp,
1036                    });
1037                    report.statistics.edges_potentiated += 1;
1038                }
1039
1040                ConsolidationEvent::EdgePruned {
1041                    from_memory_id,
1042                    to_memory_id,
1043                    final_strength,
1044                    reason,
1045                    timestamp,
1046                } => {
1047                    report.pruned_associations.push(AssociationChange {
1048                        from_memory_id: from_memory_id.clone(),
1049                        to_memory_id: to_memory_id.clone(),
1050                        strength_before: Some(*final_strength),
1051                        strength_after: 0.0,
1052                        co_activations: None,
1053                        reason: format!("{:?}", reason),
1054                        timestamp: *timestamp,
1055                    });
1056                    report.statistics.edges_pruned += 1;
1057                }
1058
1059                ConsolidationEvent::FactExtracted {
1060                    fact_id,
1061                    fact_content,
1062                    confidence,
1063                    source_memory_count,
1064                    fact_type,
1065                    timestamp,
1066                } => {
1067                    report.extracted_facts.push(FactChange {
1068                        fact_id: fact_id.clone(),
1069                        fact_content: fact_content.clone(),
1070                        confidence: *confidence,
1071                        support_count: *source_memory_count,
1072                        fact_type: fact_type.clone(),
1073                        timestamp: *timestamp,
1074                    });
1075                    report.statistics.facts_extracted += 1;
1076                }
1077
1078                ConsolidationEvent::FactReinforced {
1079                    fact_id,
1080                    fact_content,
1081                    confidence_after,
1082                    new_support_count,
1083                    timestamp,
1084                    ..
1085                } => {
1086                    report.reinforced_facts.push(FactChange {
1087                        fact_id: fact_id.clone(),
1088                        fact_content: fact_content.clone(),
1089                        confidence: *confidence_after,
1090                        support_count: *new_support_count,
1091                        fact_type: "reinforced".to_string(),
1092                        timestamp: *timestamp,
1093                    });
1094                    report.statistics.facts_reinforced += 1;
1095                }
1096
1097                ConsolidationEvent::FactDecayed {
1098                    fact_id,
1099                    fact_content,
1100                    confidence_after,
1101                    days_since_reinforcement,
1102                    timestamp,
1103                    ..
1104                } => {
1105                    report.decayed_facts.push(FactChange {
1106                        fact_id: fact_id.clone(),
1107                        fact_content: fact_content.clone(),
1108                        confidence: *confidence_after,
1109                        support_count: *days_since_reinforcement as usize,
1110                        fact_type: "decayed".to_string(),
1111                        timestamp: *timestamp,
1112                    });
1113                    report.statistics.facts_decayed += 1;
1114                }
1115
1116                ConsolidationEvent::FactDeleted {
1117                    fact_id,
1118                    fact_content,
1119                    final_confidence,
1120                    support_count,
1121                    reason,
1122                    timestamp,
1123                } => {
1124                    report.deleted_facts.push(FactChange {
1125                        fact_id: fact_id.clone(),
1126                        fact_content: fact_content.clone(),
1127                        confidence: *final_confidence,
1128                        support_count: *support_count,
1129                        fact_type: reason.clone(),
1130                        timestamp: *timestamp,
1131                    });
1132                    report.statistics.facts_deleted += 1;
1133                }
1134
1135                ConsolidationEvent::MemoryPromoted { .. } => {
1136                    // Track promotions if needed
1137                }
1138
1139                ConsolidationEvent::MaintenanceCycleCompleted { duration_ms, .. } => {
1140                    report.statistics.maintenance_cycles += 1;
1141                    report.statistics.total_maintenance_duration_ms += duration_ms;
1142                }
1143
1144                ConsolidationEvent::MemoryReplayed {
1145                    memory_id,
1146                    content_preview,
1147                    activation_before,
1148                    activation_after,
1149                    replay_priority,
1150                    connected_memories_replayed,
1151                    timestamp,
1152                } => {
1153                    report.replayed_memories.push(ReplayEvent {
1154                        memory_id: memory_id.clone(),
1155                        content_preview: content_preview.clone(),
1156                        activation_before: *activation_before,
1157                        activation_after: *activation_after,
1158                        replay_priority: *replay_priority,
1159                        connected_memories: *connected_memories_replayed,
1160                        timestamp: *timestamp,
1161                    });
1162                    report.statistics.memories_replayed += 1;
1163                    report.statistics.total_replay_priority += replay_priority;
1164                }
1165
1166                ConsolidationEvent::ReplayCycleCompleted {
1167                    memories_replayed,
1168                    total_priority_score,
1169                    ..
1170                } => {
1171                    report.statistics.replay_cycles += 1;
1172                    report.statistics.memories_replayed += memories_replayed;
1173                    report.statistics.total_replay_priority += total_priority_score;
1174                }
1175
1176                ConsolidationEvent::InterferenceDetected {
1177                    new_memory_id,
1178                    old_memory_id,
1179                    similarity,
1180                    interference_type,
1181                    timestamp,
1182                } => {
1183                    report.interference_events.push(InterferenceEvent {
1184                        new_memory_id: new_memory_id.clone(),
1185                        old_memory_id: old_memory_id.clone(),
1186                        similarity: *similarity,
1187                        interference_type: interference_type.clone(),
1188                        timestamp: *timestamp,
1189                    });
1190                    report.statistics.interference_events += 1;
1191                }
1192
1193                ConsolidationEvent::MemoryWeakened {
1194                    memory_id,
1195                    content_preview,
1196                    activation_before,
1197                    activation_after,
1198                    interfering_memory_id,
1199                    interference_type,
1200                    timestamp,
1201                } => {
1202                    report.weakened_memories.push(MemoryChange {
1203                        memory_id: memory_id.clone(),
1204                        content_preview: content_preview.clone(),
1205                        activation_before: *activation_before,
1206                        activation_after: *activation_after,
1207                        change_reason: format!(
1208                            "{:?} interference from {}",
1209                            interference_type, interfering_memory_id
1210                        ),
1211                        at_risk: *activation_after < 0.1,
1212                        timestamp: *timestamp,
1213                    });
1214                    report.statistics.memories_weakened += 1;
1215                }
1216
1217                ConsolidationEvent::RetrievalCompetition { .. } => {
1218                    report.statistics.retrieval_competitions += 1;
1219                }
1220
1221                // PIPE-2: Pattern-triggered replay events
1222                ConsolidationEvent::PatternTriggeredReplay { .. } => {}
1223                ConsolidationEvent::EntityPatternDetected { .. } => {}
1224                ConsolidationEvent::SemanticClusterFormed { .. } => {}
1225                ConsolidationEvent::TemporalClusterFormed { .. } => {}
1226                ConsolidationEvent::SalienceSpikeDetected { .. } => {}
1227                ConsolidationEvent::BehavioralChangeDetected { .. } => {}
1228                ConsolidationEvent::PatternDetected { .. } => {}
1229
1230                // Memory-Edge Tier Coupling events — logged for introspection
1231                ConsolidationEvent::EdgePromotionBoostApplied { .. } => {}
1232                ConsolidationEvent::GraphOrphanDetected { .. } => {}
1233                ConsolidationEvent::GraphAdjustedPromotion { .. } => {}
1234                ConsolidationEvent::GraphDecayConsolidated { .. } => {}
1235            }
1236        }
1237
1238        report
1239    }
1240}
1241
1242impl ConsolidationEvent {
1243    /// Get the timestamp of this event
1244    pub fn timestamp(&self) -> DateTime<Utc> {
1245        match self {
1246            ConsolidationEvent::MemoryStrengthened { timestamp, .. } => *timestamp,
1247            ConsolidationEvent::MemoryDecayed { timestamp, .. } => *timestamp,
1248            ConsolidationEvent::EdgeFormed { timestamp, .. } => *timestamp,
1249            ConsolidationEvent::EdgeStrengthened { timestamp, .. } => *timestamp,
1250            ConsolidationEvent::EdgePotentiated { timestamp, .. } => *timestamp,
1251            ConsolidationEvent::EdgePruned { timestamp, .. } => *timestamp,
1252            ConsolidationEvent::FactExtracted { timestamp, .. } => *timestamp,
1253            ConsolidationEvent::FactReinforced { timestamp, .. } => *timestamp,
1254            ConsolidationEvent::FactDecayed { timestamp, .. } => *timestamp,
1255            ConsolidationEvent::FactDeleted { timestamp, .. } => *timestamp,
1256            ConsolidationEvent::MemoryPromoted { timestamp, .. } => *timestamp,
1257            ConsolidationEvent::MaintenanceCycleCompleted { timestamp, .. } => *timestamp,
1258            // SHO-105: Replay events
1259            ConsolidationEvent::MemoryReplayed { timestamp, .. } => *timestamp,
1260            ConsolidationEvent::ReplayCycleCompleted { timestamp, .. } => *timestamp,
1261            // SHO-106: Interference events
1262            ConsolidationEvent::InterferenceDetected { timestamp, .. } => *timestamp,
1263            ConsolidationEvent::MemoryWeakened { timestamp, .. } => *timestamp,
1264            ConsolidationEvent::RetrievalCompetition { timestamp, .. } => *timestamp,
1265            // PIPE-2: Pattern-triggered replay events
1266            ConsolidationEvent::PatternTriggeredReplay { timestamp, .. } => *timestamp,
1267            ConsolidationEvent::EntityPatternDetected { timestamp, .. } => *timestamp,
1268            ConsolidationEvent::SemanticClusterFormed { timestamp, .. } => *timestamp,
1269            ConsolidationEvent::TemporalClusterFormed { timestamp, .. } => *timestamp,
1270            ConsolidationEvent::SalienceSpikeDetected { timestamp, .. } => *timestamp,
1271            ConsolidationEvent::BehavioralChangeDetected { timestamp, .. } => *timestamp,
1272            ConsolidationEvent::PatternDetected { timestamp, .. } => *timestamp,
1273            // Memory-Edge Tier Coupling events
1274            ConsolidationEvent::EdgePromotionBoostApplied { timestamp, .. } => *timestamp,
1275            ConsolidationEvent::GraphOrphanDetected { timestamp, .. } => *timestamp,
1276            ConsolidationEvent::GraphAdjustedPromotion { timestamp, .. } => *timestamp,
1277            ConsolidationEvent::GraphDecayConsolidated { timestamp, .. } => *timestamp,
1278        }
1279    }
1280
1281    /// Check if this event is significant enough to persist to learning history
1282    ///
1283    /// Significant events represent actual learning/state changes:
1284    /// - EdgePotentiated: Permanent association formed (LTP)
1285    /// - FactExtracted: New semantic knowledge created
1286    /// - FactDeleted: Knowledge was lost
1287    /// - FactReinforced: Fact got stronger evidence
1288    /// - InterferenceDetected: Memory conflict occurred
1289    /// - MemoryReplayed: Consolidation strengthened this memory
1290    /// - MemoryPromoted: Memory moved to higher tier
1291    /// - ReplayCycleCompleted: Batch consolidation summary
1292    /// - MaintenanceCycleCompleted: Maintenance summary
1293    ///
1294    /// Non-significant (routine housekeeping):
1295    /// - MemoryDecayed: Happens every cycle to every memory
1296    /// - MemoryStrengthened: High frequency access events
1297    /// - EdgeStrengthened: Incremental co-activation
1298    /// - EdgeFormed: Initial edge creation (may not survive)
1299    /// - EdgePruned: Cleanup of weak edges
1300    /// - FactDecayed: Minor confidence changes
1301    /// - MemoryWeakened: Consequence of interference (tracked via InterferenceDetected)
1302    /// - RetrievalCompetition: Transient retrieval-time event
1303    pub fn is_significant(&self) -> bool {
1304        matches!(
1305            self,
1306            ConsolidationEvent::EdgePotentiated { .. }
1307                | ConsolidationEvent::FactExtracted { .. }
1308                | ConsolidationEvent::FactDeleted { .. }
1309                | ConsolidationEvent::FactReinforced { .. }
1310                | ConsolidationEvent::InterferenceDetected { .. }
1311                | ConsolidationEvent::MemoryReplayed { .. }
1312                | ConsolidationEvent::MemoryPromoted { .. }
1313                | ConsolidationEvent::ReplayCycleCompleted { .. }
1314                | ConsolidationEvent::MaintenanceCycleCompleted { .. }
1315                // PIPE-2: Pattern-triggered events are significant
1316                | ConsolidationEvent::PatternTriggeredReplay { .. }
1317                | ConsolidationEvent::EntityPatternDetected { .. }
1318                | ConsolidationEvent::SemanticClusterFormed { .. }
1319                | ConsolidationEvent::SalienceSpikeDetected { .. }
1320                | ConsolidationEvent::PatternDetected { .. }
1321                // Memory-Edge Tier Coupling events are significant
1322                | ConsolidationEvent::EdgePromotionBoostApplied { .. }
1323                | ConsolidationEvent::GraphOrphanDetected { .. }
1324                | ConsolidationEvent::GraphAdjustedPromotion { .. }
1325                | ConsolidationEvent::GraphDecayConsolidated { .. }
1326        )
1327    }
1328}
1329
1330impl Default for ConsolidationReport {
1331    fn default() -> Self {
1332        Self {
1333            period: ReportPeriod {
1334                start: Utc::now(),
1335                end: Utc::now(),
1336            },
1337            strengthened_memories: Vec::new(),
1338            decayed_memories: Vec::new(),
1339            formed_associations: Vec::new(),
1340            strengthened_associations: Vec::new(),
1341            potentiated_associations: Vec::new(),
1342            pruned_associations: Vec::new(),
1343            extracted_facts: Vec::new(),
1344            reinforced_facts: Vec::new(),
1345            decayed_facts: Vec::new(),
1346            deleted_facts: Vec::new(),
1347            // SHO-105: Replay events
1348            replayed_memories: Vec::new(),
1349            // SHO-106: Interference events
1350            interference_events: Vec::new(),
1351            weakened_memories: Vec::new(),
1352            statistics: ConsolidationStats::default(),
1353        }
1354    }
1355}
1356
1357#[cfg(test)]
1358mod tests {
1359    use super::*;
1360
1361    #[test]
1362    fn test_event_buffer_push() {
1363        let mut buffer = ConsolidationEventBuffer::with_capacity(3);
1364
1365        for i in 0..5 {
1366            buffer.push(ConsolidationEvent::MemoryDecayed {
1367                memory_id: format!("mem-{}", i),
1368                content_preview: format!("Memory {}", i),
1369                activation_before: 0.5,
1370                activation_after: 0.4,
1371                at_risk: false,
1372                timestamp: Utc::now(),
1373            });
1374        }
1375
1376        // Should only keep last 3
1377        assert_eq!(buffer.len(), 3);
1378    }
1379
1380    #[test]
1381    fn test_generate_report() {
1382        let mut buffer = ConsolidationEventBuffer::new();
1383        let now = Utc::now();
1384
1385        buffer.push(ConsolidationEvent::MemoryStrengthened {
1386            memory_id: "mem-1".to_string(),
1387            content_preview: "Test memory".to_string(),
1388            activation_before: 0.5,
1389            activation_after: 0.7,
1390            reason: StrengtheningReason::Recalled,
1391            timestamp: now,
1392        });
1393
1394        buffer.push(ConsolidationEvent::EdgeFormed {
1395            from_memory_id: "mem-1".to_string(),
1396            to_memory_id: "mem-2".to_string(),
1397            initial_strength: 0.5,
1398            reason: EdgeFormationReason::CoRetrieval,
1399            timestamp: now,
1400        });
1401
1402        let report = buffer.generate_report(
1403            now - chrono::Duration::hours(1),
1404            now + chrono::Duration::hours(1),
1405        );
1406
1407        assert_eq!(report.statistics.memories_strengthened, 1);
1408        assert_eq!(report.statistics.edges_formed, 1);
1409    }
1410}