Skip to main content

heartbit_core/memory/
reflection.rs

1//! Reflection tracker — triggers agent self-reflection when cumulative memory importance exceeds a threshold.
2
3use parking_lot::Mutex;
4
5/// Tracks cumulative importance of stored memories to trigger reflection.
6///
7/// When the sum of importance values since the last trigger exceeds `threshold`,
8/// `record()` returns `true` and resets the accumulator. This follows the
9/// Generative Agents (Park et al., 2023) reflection pattern.
10pub struct ReflectionTracker {
11    threshold: u32,
12    accumulated: Mutex<u32>,
13}
14
15impl ReflectionTracker {
16    pub fn new(threshold: u32) -> Self {
17        Self {
18            threshold,
19            accumulated: Mutex::new(0),
20        }
21    }
22
23    /// Record an importance value. Returns `true` if the threshold was exceeded,
24    /// triggering a reflection. The accumulator is reset after triggering.
25    pub fn record(&self, importance: u8) -> bool {
26        let mut acc = self.accumulated.lock();
27        *acc += importance as u32;
28        if *acc >= self.threshold {
29            *acc = 0;
30            true
31        } else {
32            false
33        }
34    }
35
36    /// Current accumulated importance (for testing/debugging).
37    pub fn accumulated(&self) -> u32 {
38        *self.accumulated.lock()
39    }
40}
41
42/// Hint text appended to store tool output when a reflection is triggered.
43pub const REFLECTION_HINT: &str = "\n\n[Reflection suggested] You have stored several important memories recently. \
44     Take a moment to reflect on what you've learned. Use memory_store with high importance \
45     to record any insights, patterns, or connections you notice across your recent memories.";
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    #[test]
52    fn tracker_triggers_at_threshold() {
53        let tracker = ReflectionTracker::new(10);
54
55        // Accumulate to just below threshold
56        assert!(!tracker.record(5)); // 5
57        assert!(!tracker.record(4)); // 9
58        // This should trigger
59        assert!(tracker.record(1)); // 10 >= 10
60    }
61
62    #[test]
63    fn tracker_resets_after_trigger() {
64        let tracker = ReflectionTracker::new(10);
65
66        // Trigger
67        assert!(tracker.record(10));
68        assert_eq!(tracker.accumulated(), 0);
69
70        // Should need full threshold again
71        assert!(!tracker.record(5));
72        assert_eq!(tracker.accumulated(), 5);
73    }
74
75    #[test]
76    fn tracker_exceeds_threshold() {
77        let tracker = ReflectionTracker::new(10);
78
79        // Single large value exceeding threshold
80        assert!(tracker.record(15));
81        assert_eq!(tracker.accumulated(), 0);
82    }
83
84    #[test]
85    fn tracker_many_small_values() {
86        let tracker = ReflectionTracker::new(10);
87
88        for _ in 0..9 {
89            assert!(!tracker.record(1));
90        }
91        // 9 accumulated, one more should trigger
92        assert!(tracker.record(1));
93    }
94
95    #[test]
96    fn tracker_triggers_multiple_times() {
97        let tracker = ReflectionTracker::new(5);
98
99        assert!(tracker.record(5)); // first trigger
100        assert!(tracker.record(5)); // second trigger
101        assert!(!tracker.record(3)); // not yet
102        assert!(tracker.record(2)); // third trigger
103    }
104}