1use crate::error::AgentRuntimeError;
20use crate::util::recover_lock;
21use chrono::{DateTime, Utc};
22use serde::{Deserialize, Serialize};
23use std::collections::{HashMap, VecDeque};
24use std::sync::{Arc, Mutex};
25
26pub use crate::types::{AgentId, MemoryId};
28
29fn normalize_in_place(v: &mut Vec<f32>) {
34 let norm: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
35 if norm > f32::EPSILON {
36 for x in v.iter_mut() {
37 *x /= norm;
38 }
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct MemoryItem {
47 pub id: MemoryId,
49 pub agent_id: AgentId,
51 pub content: String,
53 pub importance: f32,
55 pub timestamp: DateTime<Utc>,
57 pub tags: Vec<String>,
59 #[serde(default)]
61 pub recall_count: u64,
62}
63
64impl MemoryItem {
65 pub fn new(
67 agent_id: AgentId,
68 content: impl Into<String>,
69 importance: f32,
70 tags: Vec<String>,
71 ) -> Self {
72 Self {
73 id: MemoryId::random(),
74 agent_id,
75 content: content.into(),
76 importance: importance.clamp(0.0, 1.0),
77 timestamp: Utc::now(),
78 tags,
79 recall_count: 0,
80 }
81 }
82
83 pub fn age_hours(&self) -> f64 {
87 let now = Utc::now();
88 let elapsed = now.signed_duration_since(self.timestamp);
89 elapsed.num_milliseconds().max(0) as f64 / 3_600_000.0
90 }
91
92 pub fn has_tag(&self, tag: &str) -> bool {
94 self.tags.iter().any(|t| t == tag)
95 }
96
97 pub fn word_count(&self) -> usize {
99 self.content.split_whitespace().count()
100 }
101}
102
103impl std::fmt::Display for MemoryItem {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 write!(
106 f,
107 "[{}] importance={:.2} recalls={} content=\"{}\"",
108 self.id,
109 self.importance,
110 self.recall_count,
111 self.content
112 )
113 }
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct DecayPolicy {
121 half_life_hours: f64,
123}
124
125impl DecayPolicy {
126 pub fn exponential(half_life_hours: f64) -> Result<Self, AgentRuntimeError> {
135 if half_life_hours <= 0.0 {
136 return Err(AgentRuntimeError::Memory(
137 "half_life_hours must be positive".into(),
138 ));
139 }
140 Ok(Self { half_life_hours })
141 }
142
143 pub fn apply(&self, importance: f32, age_hours: f64) -> f32 {
152 let decay = (-age_hours * std::f64::consts::LN_2 / self.half_life_hours).exp();
153 (importance as f64 * decay).clamp(0.0, 1.0) as f32
154 }
155
156 pub fn half_life_hours(&self) -> f64 {
158 self.half_life_hours
159 }
160
161 pub fn decay_item(&self, item: &mut MemoryItem) {
163 let age_hours = (Utc::now() - item.timestamp).num_seconds().max(0) as f64 / 3600.0;
164 item.importance = self.apply(item.importance, age_hours);
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
196pub enum RecallPolicy {
197 Importance,
199 Hybrid {
205 recency_weight: f32,
207 frequency_weight: f32,
209 },
210}
211
212impl Default for RecallPolicy {
213 fn default() -> Self {
214 RecallPolicy::Importance
215 }
216}
217
218fn compute_hybrid_score(
221 item: &MemoryItem,
222 recency_weight: f32,
223 frequency_weight: f32,
224 max_recall: u64,
225 now: chrono::DateTime<Utc>,
226) -> f32 {
227 let age_hours = (now - item.timestamp).num_seconds().max(0) as f64 / 3600.0;
228 let recency_score = (-age_hours / 24.0).exp() as f32;
229 let frequency_score = item.recall_count as f32 / (max_recall as f32 + 1.0);
230 item.importance + recency_score * recency_weight + frequency_score * frequency_weight
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Default)]
237pub enum EvictionPolicy {
238 #[default]
240 LowestImportance,
241 Oldest,
243}
244
245#[derive(Default)]
262pub struct EpisodicStoreBuilder {
263 decay: Option<DecayPolicy>,
264 recall_policy: Option<RecallPolicy>,
265 per_agent_capacity: Option<usize>,
266 max_age_hours: Option<f64>,
267 eviction_policy: Option<EvictionPolicy>,
268}
269
270impl EpisodicStoreBuilder {
271 pub fn decay(mut self, policy: DecayPolicy) -> Self {
273 self.decay = Some(policy);
274 self
275 }
276
277 pub fn recall_policy(mut self, policy: RecallPolicy) -> Self {
279 self.recall_policy = Some(policy);
280 self
281 }
282
283 pub fn per_agent_capacity(mut self, capacity: usize) -> Self {
285 assert!(capacity > 0, "per_agent_capacity must be > 0");
286 self.per_agent_capacity = Some(capacity);
287 self
288 }
289
290 pub fn try_per_agent_capacity(
298 mut self,
299 capacity: usize,
300 ) -> Result<Self, crate::error::AgentRuntimeError> {
301 if capacity == 0 {
302 return Err(crate::error::AgentRuntimeError::Memory(
303 "per_agent_capacity must be > 0".into(),
304 ));
305 }
306 self.per_agent_capacity = Some(capacity);
307 Ok(self)
308 }
309
310 pub fn max_age_hours(mut self, hours: f64) -> Result<Self, crate::error::AgentRuntimeError> {
312 if hours <= 0.0 {
313 return Err(crate::error::AgentRuntimeError::Memory(
314 "max_age_hours must be positive".into(),
315 ));
316 }
317 self.max_age_hours = Some(hours);
318 Ok(self)
319 }
320
321 pub fn eviction_policy(mut self, policy: EvictionPolicy) -> Self {
323 self.eviction_policy = Some(policy);
324 self
325 }
326
327 pub fn build(self) -> EpisodicStore {
329 if self.decay.is_some() {
334 if let Some(RecallPolicy::Hybrid { .. }) = &self.recall_policy {
335 tracing::warn!(
336 "EpisodicStore configured with both DecayPolicy and RecallPolicy::Hybrid \
337 — time-based decay is applied before hybrid scoring, resulting in a \
338 double time penalty. Set one or the other unless this is intentional."
339 );
340 }
341 }
342 EpisodicStore {
343 inner: Arc::new(Mutex::new(EpisodicInner {
344 items: HashMap::new(),
345 decay: self.decay,
346 recall_policy: self.recall_policy.unwrap_or(RecallPolicy::Importance),
347 per_agent_capacity: self.per_agent_capacity,
348 max_age_hours: self.max_age_hours,
349 eviction_policy: self.eviction_policy.unwrap_or_default(),
350 })),
351 }
352 }
353}
354
355#[derive(Debug, Clone)]
366pub struct EpisodicStore {
367 inner: Arc<Mutex<EpisodicInner>>,
368}
369
370#[derive(Debug)]
371struct EpisodicInner {
372 items: HashMap<AgentId, Vec<MemoryItem>>,
374 decay: Option<DecayPolicy>,
375 recall_policy: RecallPolicy,
376 per_agent_capacity: Option<usize>,
378 max_age_hours: Option<f64>,
380 eviction_policy: EvictionPolicy,
382}
383
384impl EpisodicInner {
385 fn purge_stale(&mut self, agent_id: &AgentId) {
387 if let Some(max_age_h) = self.max_age_hours {
388 let cutoff = Utc::now()
389 - chrono::Duration::seconds((max_age_h * 3600.0) as i64);
390 if let Some(agent_items) = self.items.get_mut(agent_id) {
391 agent_items.retain(|i| i.timestamp >= cutoff);
392 }
393 }
394 }
395}
396
397fn evict_if_over_capacity(
402 agent_items: &mut Vec<MemoryItem>,
403 cap: usize,
404 policy: &EvictionPolicy,
405) {
406 if agent_items.len() <= cap {
407 return;
408 }
409 let pos = match policy {
410 EvictionPolicy::LowestImportance => {
411 let len = agent_items.len();
412 agent_items[..len - 1]
413 .iter()
414 .enumerate()
415 .min_by(|(_, a), (_, b)| {
416 a.importance
417 .partial_cmp(&b.importance)
418 .unwrap_or(std::cmp::Ordering::Equal)
419 })
420 .map(|(pos, _)| pos)
421 }
422 EvictionPolicy::Oldest => {
423 let len = agent_items.len();
424 agent_items[..len - 1]
425 .iter()
426 .enumerate()
427 .min_by_key(|(_, item)| item.timestamp)
428 .map(|(pos, _)| pos)
429 }
430 };
431 if let Some(pos) = pos {
432 agent_items.remove(pos);
433 }
434}
435
436impl EpisodicStore {
437 pub fn new() -> Self {
439 Self {
440 inner: Arc::new(Mutex::new(EpisodicInner {
441 items: HashMap::new(),
442 decay: None,
443 recall_policy: RecallPolicy::Importance,
444 per_agent_capacity: None,
445 max_age_hours: None,
446 eviction_policy: EvictionPolicy::LowestImportance,
447 })),
448 }
449 }
450
451 pub fn builder() -> EpisodicStoreBuilder {
453 EpisodicStoreBuilder::default()
454 }
455
456 pub fn with_decay(policy: DecayPolicy) -> Self {
458 Self {
459 inner: Arc::new(Mutex::new(EpisodicInner {
460 items: HashMap::new(),
461 decay: Some(policy),
462 recall_policy: RecallPolicy::Importance,
463 per_agent_capacity: None,
464 max_age_hours: None,
465 eviction_policy: EvictionPolicy::LowestImportance,
466 })),
467 }
468 }
469
470 pub fn with_decay_and_recall_policy(decay: DecayPolicy, recall: RecallPolicy) -> Self {
479 if let RecallPolicy::Hybrid { .. } = &recall {
480 tracing::warn!(
481 "EpisodicStore::with_decay_and_recall_policy called with RecallPolicy::Hybrid \
482 — this applies a double time penalty. Set DecayPolicy OR Hybrid recency \
483 weighting, not both, unless the double penalty is intentional."
484 );
485 }
486 Self {
487 inner: Arc::new(Mutex::new(EpisodicInner {
488 items: HashMap::new(),
489 decay: Some(decay),
490 recall_policy: recall,
491 per_agent_capacity: None,
492 max_age_hours: None,
493 eviction_policy: EvictionPolicy::LowestImportance,
494 })),
495 }
496 }
497
498 pub fn with_recall_policy(policy: RecallPolicy) -> Self {
500 Self {
501 inner: Arc::new(Mutex::new(EpisodicInner {
502 items: HashMap::new(),
503 decay: None,
504 recall_policy: policy,
505 per_agent_capacity: None,
506 max_age_hours: None,
507 eviction_policy: EvictionPolicy::LowestImportance,
508 })),
509 }
510 }
511
512 pub fn with_per_agent_capacity(capacity: usize) -> Self {
531 assert!(capacity > 0, "per_agent_capacity must be > 0");
532 Self {
533 inner: Arc::new(Mutex::new(EpisodicInner {
534 items: HashMap::new(),
535 decay: None,
536 recall_policy: RecallPolicy::Importance,
537 per_agent_capacity: Some(capacity),
538 max_age_hours: None,
539 eviction_policy: EvictionPolicy::LowestImportance,
540 })),
541 }
542 }
543
544 pub fn try_with_per_agent_capacity(
552 capacity: usize,
553 ) -> Result<Self, AgentRuntimeError> {
554 if capacity == 0 {
555 return Err(AgentRuntimeError::Memory(
556 "per_agent_capacity must be > 0".into(),
557 ));
558 }
559 Ok(Self {
560 inner: Arc::new(Mutex::new(EpisodicInner {
561 items: HashMap::new(),
562 decay: None,
563 recall_policy: RecallPolicy::Importance,
564 per_agent_capacity: Some(capacity),
565 max_age_hours: None,
566 eviction_policy: EvictionPolicy::LowestImportance,
567 })),
568 })
569 }
570
571 pub fn with_max_age(max_age_hours: f64) -> Result<Self, AgentRuntimeError> {
579 if max_age_hours <= 0.0 {
580 return Err(AgentRuntimeError::Memory(
581 "max_age_hours must be positive".into(),
582 ));
583 }
584 Ok(Self {
585 inner: Arc::new(Mutex::new(EpisodicInner {
586 items: HashMap::new(),
587 decay: None,
588 recall_policy: RecallPolicy::Importance,
589 per_agent_capacity: None,
590 max_age_hours: Some(max_age_hours),
591 eviction_policy: EvictionPolicy::LowestImportance,
592 })),
593 })
594 }
595
596 pub fn with_eviction_policy(policy: EvictionPolicy) -> Self {
598 Self {
599 inner: Arc::new(Mutex::new(EpisodicInner {
600 items: HashMap::new(),
601 decay: None,
602 recall_policy: RecallPolicy::Importance,
603 per_agent_capacity: None,
604 max_age_hours: None,
605 eviction_policy: policy,
606 })),
607 }
608 }
609
610 #[tracing::instrument(skip(self))]
628 pub fn add_episode(
629 &self,
630 agent_id: AgentId,
631 content: impl Into<String> + std::fmt::Debug,
632 importance: f32,
633 ) -> Result<MemoryId, AgentRuntimeError> {
634 let item = MemoryItem::new(agent_id.clone(), content, importance, Vec::new());
635 let id = item.id.clone();
636 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::add_episode");
637
638 inner.purge_stale(&agent_id);
639 let cap = inner.per_agent_capacity; let eviction_policy = inner.eviction_policy.clone();
641 let agent_items = inner.items.entry(agent_id).or_default();
642 agent_items.push(item);
643
644 if let Some(cap) = cap {
645 evict_if_over_capacity(agent_items, cap, &eviction_policy);
646 }
647 Ok(id)
648 }
649
650 #[tracing::instrument(skip(self))]
657 pub fn add_episode_with_tags(
658 &self,
659 agent_id: AgentId,
660 content: impl Into<String> + std::fmt::Debug,
661 importance: f32,
662 tags: Vec<String>,
663 ) -> Result<MemoryId, AgentRuntimeError> {
664 let item = MemoryItem::new(agent_id.clone(), content, importance, tags);
665 let id = item.id.clone();
666 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::add_episode_with_tags");
667 inner.purge_stale(&agent_id);
668 let cap = inner.per_agent_capacity;
669 let eviction_policy = inner.eviction_policy.clone();
670 let agent_items = inner.items.entry(agent_id).or_default();
671 agent_items.push(item);
672 if let Some(cap) = cap {
673 evict_if_over_capacity(agent_items, cap, &eviction_policy);
674 }
675 Ok(id)
676 }
677
678 pub fn remove_by_id(
683 &self,
684 agent_id: &AgentId,
685 id: &MemoryId,
686 ) -> Result<bool, AgentRuntimeError> {
687 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::remove_by_id");
688 if let Some(items) = inner.items.get_mut(agent_id) {
689 if let Some(pos) = items.iter().position(|i| &i.id == id) {
690 items.remove(pos);
691 return Ok(true);
692 }
693 }
694 Ok(false)
695 }
696
697 pub fn update_tags_by_id(
701 &self,
702 agent_id: &AgentId,
703 id: &MemoryId,
704 new_tags: Vec<String>,
705 ) -> Result<bool, AgentRuntimeError> {
706 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::update_tags_by_id");
707 if let Some(items) = inner.items.get_mut(agent_id) {
708 if let Some(item) = items.iter_mut().find(|i| &i.id == id) {
709 item.tags = new_tags;
710 return Ok(true);
711 }
712 }
713 Ok(false)
714 }
715
716 pub fn max_importance_for(
720 &self,
721 agent_id: &AgentId,
722 ) -> Result<Option<f32>, AgentRuntimeError> {
723 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_importance_for");
724 let max = inner
725 .items
726 .get(agent_id)
727 .and_then(|items| {
728 items
729 .iter()
730 .map(|i| i.importance)
731 .reduce(f32::max)
732 });
733 Ok(max)
734 }
735
736 pub fn count_for(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
741 let inner = recover_lock(self.inner.lock(), "EpisodicStore::count_for");
742 Ok(inner.items.get(agent_id).map_or(0, |v| v.len()))
743 }
744
745 pub fn has_agent(&self, agent_id: &AgentId) -> Result<bool, AgentRuntimeError> {
749 let inner = recover_lock(self.inner.lock(), "EpisodicStore::has_agent");
750 Ok(inner.items.get(agent_id).map_or(false, |v| !v.is_empty()))
751 }
752
753 pub fn agents_with_min_episodes(&self, min: usize) -> Result<Vec<AgentId>, AgentRuntimeError> {
755 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agents_with_min_episodes");
756 let mut ids: Vec<AgentId> = inner
757 .items
758 .iter()
759 .filter(|(_, v)| v.len() >= min)
760 .map(|(id, _)| id.clone())
761 .collect();
762 ids.sort_by(|a, b| a.0.cmp(&b.0));
763 Ok(ids)
764 }
765
766 pub fn total_episode_count(&self) -> Result<usize, AgentRuntimeError> {
768 let inner = recover_lock(self.inner.lock(), "EpisodicStore::total_episode_count");
769 Ok(inner.items.values().map(|v| v.len()).sum())
770 }
771
772 pub fn episode_count_for(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
776 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_count_for");
777 Ok(inner.items.get(agent_id).map_or(0, |v| v.len()))
778 }
779
780 pub fn agent_with_most_episodes(&self) -> Result<Option<AgentId>, AgentRuntimeError> {
783 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_with_most_episodes");
784 Ok(inner
785 .items
786 .iter()
787 .max_by_key(|(_, v)| v.len())
788 .map(|(id, _)| id.clone()))
789 }
790
791 pub fn agents(&self) -> Result<Vec<AgentId>, AgentRuntimeError> {
793 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agents");
794 let mut ids: Vec<AgentId> = inner
795 .items
796 .keys()
797 .filter(|id| !inner.items[id].is_empty())
798 .cloned()
799 .collect();
800 ids.sort_unstable_by(|a, b| a.0.cmp(&b.0));
801 Ok(ids)
802 }
803
804 pub fn max_importance_overall(&self) -> Result<Option<f32>, AgentRuntimeError> {
807 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_importance_overall");
808 let max = inner
809 .items
810 .values()
811 .flat_map(|v| v.iter())
812 .map(|e| e.importance)
813 .reduce(f32::max);
814 Ok(max)
815 }
816
817 pub fn importance_variance_for(&self, agent_id: &AgentId) -> Result<f64, AgentRuntimeError> {
821 let inner = recover_lock(self.inner.lock(), "EpisodicStore::importance_variance_for");
822 let vals: Vec<f64> = inner
823 .items
824 .get(agent_id)
825 .map(|v| v.iter().map(|e| e.importance as f64).collect())
826 .unwrap_or_default();
827 if vals.len() < 2 {
828 return Ok(0.0);
829 }
830 let mean = vals.iter().sum::<f64>() / vals.len() as f64;
831 let variance = vals.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / vals.len() as f64;
832 Ok(variance)
833 }
834
835 pub fn recall_top_n(
840 &self,
841 agent_id: &AgentId,
842 n: usize,
843 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
844 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_top_n");
845 let mut items: Vec<MemoryItem> = inner
846 .items
847 .get(agent_id)
848 .cloned()
849 .unwrap_or_default();
850 items.sort_unstable_by(|a, b| {
851 b.importance
852 .partial_cmp(&a.importance)
853 .unwrap_or(std::cmp::Ordering::Equal)
854 });
855 items.truncate(n);
856 Ok(items)
857 }
858
859 pub fn filter_by_importance(
862 &self,
863 agent_id: &AgentId,
864 min: f32,
865 max: f32,
866 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
867 let inner = recover_lock(self.inner.lock(), "EpisodicStore::filter_by_importance");
868 let mut items: Vec<MemoryItem> = inner
869 .items
870 .get(agent_id)
871 .map(|v| {
872 v.iter()
873 .filter(|i| i.importance >= min && i.importance <= max)
874 .cloned()
875 .collect()
876 })
877 .unwrap_or_default();
878 items.sort_unstable_by(|a, b| {
879 b.importance
880 .partial_cmp(&a.importance)
881 .unwrap_or(std::cmp::Ordering::Equal)
882 });
883 Ok(items)
884 }
885
886 pub fn retain_top_n(&self, agent_id: &AgentId, n: usize) -> Result<usize, AgentRuntimeError> {
891 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::retain_top_n");
892 let items = inner.items.entry(agent_id.clone()).or_default();
893 if items.len() <= n {
894 return Ok(0);
895 }
896 items.sort_unstable_by(|a, b| {
897 b.importance
898 .partial_cmp(&a.importance)
899 .unwrap_or(std::cmp::Ordering::Equal)
900 });
901 let removed = items.len() - n;
902 items.truncate(n);
903 Ok(removed)
904 }
905
906 pub fn most_recent(&self, agent_id: &AgentId) -> Result<Option<MemoryItem>, AgentRuntimeError> {
912 let inner = recover_lock(self.inner.lock(), "EpisodicStore::most_recent");
913 Ok(inner
914 .items
915 .get(agent_id)
916 .and_then(|v| v.last().cloned()))
917 }
918
919 pub fn max_importance(&self, agent_id: &AgentId) -> Result<Option<f32>, AgentRuntimeError> {
922 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_importance");
923 Ok(inner
924 .items
925 .get(agent_id)
926 .and_then(|v| {
927 v.iter()
928 .map(|i| i.importance)
929 .reduce(f32::max)
930 }))
931 }
932
933 pub fn min_importance(&self, agent_id: &AgentId) -> Result<Option<f32>, AgentRuntimeError> {
936 let inner = recover_lock(self.inner.lock(), "EpisodicStore::min_importance");
937 Ok(inner
938 .items
939 .get(agent_id)
940 .and_then(|v| {
941 v.iter()
942 .map(|i| i.importance)
943 .reduce(f32::min)
944 }))
945 }
946
947 pub fn count_above_importance(
952 &self,
953 agent_id: &AgentId,
954 threshold: f32,
955 ) -> Result<usize, AgentRuntimeError> {
956 let inner = recover_lock(self.inner.lock(), "EpisodicStore::count_above_importance");
957 Ok(inner
958 .items
959 .get(agent_id)
960 .map(|v| v.iter().filter(|i| i.importance > threshold).count())
961 .unwrap_or(0))
962 }
963
964 pub fn most_recalled(&self, agent_id: &AgentId) -> Result<Option<MemoryItem>, AgentRuntimeError> {
969 let inner = recover_lock(self.inner.lock(), "EpisodicStore::most_recalled");
970 Ok(inner
971 .items
972 .get(agent_id)
973 .and_then(|v| v.iter().max_by_key(|i| i.recall_count))
974 .cloned())
975 }
976
977 pub fn importance_avg(&self, agent_id: &AgentId) -> Result<f32, AgentRuntimeError> {
980 let inner = recover_lock(self.inner.lock(), "EpisodicStore::importance_avg");
981 match inner.items.get(agent_id) {
982 None => Ok(0.0),
983 Some(items) if items.is_empty() => Ok(0.0),
984 Some(items) => {
985 let sum: f32 = items.iter().map(|i| i.importance).sum();
986 Ok(sum / items.len() as f32)
987 }
988 }
989 }
990
991 pub fn deduplicate_content(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
996 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::deduplicate_content");
997 let Some(items) = inner.items.get_mut(agent_id) else {
998 return Ok(0);
999 };
1000 let before = items.len();
1001 let mut seen: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
1003 let mut keepers: Vec<usize> = Vec::new();
1004 for (idx, item) in items.iter().enumerate() {
1005 match seen.get(&item.content) {
1006 None => {
1007 seen.insert(item.content.clone(), keepers.len());
1008 keepers.push(idx);
1009 }
1010 Some(&pos) => {
1011 let kept_idx = keepers[pos];
1012 if item.importance > items[kept_idx].importance {
1013 keepers[pos] = idx;
1014 }
1015 }
1016 }
1017 }
1018 let kept: std::collections::HashSet<usize> = keepers.into_iter().collect();
1019 let mut i = 0;
1020 items.retain(|_| {
1021 let keep = kept.contains(&i);
1022 i += 1;
1023 keep
1024 });
1025 Ok(before - items.len())
1026 }
1027
1028 pub fn agent_ids(&self) -> Result<Vec<AgentId>, AgentRuntimeError> {
1030 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_ids");
1031 Ok(inner.items.keys().cloned().collect())
1032 }
1033
1034 pub fn find_by_content(
1037 &self,
1038 agent_id: &AgentId,
1039 pattern: &str,
1040 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1041 let inner = recover_lock(self.inner.lock(), "EpisodicStore::find_by_content");
1042 let mut matches: Vec<MemoryItem> = inner
1043 .items
1044 .get(agent_id)
1045 .map(|items| {
1046 items
1047 .iter()
1048 .filter(|i| i.content.contains(pattern))
1049 .cloned()
1050 .collect()
1051 })
1052 .unwrap_or_default();
1053 matches.sort_unstable_by(|a, b| {
1054 b.importance
1055 .partial_cmp(&a.importance)
1056 .unwrap_or(std::cmp::Ordering::Equal)
1057 });
1058 Ok(matches)
1059 }
1060
1061 pub fn clear_for(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1065 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::clear_for");
1066 let count = inner.items.remove(agent_id).map_or(0, |v| v.len());
1067 Ok(count)
1068 }
1069
1070 pub fn count_episodes_with_tag(
1072 &self,
1073 agent_id: &AgentId,
1074 tag: &str,
1075 ) -> Result<usize, AgentRuntimeError> {
1076 let inner = recover_lock(self.inner.lock(), "EpisodicStore::count_episodes_with_tag");
1077 let count = inner
1078 .items
1079 .get(agent_id)
1080 .map_or(0, |items| items.iter().filter(|i| i.has_tag(tag)).count());
1081 Ok(count)
1082 }
1083
1084 pub fn episodes_with_content(
1086 &self,
1087 agent_id: &AgentId,
1088 substring: &str,
1089 ) -> Result<Vec<String>, AgentRuntimeError> {
1090 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_with_content");
1091 let items = inner
1092 .items
1093 .get(agent_id)
1094 .map_or_else(Vec::new, |v| {
1095 v.iter()
1096 .filter(|i| i.content.contains(substring))
1097 .map(|i| i.content.clone())
1098 .collect()
1099 });
1100 Ok(items)
1101 }
1102
1103 pub fn max_content_length(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1107 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_content_length");
1108 Ok(inner
1109 .items
1110 .get(agent_id)
1111 .and_then(|v| v.iter().map(|i| i.content.len()).max())
1112 .unwrap_or(0))
1113 }
1114
1115 pub fn min_content_length(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1119 let inner = recover_lock(self.inner.lock(), "EpisodicStore::min_content_length");
1120 Ok(inner
1121 .items
1122 .get(agent_id)
1123 .and_then(|v| v.iter().map(|i| i.content.len()).min())
1124 .unwrap_or(0))
1125 }
1126
1127 pub fn episodes_by_importance(
1130 &self,
1131 agent_id: &AgentId,
1132 ) -> Result<Vec<String>, AgentRuntimeError> {
1133 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_by_importance");
1134 let mut items: Vec<(f32, String)> = inner
1135 .items
1136 .get(agent_id)
1137 .map_or_else(Vec::new, |v| {
1138 v.iter().map(|i| (i.importance, i.content.clone())).collect()
1139 });
1140 items.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
1141 Ok(items.into_iter().map(|(_, c)| c).collect())
1142 }
1143
1144 pub fn content_contains_count(
1148 &self,
1149 agent_id: &AgentId,
1150 substring: &str,
1151 ) -> Result<usize, AgentRuntimeError> {
1152 let inner = recover_lock(self.inner.lock(), "EpisodicStore::content_contains_count");
1153 Ok(inner
1154 .items
1155 .get(agent_id)
1156 .map_or(0, |v| v.iter().filter(|i| i.content.contains(substring)).count()))
1157 }
1158
1159 pub fn agents_with_episodes(&self) -> Result<Vec<AgentId>, AgentRuntimeError> {
1161 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agents_with_episodes");
1162 let mut ids: Vec<AgentId> = inner
1163 .items
1164 .iter()
1165 .filter(|(_, v)| !v.is_empty())
1166 .map(|(k, _)| k.clone())
1167 .collect();
1168 ids.sort_unstable_by(|a, b| a.as_str().cmp(b.as_str()));
1169 Ok(ids)
1170 }
1171
1172 pub fn high_importance_count(
1174 &self,
1175 agent_id: &AgentId,
1176 threshold: f32,
1177 ) -> Result<usize, AgentRuntimeError> {
1178 let inner = recover_lock(self.inner.lock(), "EpisodicStore::high_importance_count");
1179 Ok(inner
1180 .items
1181 .get(agent_id)
1182 .map_or(0, |v| v.iter().filter(|i| i.importance > threshold).count()))
1183 }
1184
1185 pub fn content_lengths(&self, agent_id: &AgentId) -> Result<Vec<usize>, AgentRuntimeError> {
1187 let inner = recover_lock(self.inner.lock(), "EpisodicStore::content_lengths");
1188 let lengths = inner
1189 .items
1190 .get(agent_id)
1191 .map_or_else(Vec::new, |v| v.iter().map(|i| i.content.len()).collect());
1192 Ok(lengths)
1193 }
1194
1195 pub fn total_content_bytes(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1199 let inner = recover_lock(self.inner.lock(), "EpisodicStore::total_content_bytes");
1200 let total = inner
1201 .items
1202 .get(agent_id)
1203 .map_or(0, |items| items.iter().map(|i| i.content.len()).sum());
1204 Ok(total)
1205 }
1206
1207 pub fn avg_content_length(&self, agent_id: &AgentId) -> Result<f64, AgentRuntimeError> {
1211 let inner = recover_lock(self.inner.lock(), "EpisodicStore::avg_content_length");
1212 let items = match inner.items.get(agent_id) {
1213 Some(v) if !v.is_empty() => v,
1214 _ => return Ok(0.0),
1215 };
1216 let total: usize = items.iter().map(|i| i.content.len()).sum();
1217 Ok(total as f64 / items.len() as f64)
1218 }
1219
1220 pub fn importance_sum(&self, agent_id: &AgentId) -> Result<f32, AgentRuntimeError> {
1224 let inner = recover_lock(self.inner.lock(), "EpisodicStore::importance_sum");
1225 let sum = inner
1226 .items
1227 .get(agent_id)
1228 .map_or(0.0, |items| items.iter().map(|i| i.importance).sum());
1229 Ok(sum)
1230 }
1231
1232 pub fn recall_by_tag(
1235 &self,
1236 agent_id: &AgentId,
1237 tag: &str,
1238 limit: usize,
1239 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1240 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_by_tag");
1241 let mut matches: Vec<MemoryItem> = inner
1242 .items
1243 .get(agent_id)
1244 .map(|items| {
1245 items
1246 .iter()
1247 .filter(|i| i.tags.iter().any(|t| t == tag))
1248 .cloned()
1249 .collect()
1250 })
1251 .unwrap_or_default();
1252 matches.sort_unstable_by(|a, b| {
1253 b.importance
1254 .partial_cmp(&a.importance)
1255 .unwrap_or(std::cmp::Ordering::Equal)
1256 });
1257 if limit > 0 {
1258 matches.truncate(limit);
1259 }
1260 Ok(matches)
1261 }
1262
1263 #[tracing::instrument(skip(self))]
1265 pub fn add_episode_at(
1266 &self,
1267 agent_id: AgentId,
1268 content: impl Into<String> + std::fmt::Debug,
1269 importance: f32,
1270 timestamp: chrono::DateTime<chrono::Utc>,
1271 ) -> Result<MemoryId, AgentRuntimeError> {
1272 let mut item = MemoryItem::new(agent_id.clone(), content, importance, Vec::new());
1273 item.timestamp = timestamp;
1274 let id = item.id.clone();
1275 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::add_episode_at");
1276
1277 inner.purge_stale(&agent_id);
1278 let cap = inner.per_agent_capacity; let eviction_policy = inner.eviction_policy.clone();
1280 let agent_items = inner.items.entry(agent_id).or_default();
1281 agent_items.push(item);
1282
1283 if let Some(cap) = cap {
1284 evict_if_over_capacity(agent_items, cap, &eviction_policy);
1285 }
1286 Ok(id)
1287 }
1288
1289 pub fn add_episodes_batch(
1297 &self,
1298 agent_id: AgentId,
1299 episodes: impl IntoIterator<Item = (impl Into<String>, f32)>,
1300 ) -> Result<Vec<MemoryId>, AgentRuntimeError> {
1301 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::add_episodes_batch");
1302 inner.purge_stale(&agent_id);
1303 let cap = inner.per_agent_capacity;
1304 let eviction_policy = inner.eviction_policy.clone();
1305 let agent_items = inner.items.entry(agent_id.clone()).or_default();
1306
1307 let mut ids = Vec::new();
1308 for (content, importance) in episodes {
1309 let item = MemoryItem::new(agent_id.clone(), content, importance, Vec::new());
1310 ids.push(item.id.clone());
1311 agent_items.push(item);
1312 }
1313 if let Some(cap) = cap {
1314 evict_if_over_capacity(agent_items, cap, &eviction_policy);
1315 }
1316 Ok(ids)
1317 }
1318
1319 #[tracing::instrument(skip(self))]
1329 pub fn recall(
1330 &self,
1331 agent_id: &AgentId,
1332 limit: usize,
1333 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1334 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::recall");
1335
1336 let decay = inner.decay.clone();
1338 let max_age = inner.max_age_hours;
1339 let recall_policy = inner.recall_policy.clone();
1340
1341 if !inner.items.contains_key(agent_id) {
1343 return Ok(Vec::new());
1344 }
1345 let agent_items = inner.items.get_mut(agent_id).unwrap();
1346
1347 if let Some(ref policy) = decay {
1349 for item in agent_items.iter_mut() {
1350 policy.decay_item(item);
1351 }
1352 }
1353
1354 if let Some(max_age_h) = max_age {
1356 let cutoff =
1357 Utc::now() - chrono::Duration::seconds((max_age_h * 3600.0) as i64);
1358 agent_items.retain(|i| i.timestamp >= cutoff);
1359 }
1360
1361 let mut indices: Vec<usize> = (0..agent_items.len()).collect();
1370
1371 match recall_policy {
1372 RecallPolicy::Importance => {
1373 let cmp = |&a: &usize, &b: &usize| {
1374 agent_items[b]
1375 .importance
1376 .partial_cmp(&agent_items[a].importance)
1377 .unwrap_or(std::cmp::Ordering::Equal)
1378 };
1379 if limit > 0 && limit < indices.len() {
1380 indices.select_nth_unstable_by(limit - 1, cmp);
1381 indices[..limit].sort_unstable_by(cmp);
1382 } else {
1383 indices.sort_unstable_by(cmp);
1384 }
1385 }
1386 RecallPolicy::Hybrid {
1387 recency_weight,
1388 frequency_weight,
1389 } => {
1390 let max_recall = agent_items
1391 .iter()
1392 .map(|i| i.recall_count)
1393 .max()
1394 .unwrap_or(1)
1395 .max(1);
1396 let now = Utc::now();
1397 let cmp = |&a: &usize, &b: &usize| {
1398 let score_a = compute_hybrid_score(
1399 &agent_items[a],
1400 recency_weight,
1401 frequency_weight,
1402 max_recall,
1403 now,
1404 );
1405 let score_b = compute_hybrid_score(
1406 &agent_items[b],
1407 recency_weight,
1408 frequency_weight,
1409 max_recall,
1410 now,
1411 );
1412 score_b
1413 .partial_cmp(&score_a)
1414 .unwrap_or(std::cmp::Ordering::Equal)
1415 };
1416 if limit > 0 && limit < indices.len() {
1417 indices.select_nth_unstable_by(limit - 1, cmp);
1418 indices[..limit].sort_unstable_by(cmp);
1419 } else {
1420 indices.sort_unstable_by(cmp);
1421 }
1422 }
1423 }
1424
1425 indices.truncate(limit);
1426
1427 for &idx in &indices {
1429 agent_items[idx].recall_count += 1;
1430 }
1431
1432 let items: Vec<MemoryItem> = indices.iter().map(|&idx| agent_items[idx].clone()).collect();
1434
1435 tracing::debug!("recalled {} items", items.len());
1436 Ok(items)
1437 }
1438
1439 pub fn recall_tagged(
1446 &self,
1447 agent_id: &AgentId,
1448 tags: &[&str],
1449 limit: usize,
1450 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1451 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_tagged");
1452 let items = inner.items.get(agent_id).cloned().unwrap_or_default();
1453 drop(inner);
1454 let mut matched: Vec<MemoryItem> = items
1455 .into_iter()
1456 .filter(|item| {
1457 tags.iter()
1458 .all(|t| item.tags.iter().any(|it| it.as_str() == *t))
1459 })
1460 .collect();
1461 matched.sort_unstable_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap_or(std::cmp::Ordering::Equal));
1462 if limit > 0 {
1463 matched.truncate(limit);
1464 }
1465 Ok(matched)
1466 }
1467
1468 pub fn recall_by_id(
1473 &self,
1474 agent_id: &AgentId,
1475 id: &MemoryId,
1476 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
1477 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_by_id");
1478 Ok(inner
1479 .items
1480 .get(agent_id)
1481 .and_then(|items| items.iter().find(|i| &i.id == id).cloned()))
1482 }
1483
1484 pub fn merge_from(
1491 &self,
1492 other: &EpisodicStore,
1493 agent_id: &AgentId,
1494 ) -> Result<usize, AgentRuntimeError> {
1495 let other_items = {
1496 let inner = recover_lock(other.inner.lock(), "EpisodicStore::merge_from:read");
1497 inner.items.get(agent_id).cloned().unwrap_or_default()
1498 };
1499 let count = other_items.len();
1500 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::merge_from:write");
1501 inner.purge_stale(agent_id);
1502 let cap = inner.per_agent_capacity;
1503 let bucket = inner.items.entry(agent_id.clone()).or_default();
1504 for item in other_items {
1505 if let Some(cap) = cap {
1506 while bucket.len() >= cap {
1507 bucket.remove(0);
1508 }
1509 }
1510 bucket.push(item);
1511 }
1512 Ok(count)
1513 }
1514
1515 pub fn update_importance(
1522 &self,
1523 agent_id: &AgentId,
1524 id: &MemoryId,
1525 new_importance: f32,
1526 ) -> Result<bool, AgentRuntimeError> {
1527 let importance = new_importance.clamp(0.0, 1.0);
1528 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::update_importance");
1529 if let Some(items) = inner.items.get_mut(agent_id) {
1530 if let Some(item) = items.iter_mut().find(|i| &i.id == id) {
1531 item.importance = importance;
1532 return Ok(true);
1533 }
1534 }
1535 Ok(false)
1536 }
1537
1538 pub fn recall_since(
1545 &self,
1546 agent_id: &AgentId,
1547 cutoff: chrono::DateTime<chrono::Utc>,
1548 limit: usize,
1549 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1550 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_since");
1551 let mut items: Vec<MemoryItem> = inner
1552 .items
1553 .get(agent_id)
1554 .cloned()
1555 .unwrap_or_default()
1556 .into_iter()
1557 .filter(|i| i.timestamp >= cutoff)
1558 .collect();
1559 drop(inner);
1560 items.sort_unstable_by(|a, b| {
1561 b.importance
1562 .partial_cmp(&a.importance)
1563 .unwrap_or(std::cmp::Ordering::Equal)
1564 });
1565 if limit > 0 {
1566 items.truncate(limit);
1567 }
1568 Ok(items)
1569 }
1570
1571 pub fn update_content(
1576 &self,
1577 agent_id: &AgentId,
1578 id: &MemoryId,
1579 new_content: impl Into<String>,
1580 ) -> Result<bool, AgentRuntimeError> {
1581 let new_content = new_content.into();
1582 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::update_content");
1583 if let Some(items) = inner.items.get_mut(agent_id) {
1584 if let Some(item) = items.iter_mut().find(|i| &i.id == id) {
1585 item.content = new_content;
1586 return Ok(true);
1587 }
1588 }
1589 Ok(false)
1590 }
1591
1592 pub fn recall_recent(
1600 &self,
1601 agent_id: &AgentId,
1602 limit: usize,
1603 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1604 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_recent");
1605 let items = inner.items.get(agent_id).cloned().unwrap_or_default();
1606 drop(inner);
1607 let start = if limit > 0 && limit < items.len() {
1608 items.len() - limit
1609 } else {
1610 0
1611 };
1612 Ok(items[start..].iter().rev().cloned().collect())
1613 }
1614
1615 pub fn recall_all(&self, agent_id: &AgentId) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1622 self.recall(agent_id, usize::MAX)
1623 }
1624
1625 pub fn top_n(&self, agent_id: &AgentId, n: usize) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1629 let inner = recover_lock(self.inner.lock(), "EpisodicStore::top_n");
1630 let mut items = inner.items.get(agent_id).cloned().unwrap_or_default();
1631 items.sort_unstable_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap_or(std::cmp::Ordering::Equal));
1632 if n > 0 {
1633 items.truncate(n);
1634 }
1635 Ok(items)
1636 }
1637
1638 pub fn search_by_importance_range(
1641 &self,
1642 agent_id: &AgentId,
1643 min: f32,
1644 max: f32,
1645 limit: usize,
1646 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1647 let inner = recover_lock(self.inner.lock(), "EpisodicStore::search_by_importance_range");
1648 let mut items: Vec<MemoryItem> = inner
1649 .items
1650 .get(agent_id)
1651 .map_or_else(Vec::new, |v| {
1652 v.iter().filter(|i| i.importance >= min && i.importance <= max).cloned().collect()
1653 });
1654 items.sort_unstable_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap_or(std::cmp::Ordering::Equal));
1655 if limit > 0 {
1656 items.truncate(limit);
1657 }
1658 Ok(items)
1659 }
1660
1661 pub fn total_recall_count(&self, agent_id: &AgentId) -> Result<u64, AgentRuntimeError> {
1665 let inner = recover_lock(self.inner.lock(), "EpisodicStore::total_recall_count");
1666 Ok(inner
1667 .items
1668 .get(agent_id)
1669 .map_or(0, |items| items.iter().map(|i| i.recall_count).sum()))
1670 }
1671
1672 pub fn importance_stats(&self, agent_id: &AgentId) -> Result<(usize, f32, f32, f32), AgentRuntimeError> {
1676 let inner = recover_lock(self.inner.lock(), "EpisodicStore::importance_stats");
1677 let items = inner.items.get(agent_id).map(|v| v.as_slice()).unwrap_or(&[]);
1678 if items.is_empty() {
1679 return Ok((0, 0.0, 0.0, 0.0));
1680 }
1681 let count = items.len();
1682 let min = items.iter().map(|i| i.importance).fold(f32::MAX, f32::min);
1683 let max = items.iter().map(|i| i.importance).fold(f32::MIN, f32::max);
1684 let mean = items.iter().map(|i| i.importance).sum::<f32>() / count as f32;
1685 Ok((count, min, max, mean))
1686 }
1687
1688 pub fn oldest(
1691 &self,
1692 agent_id: &AgentId,
1693 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
1694 let inner = recover_lock(self.inner.lock(), "EpisodicStore::oldest");
1695 let item = inner.items.get(agent_id).and_then(|v| v.first()).cloned();
1696 Ok(item)
1697 }
1698
1699 pub fn clear_agent(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1703 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::clear_agent");
1704 let count = inner.items.remove(agent_id).map_or(0, |v| v.len());
1705 Ok(count)
1706 }
1707
1708 pub fn oldest_episode(
1711 &self,
1712 agent_id: &AgentId,
1713 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
1714 let inner = recover_lock(self.inner.lock(), "EpisodicStore::oldest_episode");
1715 Ok(inner
1716 .items
1717 .get(agent_id)
1718 .and_then(|v| v.iter().min_by_key(|i| i.timestamp))
1719 .cloned())
1720 }
1721
1722 pub fn max_importance_episode(
1726 &self,
1727 agent_id: &AgentId,
1728 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
1729 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_importance_episode");
1730 let item = inner
1731 .items
1732 .get(agent_id)
1733 .and_then(|v| {
1734 v.iter()
1735 .max_by(|a, b| {
1736 a.importance
1737 .partial_cmp(&b.importance)
1738 .unwrap_or(std::cmp::Ordering::Equal)
1739 })
1740 })
1741 .cloned();
1742 Ok(item)
1743 }
1744
1745 pub fn newest(
1748 &self,
1749 agent_id: &AgentId,
1750 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
1751 let inner = recover_lock(self.inner.lock(), "EpisodicStore::newest");
1752 let item = inner.items.get(agent_id).and_then(|v| v.last()).cloned();
1753 Ok(item)
1754 }
1755
1756 pub fn len(&self) -> Result<usize, AgentRuntimeError> {
1758 let inner = recover_lock(self.inner.lock(), "EpisodicStore::len");
1759 Ok(inner.items.values().map(|v| v.len()).sum())
1760 }
1761
1762 pub fn is_empty(&self) -> Result<bool, AgentRuntimeError> {
1764 Ok(self.len()? == 0)
1765 }
1766
1767 pub fn agent_count(&self) -> Result<usize, AgentRuntimeError> {
1769 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_count");
1770 Ok(inner.items.len())
1771 }
1772
1773 pub fn agent_memory_count(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1777 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_memory_count");
1778 Ok(inner.items.get(agent_id).map_or(0, |v| v.len()))
1779 }
1780
1781 pub fn has_episodes(&self, agent_id: &AgentId) -> Result<bool, AgentRuntimeError> {
1783 let inner = recover_lock(self.inner.lock(), "EpisodicStore::has_episodes");
1784 Ok(inner
1785 .items
1786 .get(agent_id)
1787 .map_or(false, |v| !v.is_empty()))
1788 }
1789
1790 pub fn latest_episode(
1795 &self,
1796 agent_id: &AgentId,
1797 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
1798 let inner = recover_lock(self.inner.lock(), "EpisodicStore::latest_episode");
1799 Ok(inner
1800 .items
1801 .get(agent_id)
1802 .and_then(|v| v.iter().max_by_key(|i| i.timestamp))
1803 .cloned())
1804 }
1805
1806 pub fn max_recall_count_for(&self, agent_id: &AgentId) -> Result<Option<u64>, AgentRuntimeError> {
1809 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_recall_count_for");
1810 Ok(inner
1811 .items
1812 .get(agent_id)
1813 .and_then(|v| v.iter().map(|i| i.recall_count).max()))
1814 }
1815
1816 pub fn avg_importance(&self, agent_id: &AgentId) -> Result<f64, AgentRuntimeError> {
1820 let inner = recover_lock(self.inner.lock(), "EpisodicStore::avg_importance");
1821 let episodes = match inner.items.get(agent_id) {
1822 Some(v) if !v.is_empty() => v,
1823 _ => return Ok(0.0),
1824 };
1825 let sum: f64 = episodes.iter().map(|i| f64::from(i.importance)).sum();
1826 Ok(sum / episodes.len() as f64)
1827 }
1828
1829 pub fn importance_range(
1832 &self,
1833 agent_id: &AgentId,
1834 ) -> Result<Option<(f32, f32)>, AgentRuntimeError> {
1835 let inner = recover_lock(self.inner.lock(), "EpisodicStore::importance_range");
1836 Ok(inner.items.get(agent_id).and_then(|v| {
1837 if v.is_empty() {
1838 return None;
1839 }
1840 let min = v
1841 .iter()
1842 .map(|i| i.importance)
1843 .fold(f32::INFINITY, f32::min);
1844 let max = v
1845 .iter()
1846 .map(|i| i.importance)
1847 .fold(f32::NEG_INFINITY, f32::max);
1848 Some((min, max))
1849 }))
1850 }
1851
1852 pub fn sum_recall_counts(&self, agent_id: &AgentId) -> Result<u64, AgentRuntimeError> {
1856 let inner = recover_lock(self.inner.lock(), "EpisodicStore::sum_recall_counts");
1857 Ok(inner
1858 .items
1859 .get(agent_id)
1860 .map(|v| v.iter().map(|i| i.recall_count).sum())
1861 .unwrap_or(0))
1862 }
1863
1864 pub fn list_agents(&self) -> Result<Vec<AgentId>, AgentRuntimeError> {
1868 let inner = recover_lock(self.inner.lock(), "EpisodicStore::list_agents");
1869 Ok(inner.items.keys().cloned().collect())
1870 }
1871
1872 pub fn purge_agent_memories(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1876 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::purge_agent_memories");
1877 let removed = inner.items.remove(agent_id).map_or(0, |v| v.len());
1878 Ok(removed)
1879 }
1880
1881 pub fn clear_agent_memory(&self, agent_id: &AgentId) -> Result<(), AgentRuntimeError> {
1885 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::clear_agent_memory");
1886 inner.items.remove(agent_id);
1887 Ok(())
1888 }
1889
1890 pub fn clear_all(&self) -> Result<(), AgentRuntimeError> {
1894 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::clear_all");
1895 inner.items.clear();
1896 Ok(())
1897 }
1898
1899 pub fn export_agent_memory(&self, agent_id: &AgentId) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1903 let inner = recover_lock(self.inner.lock(), "EpisodicStore::export_agent_memory");
1904 Ok(inner.items.get(agent_id).cloned().unwrap_or_default())
1905 }
1906
1907 pub fn import_agent_memory(&self, agent_id: &AgentId, items: Vec<MemoryItem>) -> Result<(), AgentRuntimeError> {
1911 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::import_agent_memory");
1912 inner.items.insert(agent_id.clone(), items);
1913 Ok(())
1914 }
1915
1916 #[doc(hidden)]
1921 pub fn bump_recall_count_by_content(&self, content: &str, amount: u64) {
1922 let mut inner = recover_lock(
1923 self.inner.lock(),
1924 "EpisodicStore::bump_recall_count_by_content",
1925 );
1926 for agent_items in inner.items.values_mut() {
1927 for item in agent_items.iter_mut() {
1928 if item.content == content {
1929 item.recall_count = item.recall_count.saturating_add(amount);
1930 }
1931 }
1932 }
1933 }
1934
1935 pub fn search_by_content(
1942 &self,
1943 agent_id: &AgentId,
1944 query: &str,
1945 limit: usize,
1946 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1947 let inner = recover_lock(self.inner.lock(), "EpisodicStore::search_by_content");
1948 let items = inner.items.get(agent_id).cloned().unwrap_or_default();
1949 drop(inner);
1950 let mut matched: Vec<MemoryItem> = items
1951 .into_iter()
1952 .filter(|item| item.content.contains(query))
1953 .collect();
1954 matched.sort_unstable_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap_or(std::cmp::Ordering::Equal));
1955 if limit > 0 {
1956 matched.truncate(limit);
1957 }
1958 Ok(matched)
1959 }
1960}
1961
1962impl Default for EpisodicStore {
1963 fn default() -> Self {
1964 Self::new()
1965 }
1966}
1967
1968#[derive(Debug, Clone)]
1977pub struct SemanticStore {
1978 inner: Arc<Mutex<SemanticInner>>,
1979}
1980
1981#[derive(Debug)]
1982struct SemanticInner {
1983 entries: Vec<SemanticEntry>,
1984 expected_dim: Option<usize>,
1985}
1986
1987#[derive(Debug, Clone)]
1988struct SemanticEntry {
1989 key: String,
1990 value: String,
1991 tags: Vec<String>,
1992 embedding: Option<Vec<f32>>,
1993}
1994
1995impl SemanticStore {
1996 pub fn new() -> Self {
1998 Self {
1999 inner: Arc::new(Mutex::new(SemanticInner {
2000 entries: Vec::new(),
2001 expected_dim: None,
2002 })),
2003 }
2004 }
2005
2006 #[tracing::instrument(skip(self))]
2008 pub fn store(
2009 &self,
2010 key: impl Into<String> + std::fmt::Debug,
2011 value: impl Into<String> + std::fmt::Debug,
2012 tags: Vec<String>,
2013 ) -> Result<(), AgentRuntimeError> {
2014 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::store");
2015 inner.entries.push(SemanticEntry {
2016 key: key.into(),
2017 value: value.into(),
2018 tags,
2019 embedding: None,
2020 });
2021 Ok(())
2022 }
2023
2024 #[tracing::instrument(skip(self))]
2029 pub fn store_with_embedding(
2030 &self,
2031 key: impl Into<String> + std::fmt::Debug,
2032 value: impl Into<String> + std::fmt::Debug,
2033 tags: Vec<String>,
2034 embedding: Vec<f32>,
2035 ) -> Result<(), AgentRuntimeError> {
2036 if embedding.is_empty() {
2037 return Err(AgentRuntimeError::Memory(
2038 "embedding vector must not be empty".into(),
2039 ));
2040 }
2041 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::store_with_embedding");
2042 if let Some(expected) = inner.expected_dim {
2044 if expected != embedding.len() {
2045 return Err(AgentRuntimeError::Memory(format!(
2046 "embedding dimension mismatch: expected {expected}, got {}",
2047 embedding.len()
2048 )));
2049 }
2050 } else {
2051 inner.expected_dim = Some(embedding.len());
2052 }
2053 let mut embedding = embedding;
2056 normalize_in_place(&mut embedding);
2057 inner.entries.push(SemanticEntry {
2058 key: key.into(),
2059 value: value.into(),
2060 tags,
2061 embedding: Some(embedding),
2062 });
2063 Ok(())
2064 }
2065
2066 #[tracing::instrument(skip(self))]
2070 pub fn retrieve(&self, tags: &[&str]) -> Result<Vec<(String, String)>, AgentRuntimeError> {
2071 let inner = recover_lock(self.inner.lock(), "SemanticStore::retrieve");
2072
2073 let results = inner
2074 .entries
2075 .iter()
2076 .filter(|entry| {
2077 tags.iter()
2078 .all(|t| entry.tags.iter().any(|et| et.as_str() == *t))
2079 })
2080 .map(|e| (e.key.clone(), e.value.clone()))
2081 .collect();
2082
2083 Ok(results)
2084 }
2085
2086 #[tracing::instrument(skip(self, query_embedding))]
2096 pub fn retrieve_similar(
2097 &self,
2098 query_embedding: &[f32],
2099 top_k: usize,
2100 ) -> Result<Vec<(String, String, f32)>, AgentRuntimeError> {
2101 let inner = recover_lock(self.inner.lock(), "SemanticStore::retrieve_similar");
2102
2103 if let Some(expected) = inner.expected_dim {
2105 if expected != query_embedding.len() {
2106 return Err(AgentRuntimeError::Memory(format!(
2107 "query embedding dimension mismatch: expected {expected}, got {}",
2108 query_embedding.len()
2109 )));
2110 }
2111 }
2112
2113 let query_norm: f32 = query_embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
2116 if query_norm < f32::EPSILON {
2117 return Ok(vec![]);
2118 }
2119 let query_unit: Vec<f32> = query_embedding.iter().map(|x| x / query_norm).collect();
2120
2121 let mut scored: Vec<(String, String, f32)> = inner
2122 .entries
2123 .iter()
2124 .filter_map(|entry| {
2125 entry.embedding.as_ref().map(|emb| {
2126 let sim = emb
2127 .iter()
2128 .zip(query_unit.iter())
2129 .map(|(a, b)| a * b)
2130 .sum::<f32>()
2131 .clamp(-1.0, 1.0);
2132 (entry.key.clone(), entry.value.clone(), sim)
2133 })
2134 })
2135 .collect();
2136
2137 let cmp = |a: &(String, String, f32), b: &(String, String, f32)| {
2140 b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal)
2141 };
2142 if top_k > 0 && top_k < scored.len() {
2143 scored.select_nth_unstable_by(top_k - 1, cmp);
2144 scored[..top_k].sort_unstable_by(cmp);
2145 } else {
2146 scored.sort_unstable_by(cmp);
2147 }
2148 scored.truncate(top_k);
2149 Ok(scored)
2150 }
2151
2152 pub fn update(&self, key: &str, new_value: impl Into<String>) -> Result<bool, AgentRuntimeError> {
2156 let new_value = new_value.into();
2157 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::update");
2158 if let Some(entry) = inner.entries.iter_mut().find(|e| e.key == key) {
2159 entry.value = new_value;
2160 Ok(true)
2161 } else {
2162 Ok(false)
2163 }
2164 }
2165
2166 pub fn retrieve_by_key(&self, key: &str) -> Result<Option<(String, Vec<String>)>, AgentRuntimeError> {
2170 let inner = recover_lock(self.inner.lock(), "SemanticStore::retrieve_by_key");
2171 Ok(inner.entries.iter().find(|e| e.key == key).map(|e| (e.value.clone(), e.tags.clone())))
2172 }
2173
2174 pub fn contains(&self, key: &str) -> Result<bool, AgentRuntimeError> {
2176 let inner = recover_lock(self.inner.lock(), "SemanticStore::contains");
2177 Ok(inner.entries.iter().any(|e| e.key == key))
2178 }
2179
2180 pub fn clear(&self) -> Result<(), AgentRuntimeError> {
2182 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::clear");
2183 inner.entries.clear();
2184 inner.expected_dim = None;
2185 Ok(())
2186 }
2187
2188 pub fn count_by_tag(&self, tag: &str) -> Result<usize, AgentRuntimeError> {
2190 let inner = recover_lock(self.inner.lock(), "SemanticStore::count_by_tag");
2191 Ok(inner
2192 .entries
2193 .iter()
2194 .filter(|e| e.tags.iter().any(|t| t.as_str() == tag))
2195 .count())
2196 }
2197
2198 pub fn list_tags(&self) -> Result<Vec<String>, AgentRuntimeError> {
2200 let inner = recover_lock(self.inner.lock(), "SemanticStore::list_tags");
2201 let mut tags: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
2202 for entry in &inner.entries {
2203 for tag in &entry.tags {
2204 tags.insert(tag.clone());
2205 }
2206 }
2207 Ok(tags.into_iter().collect())
2208 }
2209
2210 pub fn remove_entries_with_tag(&self, tag: &str) -> Result<usize, AgentRuntimeError> {
2214 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::remove_entries_with_tag");
2215 let before = inner.entries.len();
2216 inner.entries.retain(|e| !e.tags.iter().any(|t| t == tag));
2217 Ok(before - inner.entries.len())
2218 }
2219
2220 pub fn most_common_tag(&self) -> Result<Option<String>, AgentRuntimeError> {
2224 let inner = recover_lock(self.inner.lock(), "SemanticStore::most_common_tag");
2225 let mut counts: std::collections::HashMap<&str, usize> = std::collections::HashMap::new();
2226 for entry in &inner.entries {
2227 for tag in &entry.tags {
2228 *counts.entry(tag.as_str()).or_insert(0) += 1;
2229 }
2230 }
2231 Ok(counts.into_iter().max_by_key(|(_, c)| *c).map(|(t, _)| t.to_string()))
2232 }
2233
2234 pub fn keys_for_tag(&self, tag: &str) -> Result<Vec<String>, AgentRuntimeError> {
2236 let inner = recover_lock(self.inner.lock(), "SemanticStore::keys_for_tag");
2237 let keys = inner
2238 .entries
2239 .iter()
2240 .filter(|e| e.tags.iter().any(|t| t == tag))
2241 .map(|e| e.key.clone())
2242 .collect();
2243 Ok(keys)
2244 }
2245
2246 pub fn unique_tags(&self) -> Result<Vec<String>, AgentRuntimeError> {
2248 let inner = recover_lock(self.inner.lock(), "SemanticStore::unique_tags");
2249 let mut tags: Vec<String> = inner
2250 .entries
2251 .iter()
2252 .flat_map(|e| e.tags.iter().cloned())
2253 .collect::<std::collections::HashSet<_>>()
2254 .into_iter()
2255 .collect();
2256 tags.sort_unstable();
2257 Ok(tags)
2258 }
2259
2260 pub fn tag_count(&self) -> Result<usize, AgentRuntimeError> {
2264 let inner = recover_lock(self.inner.lock(), "SemanticStore::tag_count");
2265 let distinct: std::collections::HashSet<&str> = inner
2266 .entries
2267 .iter()
2268 .flat_map(|e| e.tags.iter().map(|t| t.as_str()))
2269 .collect();
2270 Ok(distinct.len())
2271 }
2272
2273 pub fn entry_count_with_embedding(&self) -> Result<usize, AgentRuntimeError> {
2275 let inner = recover_lock(self.inner.lock(), "SemanticStore::entry_count_with_embedding");
2276 Ok(inner.entries.iter().filter(|e| e.embedding.is_some()).count())
2277 }
2278
2279 pub fn get_value(&self, key: &str) -> Result<Option<String>, AgentRuntimeError> {
2287 let inner = recover_lock(self.inner.lock(), "SemanticStore::get_value");
2288 Ok(inner
2289 .entries
2290 .iter()
2291 .find(|e| e.key == key)
2292 .map(|e| e.value.clone()))
2293 }
2294
2295 pub fn get_tags(&self, key: &str) -> Result<Option<Vec<String>>, AgentRuntimeError> {
2299 let inner = recover_lock(self.inner.lock(), "SemanticStore::get_tags");
2300 Ok(inner.entries.iter().find(|e| e.key == key).map(|e| e.tags.clone()))
2301 }
2302
2303 pub fn keys_with_tag(&self, tag: &str) -> Result<Vec<String>, AgentRuntimeError> {
2305 let inner = recover_lock(self.inner.lock(), "SemanticStore::keys_with_tag");
2306 Ok(inner
2307 .entries
2308 .iter()
2309 .filter(|e| e.tags.iter().any(|t| t.as_str() == tag))
2310 .map(|e| e.key.clone())
2311 .collect())
2312 }
2313
2314 pub fn tags_for(&self, key: &str) -> Result<Option<Vec<String>>, AgentRuntimeError> {
2317 let inner = recover_lock(self.inner.lock(), "SemanticStore::tags_for");
2318 Ok(inner
2319 .entries
2320 .iter()
2321 .find(|e| e.key == key)
2322 .map(|e| e.tags.clone()))
2323 }
2324
2325 pub fn has_key(&self, key: &str) -> Result<bool, AgentRuntimeError> {
2327 let inner = recover_lock(self.inner.lock(), "SemanticStore::has_key");
2328 Ok(inner.entries.iter().any(|e| e.key == key))
2329 }
2330
2331 pub fn value_for(&self, key: &str) -> Result<Option<String>, AgentRuntimeError> {
2334 let inner = recover_lock(self.inner.lock(), "SemanticStore::value_for");
2335 Ok(inner
2336 .entries
2337 .iter()
2338 .find(|e| e.key == key)
2339 .map(|e| e.value.clone()))
2340 }
2341
2342 pub fn entries_without_tags(&self) -> Result<usize, AgentRuntimeError> {
2344 let inner = recover_lock(self.inner.lock(), "SemanticStore::entries_without_tags");
2345 Ok(inner.entries.iter().filter(|e| e.tags.is_empty()).count())
2346 }
2347
2348 pub fn most_tagged_key(&self) -> Result<Option<String>, AgentRuntimeError> {
2352 let inner = recover_lock(self.inner.lock(), "SemanticStore::most_tagged_key");
2353 Ok(inner
2354 .entries
2355 .iter()
2356 .max_by_key(|e| e.tags.len())
2357 .map(|e| e.key.clone()))
2358 }
2359
2360 pub fn rename_tag(&self, old_tag: &str, new_tag: &str) -> Result<usize, AgentRuntimeError> {
2365 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::rename_tag");
2366 let mut count = 0;
2367 for entry in &mut inner.entries {
2368 for tag in &mut entry.tags {
2369 if tag == old_tag {
2370 *tag = new_tag.to_string();
2371 count += 1;
2372 }
2373 }
2374 }
2375 Ok(count)
2376 }
2377
2378 pub fn count_matching_value(&self, substring: &str) -> Result<usize, AgentRuntimeError> {
2380 let inner = recover_lock(self.inner.lock(), "SemanticStore::count_matching_value");
2381 Ok(inner.entries.iter().filter(|e| e.value.contains(substring)).count())
2382 }
2383
2384 pub fn entries_with_no_tags(&self) -> Result<Vec<String>, AgentRuntimeError> {
2386 let inner = recover_lock(self.inner.lock(), "SemanticStore::entries_with_no_tags");
2387 Ok(inner
2388 .entries
2389 .iter()
2390 .filter(|e| e.tags.is_empty())
2391 .map(|e| e.key.clone())
2392 .collect())
2393 }
2394
2395 pub fn avg_tag_count_per_entry(&self) -> Result<f64, AgentRuntimeError> {
2399 let inner = recover_lock(self.inner.lock(), "SemanticStore::avg_tag_count_per_entry");
2400 let n = inner.entries.len();
2401 if n == 0 {
2402 return Ok(0.0);
2403 }
2404 let total: usize = inner.entries.iter().map(|e| e.tags.len()).sum();
2405 Ok(total as f64 / n as f64)
2406 }
2407
2408 pub fn most_recent_key(&self) -> Result<Option<String>, AgentRuntimeError> {
2410 let inner = recover_lock(self.inner.lock(), "SemanticStore::most_recent_key");
2411 Ok(inner.entries.last().map(|e| e.key.clone()))
2412 }
2413
2414 pub fn oldest_key(&self) -> Result<Option<String>, AgentRuntimeError> {
2416 let inner = recover_lock(self.inner.lock(), "SemanticStore::oldest_key");
2417 Ok(inner.entries.first().map(|e| e.key.clone()))
2418 }
2419
2420 pub fn remove_by_key(&self, key: &str) -> Result<usize, AgentRuntimeError> {
2424 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::remove_by_key");
2425 let before = inner.entries.len();
2426 inner.entries.retain(|e| e.key != key);
2427 Ok(before - inner.entries.len())
2428 }
2429
2430 pub fn entry_count_with_tag(&self, tag: &str) -> Result<usize, AgentRuntimeError> {
2432 let inner = recover_lock(self.inner.lock(), "SemanticStore::entry_count_with_tag");
2433 Ok(inner
2434 .entries
2435 .iter()
2436 .filter(|e| e.tags.iter().any(|t| t == tag))
2437 .count())
2438 }
2439
2440 pub fn list_keys(&self) -> Result<Vec<String>, AgentRuntimeError> {
2444 let inner = recover_lock(self.inner.lock(), "SemanticStore::list_keys");
2445 Ok(inner.entries.iter().map(|e| e.key.clone()).collect())
2446 }
2447
2448 pub fn keys(&self) -> Result<Vec<String>, AgentRuntimeError> {
2454 self.list_keys()
2455 }
2456
2457 pub fn values(&self) -> Result<Vec<String>, AgentRuntimeError> {
2459 let inner = recover_lock(self.inner.lock(), "SemanticStore::values");
2460 Ok(inner.entries.iter().map(|e| e.value.clone()).collect())
2461 }
2462
2463 pub fn keys_matching(&self, pattern: &str) -> Result<Vec<String>, AgentRuntimeError> {
2467 let inner = recover_lock(self.inner.lock(), "SemanticStore::keys_matching");
2468 let lower = pattern.to_ascii_lowercase();
2469 Ok(inner
2470 .entries
2471 .iter()
2472 .filter(|e| e.key.to_ascii_lowercase().contains(&lower))
2473 .map(|e| e.key.clone())
2474 .collect())
2475 }
2476
2477 pub fn update_value(
2481 &self,
2482 key: &str,
2483 new_value: impl Into<String>,
2484 ) -> Result<bool, AgentRuntimeError> {
2485 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::update_value");
2486 if let Some(entry) = inner.entries.iter_mut().find(|e| e.key == key) {
2487 entry.value = new_value.into();
2488 Ok(true)
2489 } else {
2490 Ok(false)
2491 }
2492 }
2493
2494 pub fn to_map(&self) -> Result<std::collections::HashMap<String, String>, AgentRuntimeError> {
2499 let inner = recover_lock(self.inner.lock(), "SemanticStore::to_map");
2500 Ok(inner
2501 .entries
2502 .iter()
2503 .map(|e| (e.key.clone(), e.value.clone()))
2504 .collect())
2505 }
2506
2507 pub fn update_tags(
2511 &self,
2512 key: &str,
2513 new_tags: Vec<String>,
2514 ) -> Result<bool, AgentRuntimeError> {
2515 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::update_tags");
2516 if let Some(entry) = inner.entries.iter_mut().find(|e| e.key == key) {
2517 entry.tags = new_tags;
2518 Ok(true)
2519 } else {
2520 Ok(false)
2521 }
2522 }
2523
2524 pub fn total_value_bytes(&self) -> Result<usize, AgentRuntimeError> {
2528 let inner = recover_lock(self.inner.lock(), "SemanticStore::total_value_bytes");
2529 Ok(inner.entries.iter().map(|e| e.value.len()).sum())
2530 }
2531
2532 pub fn avg_value_bytes(&self) -> Result<f64, AgentRuntimeError> {
2536 let inner = recover_lock(self.inner.lock(), "SemanticStore::avg_value_bytes");
2537 if inner.entries.is_empty() {
2538 return Ok(0.0);
2539 }
2540 let total: usize = inner.entries.iter().map(|e| e.value.len()).sum();
2541 Ok(total as f64 / inner.entries.len() as f64)
2542 }
2543
2544 pub fn max_value_bytes(&self) -> Result<usize, AgentRuntimeError> {
2548 let inner = recover_lock(self.inner.lock(), "SemanticStore::max_value_bytes");
2549 Ok(inner.entries.iter().map(|e| e.value.len()).max().unwrap_or(0))
2550 }
2551
2552 pub fn min_value_bytes(&self) -> Result<usize, AgentRuntimeError> {
2556 let inner = recover_lock(self.inner.lock(), "SemanticStore::min_value_bytes");
2557 Ok(inner.entries.iter().map(|e| e.value.len()).min().unwrap_or(0))
2558 }
2559
2560 pub fn all_keys(&self) -> Result<Vec<String>, AgentRuntimeError> {
2562 let inner = recover_lock(self.inner.lock(), "SemanticStore::all_keys");
2563 let mut keys: Vec<String> = inner.entries.iter().map(|e| e.key.clone()).collect();
2564 keys.sort_unstable();
2565 Ok(keys)
2566 }
2567
2568 pub fn len(&self) -> Result<usize, AgentRuntimeError> {
2570 let inner = recover_lock(self.inner.lock(), "SemanticStore::len");
2571 Ok(inner.entries.len())
2572 }
2573
2574 pub fn is_empty(&self) -> Result<bool, AgentRuntimeError> {
2576 Ok(self.len()? == 0)
2577 }
2578
2579 pub fn count(&self) -> Result<usize, AgentRuntimeError> {
2585 self.len()
2586 }
2587
2588 pub fn remove(&self, key: &str) -> Result<bool, AgentRuntimeError> {
2593 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::remove");
2594 let before = inner.entries.len();
2595 inner.entries.retain(|e| e.key != key);
2596 Ok(inner.entries.len() < before)
2597 }
2598
2599}
2600
2601impl Default for SemanticStore {
2602 fn default() -> Self {
2603 Self::new()
2604 }
2605}
2606
2607#[derive(Debug, Clone)]
2618pub struct WorkingMemory {
2619 capacity: usize,
2620 inner: Arc<Mutex<WorkingInner>>,
2621}
2622
2623#[derive(Debug)]
2624struct WorkingInner {
2625 map: HashMap<String, String>,
2626 order: VecDeque<String>,
2627}
2628
2629impl WorkingMemory {
2630 pub fn new(capacity: usize) -> Result<Self, AgentRuntimeError> {
2636 if capacity == 0 {
2637 return Err(AgentRuntimeError::Memory(
2638 "WorkingMemory capacity must be > 0".into(),
2639 ));
2640 }
2641 Ok(Self {
2642 capacity,
2643 inner: Arc::new(Mutex::new(WorkingInner {
2644 map: HashMap::new(),
2645 order: VecDeque::new(),
2646 })),
2647 })
2648 }
2649
2650 #[tracing::instrument(skip(self))]
2652 pub fn set(
2653 &self,
2654 key: impl Into<String> + std::fmt::Debug,
2655 value: impl Into<String> + std::fmt::Debug,
2656 ) -> Result<(), AgentRuntimeError> {
2657 let key = key.into();
2658 let value = value.into();
2659 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::set");
2660
2661 if inner.map.contains_key(&key) {
2663 inner.order.retain(|k| k != &key);
2664 } else if inner.map.len() >= self.capacity {
2665 if let Some(oldest) = inner.order.pop_front() {
2667 inner.map.remove(&oldest);
2668 }
2669 }
2670
2671 inner.order.push_back(key.clone());
2672 inner.map.insert(key, value);
2673 Ok(())
2674 }
2675
2676 #[tracing::instrument(skip(self))]
2682 pub fn get(&self, key: &str) -> Result<Option<String>, AgentRuntimeError> {
2683 let inner = recover_lock(self.inner.lock(), "WorkingMemory::get");
2684 Ok(inner.map.get(key).cloned())
2685 }
2686
2687 pub fn set_many(
2695 &self,
2696 pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
2697 ) -> Result<(), AgentRuntimeError> {
2698 let capacity = self.capacity;
2699 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::set_many");
2700 for (key, value) in pairs {
2701 let key: String = key.into();
2702 let value: String = value.into();
2703 if inner.map.contains_key(&key) {
2704 inner.order.retain(|k| k != &key);
2705 } else if inner.map.len() >= capacity {
2706 if let Some(oldest) = inner.order.pop_front() {
2707 inner.map.remove(&oldest);
2708 }
2709 }
2710 inner.order.push_back(key.clone());
2711 inner.map.insert(key, value);
2712 }
2713 Ok(())
2714 }
2715
2716 pub fn set_if_absent(
2726 &self,
2727 key: impl Into<String> + std::fmt::Debug,
2728 value: impl Into<String> + std::fmt::Debug,
2729 ) -> Result<bool, AgentRuntimeError> {
2730 let key = key.into();
2731 {
2732 let inner = recover_lock(self.inner.lock(), "WorkingMemory::set_if_absent");
2733 if inner.map.contains_key(&key) {
2734 return Ok(false);
2735 }
2736 }
2737 self.set(key, value)?;
2738 Ok(true)
2739 }
2740
2741 pub fn update_if_exists(
2743 &self,
2744 key: &str,
2745 value: impl Into<String>,
2746 ) -> Result<bool, AgentRuntimeError> {
2747 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::update_if_exists");
2748 if let Some(v) = inner.map.get_mut(key) {
2749 *v = value.into();
2750 Ok(true)
2751 } else {
2752 Ok(false)
2753 }
2754 }
2755
2756 pub fn update_many(
2764 &self,
2765 pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
2766 ) -> Result<usize, AgentRuntimeError> {
2767 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::update_many");
2768 let mut updated = 0;
2769 for (key, value) in pairs {
2770 let key: String = key.into();
2771 if let Some(v) = inner.map.get_mut(&key) {
2772 *v = value.into();
2773 updated += 1;
2774 }
2775 }
2776 Ok(updated)
2777 }
2778
2779 pub fn keys_starting_with(&self, prefix: &str) -> Result<Vec<String>, AgentRuntimeError> {
2784 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys_starting_with");
2785 Ok(inner
2786 .map
2787 .keys()
2788 .filter(|k| k.starts_with(prefix))
2789 .cloned()
2790 .collect())
2791 }
2792
2793 pub fn values_matching(&self, pattern: &str) -> Result<Vec<(String, String)>, AgentRuntimeError> {
2799 let inner = recover_lock(self.inner.lock(), "WorkingMemory::values_matching");
2800 Ok(inner
2801 .map
2802 .iter()
2803 .filter(|(_, v)| v.contains(pattern))
2804 .map(|(k, v)| (k.clone(), v.clone()))
2805 .collect())
2806 }
2807
2808 pub fn value_length(&self, key: &str) -> Result<Option<usize>, AgentRuntimeError> {
2813 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_length");
2814 Ok(inner.map.get(key).map(|v| v.len()))
2815 }
2816
2817 pub fn contains_all<'a>(&self, keys: impl IntoIterator<Item = &'a str>) -> Result<bool, AgentRuntimeError> {
2823 let inner = recover_lock(self.inner.lock(), "WorkingMemory::contains_all");
2824 Ok(keys.into_iter().all(|k| inner.map.contains_key(k)))
2825 }
2826
2827 pub fn has_any_key<'a>(&self, keys: impl IntoIterator<Item = &'a str>) -> Result<bool, AgentRuntimeError> {
2831 let inner = recover_lock(self.inner.lock(), "WorkingMemory::has_any_key");
2832 Ok(keys.into_iter().any(|k| inner.map.contains_key(k)))
2833 }
2834
2835 pub fn rename(
2839 &self,
2840 old_key: &str,
2841 new_key: impl Into<String>,
2842 ) -> Result<bool, AgentRuntimeError> {
2843 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::rename");
2844 let value = match inner.map.remove(old_key) {
2845 None => return Ok(false),
2846 Some(v) => v,
2847 };
2848 let new_key = new_key.into();
2849 if let Some(pos) = inner.order.iter().position(|k| k == old_key) {
2851 inner.order[pos] = new_key.clone();
2852 }
2853 inner.map.insert(new_key, value);
2854 Ok(true)
2855 }
2856
2857 pub fn get_many(&self, keys: &[&str]) -> Result<Vec<Option<String>>, AgentRuntimeError> {
2862 let inner = recover_lock(self.inner.lock(), "WorkingMemory::get_many");
2863 Ok(keys.iter().map(|k| inner.map.get(*k).cloned()).collect())
2864 }
2865
2866 pub fn contains(&self, key: &str) -> Result<bool, AgentRuntimeError> {
2868 let inner = recover_lock(self.inner.lock(), "WorkingMemory::contains");
2869 Ok(inner.map.contains_key(key))
2870 }
2871
2872 pub fn get_or_default(
2874 &self,
2875 key: &str,
2876 default: impl Into<String>,
2877 ) -> Result<String, AgentRuntimeError> {
2878 Ok(self.get(key)?.unwrap_or_else(|| default.into()))
2879 }
2880
2881 pub fn remove(&self, key: &str) -> Result<bool, AgentRuntimeError> {
2883 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::remove");
2884 if inner.map.remove(key).is_some() {
2885 inner.order.retain(|k| k != key);
2886 Ok(true)
2887 } else {
2888 Ok(false)
2889 }
2890 }
2891
2892 pub fn keys(&self) -> Result<Vec<String>, AgentRuntimeError> {
2894 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys");
2895 Ok(inner.order.iter().cloned().collect())
2896 }
2897
2898 pub fn values(&self) -> Result<Vec<String>, AgentRuntimeError> {
2902 let inner = recover_lock(self.inner.lock(), "WorkingMemory::values");
2903 Ok(inner
2904 .order
2905 .iter()
2906 .filter_map(|k| inner.map.get(k).cloned())
2907 .collect())
2908 }
2909
2910 pub fn clear(&self) -> Result<(), AgentRuntimeError> {
2912 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::clear");
2913 inner.map.clear();
2914 inner.order.clear();
2915 Ok(())
2916 }
2917
2918 pub fn iter_sorted(&self) -> Result<Vec<(String, String)>, AgentRuntimeError> {
2928 let inner = recover_lock(self.inner.lock(), "WorkingMemory::iter_sorted");
2929 let mut pairs: Vec<(String, String)> = inner
2930 .map
2931 .iter()
2932 .map(|(k, v)| (k.clone(), v.clone()))
2933 .collect();
2934 pairs.sort_unstable_by(|a, b| a.0.cmp(&b.0));
2935 Ok(pairs)
2936 }
2937
2938 pub fn drain(&self) -> Result<Vec<(String, String)>, AgentRuntimeError> {
2941 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::drain");
2942 let pairs: Vec<(String, String)> = inner
2943 .order
2944 .iter()
2945 .filter_map(|k| inner.map.get(k).map(|v| (k.clone(), v.clone())))
2946 .collect();
2947 inner.map.clear();
2948 inner.order.clear();
2949 Ok(pairs)
2950 }
2951
2952 pub fn snapshot(&self) -> Result<std::collections::HashMap<String, String>, AgentRuntimeError> {
2958 let inner = recover_lock(self.inner.lock(), "WorkingMemory::snapshot");
2959 Ok(inner.map.clone())
2960 }
2961
2962 pub fn len(&self) -> Result<usize, AgentRuntimeError> {
2964 let inner = recover_lock(self.inner.lock(), "WorkingMemory::len");
2965 Ok(inner.map.len())
2966 }
2967
2968 pub fn is_empty(&self) -> Result<bool, AgentRuntimeError> {
2970 Ok(self.len()? == 0)
2971 }
2972
2973 pub fn iter(&self) -> Result<Vec<(String, String)>, AgentRuntimeError> {
2980 self.entries()
2981 }
2982
2983 pub fn entries(&self) -> Result<Vec<(String, String)>, AgentRuntimeError> {
2985 let inner = recover_lock(self.inner.lock(), "WorkingMemory::entries");
2986 let entries = inner
2987 .order
2988 .iter()
2989 .filter_map(|k| inner.map.get(k).map(|v| (k.clone(), v.clone())))
2990 .collect();
2991 Ok(entries)
2992 }
2993
2994 pub fn pop_oldest(&self) -> Result<Option<(String, String)>, AgentRuntimeError> {
2998 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::pop_oldest");
2999 if let Some(key) = inner.order.pop_front() {
3000 let value = inner.map.remove(&key).unwrap_or_default();
3001 Ok(Some((key, value)))
3002 } else {
3003 Ok(None)
3004 }
3005 }
3006
3007 pub fn peek_oldest(&self) -> Result<Option<(String, String)>, AgentRuntimeError> {
3014 let inner = recover_lock(self.inner.lock(), "WorkingMemory::peek_oldest");
3015 Ok(inner.order.front().and_then(|key| {
3016 inner.map.get(key).map(|val| (key.clone(), val.clone()))
3017 }))
3018 }
3019
3020 pub fn capacity(&self) -> usize {
3028 self.capacity
3029 }
3030
3031 pub fn fill_ratio(&self) -> Result<f64, AgentRuntimeError> {
3036 Ok(self.len()? as f64 / self.capacity as f64)
3037 }
3038
3039 pub fn is_at_capacity(&self) -> Result<bool, AgentRuntimeError> {
3045 Ok(self.len()? >= self.capacity)
3046 }
3047
3048 pub fn remove_keys_starting_with(&self, prefix: &str) -> Result<usize, AgentRuntimeError> {
3053 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::remove_keys_starting_with");
3054 let before = inner.map.len();
3055 inner.map.retain(|k, _| !k.starts_with(prefix));
3056 Ok(before - inner.map.len())
3057 }
3058
3059 pub fn total_value_bytes(&self) -> Result<usize, AgentRuntimeError> {
3063 let inner = recover_lock(self.inner.lock(), "WorkingMemory::total_value_bytes");
3064 Ok(inner.map.values().map(|v| v.len()).sum())
3065 }
3066
3067 pub fn max_key_length(&self) -> Result<usize, AgentRuntimeError> {
3071 let inner = recover_lock(self.inner.lock(), "WorkingMemory::max_key_length");
3072 Ok(inner.map.keys().map(|k| k.len()).max().unwrap_or(0))
3073 }
3074
3075 pub fn max_value_length(&self) -> Result<usize, AgentRuntimeError> {
3079 let inner = recover_lock(self.inner.lock(), "WorkingMemory::max_value_length");
3080 Ok(inner.map.values().map(|v| v.len()).max().unwrap_or(0))
3081 }
3082
3083 pub fn min_value_length(&self) -> Result<usize, AgentRuntimeError> {
3087 let inner = recover_lock(self.inner.lock(), "WorkingMemory::min_value_length");
3088 Ok(inner.map.values().map(|v| v.len()).min().unwrap_or(0))
3089 }
3090
3091 pub fn key_count_matching(&self, substring: &str) -> Result<usize, AgentRuntimeError> {
3096 let inner = recover_lock(self.inner.lock(), "WorkingMemory::key_count_matching");
3097 Ok(inner.map.keys().filter(|k| k.contains(substring)).count())
3098 }
3099
3100 pub fn avg_value_length(&self) -> Result<f64, AgentRuntimeError> {
3104 let inner = recover_lock(self.inner.lock(), "WorkingMemory::avg_value_length");
3105 let n = inner.map.len();
3106 if n == 0 {
3107 return Ok(0.0);
3108 }
3109 let total: usize = inner.map.values().map(|v| v.len()).sum();
3110 Ok(total as f64 / n as f64)
3111 }
3112
3113 pub fn count_above_value_length(&self, min_bytes: usize) -> Result<usize, AgentRuntimeError> {
3115 let inner = recover_lock(self.inner.lock(), "WorkingMemory::count_above_value_length");
3116 Ok(inner.map.values().filter(|v| v.len() > min_bytes).count())
3117 }
3118
3119 pub fn longest_key(&self) -> Result<Option<String>, AgentRuntimeError> {
3124 let inner = recover_lock(self.inner.lock(), "WorkingMemory::longest_key");
3125 Ok(inner
3126 .map
3127 .keys()
3128 .max_by_key(|k| k.len())
3129 .map(|k| k.clone()))
3130 }
3131
3132 pub fn longest_value(&self) -> Result<Option<String>, AgentRuntimeError> {
3137 let inner = recover_lock(self.inner.lock(), "WorkingMemory::longest_value");
3138 Ok(inner
3139 .map
3140 .values()
3141 .max_by_key(|v| v.len())
3142 .map(|v| v.clone()))
3143 }
3144
3145 pub fn pairs_starting_with(&self, prefix: &str) -> Result<Vec<(String, String)>, AgentRuntimeError> {
3147 let inner = recover_lock(self.inner.lock(), "WorkingMemory::pairs_starting_with");
3148 let pairs = inner
3149 .map
3150 .iter()
3151 .filter(|(k, _)| k.starts_with(prefix))
3152 .map(|(k, v)| (k.clone(), v.clone()))
3153 .collect();
3154 Ok(pairs)
3155 }
3156
3157 pub fn total_key_bytes(&self) -> Result<usize, AgentRuntimeError> {
3159 let inner = recover_lock(self.inner.lock(), "WorkingMemory::total_key_bytes");
3160 Ok(inner.map.keys().map(|k| k.len()).sum())
3161 }
3162
3163 pub fn min_key_length(&self) -> Result<usize, AgentRuntimeError> {
3165 let inner = recover_lock(self.inner.lock(), "WorkingMemory::min_key_length");
3166 Ok(inner.map.keys().map(|k| k.len()).min().unwrap_or(0))
3167 }
3168
3169 pub fn count_matching_prefix(&self, prefix: &str) -> Result<usize, AgentRuntimeError> {
3171 let inner = recover_lock(self.inner.lock(), "WorkingMemory::count_matching_prefix");
3172 Ok(inner.map.keys().filter(|k| k.starts_with(prefix)).count())
3173 }
3174
3175 pub fn entry_count(&self) -> Result<usize, AgentRuntimeError> {
3178 let inner = recover_lock(self.inner.lock(), "WorkingMemory::entry_count");
3179 Ok(inner.map.len())
3180 }
3181
3182 pub fn value_lengths(&self) -> Result<Vec<(String, usize)>, AgentRuntimeError> {
3184 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_lengths");
3185 Ok(inner.map.iter().map(|(k, v)| (k.clone(), v.len())).collect())
3186 }
3187
3188 pub fn keys_with_value_longer_than(&self, threshold: usize) -> Result<Vec<String>, AgentRuntimeError> {
3191 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys_with_value_longer_than");
3192 Ok(inner
3193 .map
3194 .iter()
3195 .filter(|(_, v)| v.len() > threshold)
3196 .map(|(k, _)| k.clone())
3197 .collect())
3198 }
3199
3200 pub fn longest_value_key(&self) -> Result<Option<String>, AgentRuntimeError> {
3202 let inner = recover_lock(self.inner.lock(), "WorkingMemory::longest_value_key");
3203 Ok(inner
3204 .map
3205 .iter()
3206 .max_by_key(|(_, v)| v.len())
3207 .map(|(k, _)| k.clone()))
3208 }
3209
3210 pub fn retain<F>(&self, mut predicate: F) -> Result<usize, AgentRuntimeError>
3215 where
3216 F: FnMut(&str, &str) -> bool,
3217 {
3218 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::retain");
3219 let before = inner.map.len();
3220 inner.map.retain(|k, v| predicate(k.as_str(), v.as_str()));
3221 let surviving: std::collections::HashSet<String> =
3222 inner.map.keys().cloned().collect();
3223 inner.order.retain(|k| surviving.contains(k));
3224 Ok(before - inner.map.len())
3225 }
3226
3227 pub fn merge_from(&self, other: &WorkingMemory) -> Result<usize, AgentRuntimeError> {
3235 let pairs: Vec<(String, String)> = {
3236 let inner = recover_lock(other.inner.lock(), "WorkingMemory::merge_from(read)");
3237 inner.order.iter().filter_map(|k| {
3238 inner.map.get(k).map(|v| (k.clone(), v.clone()))
3239 }).collect()
3240 };
3241 let count = pairs.len();
3242 self.set_many(pairs)?;
3243 Ok(count)
3244 }
3245
3246 pub fn entry_count_satisfying<F>(&self, mut predicate: F) -> Result<usize, AgentRuntimeError>
3248 where
3249 F: FnMut(&str, &str) -> bool,
3250 {
3251 let inner = recover_lock(self.inner.lock(), "WorkingMemory::entry_count_satisfying");
3252 Ok(inner.map.iter().filter(|(k, v)| predicate(k.as_str(), v.as_str())).count())
3253 }
3254
3255 pub fn get_all_keys(&self) -> Result<Vec<String>, AgentRuntimeError> {
3257 let inner = recover_lock(self.inner.lock(), "WorkingMemory::get_all_keys");
3258 Ok(inner.order.iter().cloned().collect())
3259 }
3260
3261 pub fn replace_all(
3267 &self,
3268 map: std::collections::HashMap<String, String>,
3269 ) -> Result<(), AgentRuntimeError> {
3270 let capacity = self.capacity;
3271 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::replace_all");
3272 inner.map.clear();
3273 inner.order.clear();
3274 for (k, v) in map {
3275 if inner.map.len() >= capacity {
3276 if let Some(oldest) = inner.order.pop_front() {
3277 inner.map.remove(&oldest);
3278 }
3279 }
3280 inner.order.push_back(k.clone());
3281 inner.map.insert(k, v);
3282 }
3283 Ok(())
3284 }
3285}
3286
3287#[cfg(test)]
3290mod tests {
3291 use super::*;
3292
3293 #[test]
3296 fn test_agent_id_new_stores_string() {
3297 let id = AgentId::new("agent-1");
3298 assert_eq!(id.0, "agent-1");
3299 }
3300
3301 #[test]
3302 fn test_agent_id_random_is_unique() {
3303 let a = AgentId::random();
3304 let b = AgentId::random();
3305 assert_ne!(a, b);
3306 }
3307
3308 #[test]
3309 fn test_memory_id_new_stores_string() {
3310 let id = MemoryId::new("mem-1");
3311 assert_eq!(id.0, "mem-1");
3312 }
3313
3314 #[test]
3315 fn test_memory_id_random_is_unique() {
3316 let a = MemoryId::random();
3317 let b = MemoryId::random();
3318 assert_ne!(a, b);
3319 }
3320
3321 #[test]
3324 fn test_memory_item_new_clamps_importance_above_one() {
3325 let item = MemoryItem::new(AgentId::new("a"), "test", 1.5, vec![]);
3326 assert_eq!(item.importance, 1.0);
3327 }
3328
3329 #[test]
3330 fn test_memory_item_new_clamps_importance_below_zero() {
3331 let item = MemoryItem::new(AgentId::new("a"), "test", -0.5, vec![]);
3332 assert_eq!(item.importance, 0.0);
3333 }
3334
3335 #[test]
3336 fn test_memory_item_new_preserves_valid_importance() {
3337 let item = MemoryItem::new(AgentId::new("a"), "test", 0.7, vec![]);
3338 assert!((item.importance - 0.7).abs() < 1e-6);
3339 }
3340
3341 #[test]
3344 fn test_decay_policy_rejects_zero_half_life() {
3345 assert!(DecayPolicy::exponential(0.0).is_err());
3346 }
3347
3348 #[test]
3349 fn test_decay_policy_rejects_negative_half_life() {
3350 assert!(DecayPolicy::exponential(-1.0).is_err());
3351 }
3352
3353 #[test]
3354 fn test_decay_policy_no_decay_at_age_zero() {
3355 let p = DecayPolicy::exponential(24.0).unwrap();
3356 let decayed = p.apply(1.0, 0.0);
3357 assert!((decayed - 1.0).abs() < 1e-5);
3358 }
3359
3360 #[test]
3361 fn test_decay_policy_half_importance_at_half_life() {
3362 let p = DecayPolicy::exponential(24.0).unwrap();
3363 let decayed = p.apply(1.0, 24.0);
3364 assert!((decayed - 0.5).abs() < 1e-5);
3365 }
3366
3367 #[test]
3368 fn test_decay_policy_quarter_importance_at_two_half_lives() {
3369 let p = DecayPolicy::exponential(24.0).unwrap();
3370 let decayed = p.apply(1.0, 48.0);
3371 assert!((decayed - 0.25).abs() < 1e-5);
3372 }
3373
3374 #[test]
3375 fn test_decay_policy_result_is_clamped_to_zero_one() {
3376 let p = DecayPolicy::exponential(1.0).unwrap();
3377 let decayed = p.apply(0.0, 1000.0);
3378 assert!(decayed >= 0.0 && decayed <= 1.0);
3379 }
3380
3381 #[test]
3384 fn test_episodic_store_add_episode_returns_id() {
3385 let store = EpisodicStore::new();
3386 let id = store.add_episode(AgentId::new("a"), "event", 0.8).unwrap();
3387 assert!(!id.0.is_empty());
3388 }
3389
3390 #[test]
3391 fn test_episodic_store_recall_returns_stored_item() {
3392 let store = EpisodicStore::new();
3393 let agent = AgentId::new("agent-1");
3394 store
3395 .add_episode(agent.clone(), "hello world", 0.9)
3396 .unwrap();
3397 let items = store.recall(&agent, 10).unwrap();
3398 assert_eq!(items.len(), 1);
3399 assert_eq!(items[0].content, "hello world");
3400 }
3401
3402 #[test]
3403 fn test_episodic_store_recall_filters_by_agent() {
3404 let store = EpisodicStore::new();
3405 let a = AgentId::new("agent-a");
3406 let b = AgentId::new("agent-b");
3407 store.add_episode(a.clone(), "for a", 0.5).unwrap();
3408 store.add_episode(b.clone(), "for b", 0.5).unwrap();
3409 let items = store.recall(&a, 10).unwrap();
3410 assert_eq!(items.len(), 1);
3411 assert_eq!(items[0].content, "for a");
3412 }
3413
3414 #[test]
3415 fn test_episodic_store_recall_sorted_by_descending_importance() {
3416 let store = EpisodicStore::new();
3417 let agent = AgentId::new("agent-1");
3418 store.add_episode(agent.clone(), "low", 0.1).unwrap();
3419 store.add_episode(agent.clone(), "high", 0.9).unwrap();
3420 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
3421 let items = store.recall(&agent, 10).unwrap();
3422 assert_eq!(items[0].content, "high");
3423 assert_eq!(items[1].content, "mid");
3424 assert_eq!(items[2].content, "low");
3425 }
3426
3427 #[test]
3428 fn test_episodic_store_recall_respects_limit() {
3429 let store = EpisodicStore::new();
3430 let agent = AgentId::new("agent-1");
3431 for i in 0..5 {
3432 store
3433 .add_episode(agent.clone(), format!("item {i}"), 0.5)
3434 .unwrap();
3435 }
3436 let items = store.recall(&agent, 3).unwrap();
3437 assert_eq!(items.len(), 3);
3438 }
3439
3440 #[test]
3441 fn test_episodic_store_len_tracks_insertions() {
3442 let store = EpisodicStore::new();
3443 let agent = AgentId::new("a");
3444 store.add_episode(agent.clone(), "a", 0.5).unwrap();
3445 store.add_episode(agent.clone(), "b", 0.5).unwrap();
3446 assert_eq!(store.len().unwrap(), 2);
3447 }
3448
3449 #[test]
3450 fn test_episodic_store_is_empty_initially() {
3451 let store = EpisodicStore::new();
3452 assert!(store.is_empty().unwrap());
3453 }
3454
3455 #[test]
3456 fn test_episodic_store_with_decay_reduces_importance() {
3457 let policy = DecayPolicy::exponential(0.001).unwrap(); let store = EpisodicStore::with_decay(policy);
3459 let agent = AgentId::new("a");
3460
3461 let old_ts = Utc::now() - chrono::Duration::hours(1);
3463 store
3464 .add_episode_at(agent.clone(), "old event", 1.0, old_ts)
3465 .unwrap();
3466
3467 let items = store.recall(&agent, 10).unwrap();
3468 assert_eq!(items.len(), 1);
3470 assert!(
3471 items[0].importance < 0.01,
3472 "expected near-zero importance, got {}",
3473 items[0].importance
3474 );
3475 }
3476
3477 #[test]
3480 fn test_max_age_rejects_zero() {
3481 assert!(EpisodicStore::with_max_age(0.0).is_err());
3482 }
3483
3484 #[test]
3485 fn test_max_age_rejects_negative() {
3486 assert!(EpisodicStore::with_max_age(-1.0).is_err());
3487 }
3488
3489 #[test]
3490 fn test_max_age_evicts_old_items_on_recall() {
3491 let store = EpisodicStore::with_max_age(0.001).unwrap();
3493 let agent = AgentId::new("a");
3494
3495 let old_ts = Utc::now() - chrono::Duration::hours(1);
3496 store
3497 .add_episode_at(agent.clone(), "old", 0.9, old_ts)
3498 .unwrap();
3499 store.add_episode(agent.clone(), "new", 0.5).unwrap();
3500
3501 let items = store.recall(&agent, 10).unwrap();
3502 assert_eq!(items.len(), 1, "old item should be evicted by max_age");
3503 assert_eq!(items[0].content, "new");
3504 }
3505
3506 #[test]
3507 fn test_max_age_evicts_old_items_on_add() {
3508 let store = EpisodicStore::with_max_age(0.001).unwrap();
3509 let agent = AgentId::new("a");
3510
3511 let old_ts = Utc::now() - chrono::Duration::hours(1);
3512 store
3513 .add_episode_at(agent.clone(), "old", 0.9, old_ts)
3514 .unwrap();
3515 store.add_episode(agent.clone(), "new", 0.5).unwrap();
3517
3518 assert_eq!(store.len().unwrap(), 1);
3519 }
3520
3521 #[test]
3524 fn test_recall_increments_recall_count() {
3525 let store = EpisodicStore::new();
3526 let agent = AgentId::new("agent-rc");
3527 store.add_episode(agent.clone(), "memory", 0.5).unwrap();
3528
3529 let items = store.recall(&agent, 10).unwrap();
3531 assert_eq!(items[0].recall_count, 1);
3532
3533 let items = store.recall(&agent, 10).unwrap();
3535 assert_eq!(items[0].recall_count, 2);
3536 }
3537
3538 #[test]
3539 fn test_hybrid_recall_policy_prefers_recently_used() {
3540 let store = EpisodicStore::with_recall_policy(RecallPolicy::Hybrid {
3541 recency_weight: 0.1,
3542 frequency_weight: 2.0,
3543 });
3544 let agent = AgentId::new("agent-hybrid");
3545
3546 let old_ts = Utc::now() - chrono::Duration::hours(48);
3547 store
3548 .add_episode_at(agent.clone(), "old_frequent", 0.5, old_ts)
3549 .unwrap();
3550 store.add_episode(agent.clone(), "new_never", 0.5).unwrap();
3551
3552 store.bump_recall_count_by_content("old_frequent", 100);
3554
3555 let items = store.recall(&agent, 10).unwrap();
3556 assert_eq!(items.len(), 2);
3557 assert_eq!(
3558 items[0].content, "old_frequent",
3559 "hybrid policy should rank the frequently-recalled item first"
3560 );
3561 }
3562
3563 #[test]
3564 fn test_per_agent_capacity_evicts_lowest_importance() {
3565 let store = EpisodicStore::with_per_agent_capacity(2);
3566 let agent = AgentId::new("agent-cap");
3567
3568 store.add_episode(agent.clone(), "low", 0.1).unwrap();
3570 store.add_episode(agent.clone(), "high", 0.9).unwrap();
3571 store.add_episode(agent.clone(), "new", 0.5).unwrap();
3574
3575 assert_eq!(
3576 store.len().unwrap(),
3577 2,
3578 "store should hold exactly 2 items after eviction"
3579 );
3580
3581 let items = store.recall(&agent, 10).unwrap();
3582 let contents: Vec<&str> = items.iter().map(|i| i.content.as_str()).collect();
3583 assert!(
3584 !contents.contains(&"low"),
3585 "the pre-existing lowest-importance item should have been evicted; remaining: {:?}",
3586 contents
3587 );
3588 assert!(
3589 contents.contains(&"new"),
3590 "the newly added item must never be evicted; remaining: {:?}",
3591 contents
3592 );
3593 }
3594
3595 #[test]
3598 fn test_many_agents_do_not_see_each_others_memories() {
3599 let store = EpisodicStore::new();
3600 let n_agents = 20usize;
3601 for i in 0..n_agents {
3602 let agent = AgentId::new(format!("agent-{i}"));
3603 for j in 0..5 {
3604 store
3605 .add_episode(agent.clone(), format!("item-{i}-{j}"), 0.5)
3606 .unwrap();
3607 }
3608 }
3609 for i in 0..n_agents {
3611 let agent = AgentId::new(format!("agent-{i}"));
3612 let items = store.recall(&agent, 100).unwrap();
3613 assert_eq!(
3614 items.len(),
3615 5,
3616 "agent {i} should see exactly 5 items, got {}",
3617 items.len()
3618 );
3619 for item in &items {
3620 assert!(
3621 item.content.starts_with(&format!("item-{i}-")),
3622 "agent {i} saw foreign item: {}",
3623 item.content
3624 );
3625 }
3626 }
3627 }
3628
3629 #[test]
3632 fn test_agent_memory_count_returns_zero_for_unknown_agent() {
3633 let store = EpisodicStore::new();
3634 let count = store.agent_memory_count(&AgentId::new("ghost")).unwrap();
3635 assert_eq!(count, 0);
3636 }
3637
3638 #[test]
3639 fn test_agent_memory_count_tracks_insertions() {
3640 let store = EpisodicStore::new();
3641 let agent = AgentId::new("a");
3642 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
3643 store.add_episode(agent.clone(), "e2", 0.5).unwrap();
3644 assert_eq!(store.agent_memory_count(&agent).unwrap(), 2);
3645 }
3646
3647 #[test]
3648 fn test_list_agents_returns_all_known_agents() {
3649 let store = EpisodicStore::new();
3650 let a = AgentId::new("agent-a");
3651 let b = AgentId::new("agent-b");
3652 store.add_episode(a.clone(), "x", 0.5).unwrap();
3653 store.add_episode(b.clone(), "y", 0.5).unwrap();
3654 let agents = store.list_agents().unwrap();
3655 assert_eq!(agents.len(), 2);
3656 assert!(agents.contains(&a));
3657 assert!(agents.contains(&b));
3658 }
3659
3660 #[test]
3661 fn test_list_agents_empty_when_no_episodes() {
3662 let store = EpisodicStore::new();
3663 let agents = store.list_agents().unwrap();
3664 assert!(agents.is_empty());
3665 }
3666
3667 #[test]
3668 fn test_purge_agent_memories_removes_all_for_agent() {
3669 let store = EpisodicStore::new();
3670 let a = AgentId::new("a");
3671 let b = AgentId::new("b");
3672 store.add_episode(a.clone(), "ep1", 0.5).unwrap();
3673 store.add_episode(a.clone(), "ep2", 0.5).unwrap();
3674 store.add_episode(b.clone(), "ep-b", 0.5).unwrap();
3675
3676 let removed = store.purge_agent_memories(&a).unwrap();
3677 assert_eq!(removed, 2);
3678 assert_eq!(store.agent_memory_count(&a).unwrap(), 0);
3679 assert_eq!(store.agent_memory_count(&b).unwrap(), 1);
3680 assert_eq!(store.len().unwrap(), 1);
3681 }
3682
3683 #[test]
3684 fn test_purge_agent_memories_returns_zero_for_unknown_agent() {
3685 let store = EpisodicStore::new();
3686 let removed = store.purge_agent_memories(&AgentId::new("ghost")).unwrap();
3687 assert_eq!(removed, 0);
3688 }
3689
3690 #[test]
3693 fn test_recall_returns_empty_when_all_items_are_stale() {
3694 let store = EpisodicStore::with_max_age(0.001).unwrap();
3696 let agent = AgentId::new("stale-agent");
3697
3698 let old_ts = Utc::now() - chrono::Duration::hours(1);
3699 store
3700 .add_episode_at(agent.clone(), "stale-1", 0.9, old_ts)
3701 .unwrap();
3702 store
3703 .add_episode_at(agent.clone(), "stale-2", 0.7, old_ts)
3704 .unwrap();
3705
3706 let items = store.recall(&agent, 100).unwrap();
3707 assert!(
3708 items.is_empty(),
3709 "all stale items should be evicted on recall, got {}",
3710 items.len()
3711 );
3712 }
3713
3714 #[test]
3717 fn test_concurrent_add_and_recall_are_consistent() {
3718 use std::sync::Arc;
3719 use std::thread;
3720
3721 let store = Arc::new(EpisodicStore::new());
3722 let agent = AgentId::new("concurrent-agent");
3723 let n_threads = 8;
3724 let items_per_thread = 25;
3725
3726 let mut handles = Vec::new();
3728 for t in 0..n_threads {
3729 let s = Arc::clone(&store);
3730 let a = agent.clone();
3731 handles.push(thread::spawn(move || {
3732 for i in 0..items_per_thread {
3733 s.add_episode(a.clone(), format!("t{t}-i{i}"), 0.5).unwrap();
3734 }
3735 }));
3736 }
3737 for h in handles {
3738 h.join().unwrap();
3739 }
3740
3741 let mut read_handles = Vec::new();
3743 for _ in 0..n_threads {
3744 let s = Arc::clone(&store);
3745 let a = agent.clone();
3746 read_handles.push(thread::spawn(move || {
3747 let items = s.recall(&a, 1000).unwrap();
3748 assert!(items.len() <= n_threads * items_per_thread);
3749 }));
3750 }
3751 for h in read_handles {
3752 h.join().unwrap();
3753 }
3754 }
3755
3756 #[test]
3759 fn test_concurrent_capacity_eviction_never_exceeds_cap() {
3760 use std::sync::Arc;
3761 use std::thread;
3762
3763 let cap = 5usize;
3764 let store = Arc::new(EpisodicStore::with_per_agent_capacity(cap));
3765 let agent = AgentId::new("cap-agent");
3766 let n_threads = 8;
3767 let items_per_thread = 10;
3768
3769 let mut handles = Vec::new();
3770 for t in 0..n_threads {
3771 let s = Arc::clone(&store);
3772 let a = agent.clone();
3773 handles.push(thread::spawn(move || {
3774 for i in 0..items_per_thread {
3775 let importance = (t * items_per_thread + i) as f32 / 100.0;
3776 s.add_episode(a.clone(), format!("t{t}-i{i}"), importance)
3777 .unwrap();
3778 }
3779 }));
3780 }
3781 for h in handles {
3782 h.join().unwrap();
3783 }
3784
3785 let count = store.agent_memory_count(&agent).unwrap();
3790 assert!(
3791 count <= cap + n_threads,
3792 "expected at most {} items, got {}",
3793 cap + n_threads,
3794 count
3795 );
3796 }
3797
3798 #[test]
3801 fn test_semantic_store_store_and_retrieve_all() {
3802 let store = SemanticStore::new();
3803 store.store("key1", "value1", vec!["tag-a".into()]).unwrap();
3804 store.store("key2", "value2", vec!["tag-b".into()]).unwrap();
3805 let results = store.retrieve(&[]).unwrap();
3806 assert_eq!(results.len(), 2);
3807 }
3808
3809 #[test]
3810 fn test_semantic_store_retrieve_filters_by_tag() {
3811 let store = SemanticStore::new();
3812 store
3813 .store("k1", "v1", vec!["rust".into(), "async".into()])
3814 .unwrap();
3815 store.store("k2", "v2", vec!["rust".into()]).unwrap();
3816 let results = store.retrieve(&["async"]).unwrap();
3817 assert_eq!(results.len(), 1);
3818 assert_eq!(results[0].0, "k1");
3819 }
3820
3821 #[test]
3822 fn test_semantic_store_retrieve_requires_all_tags() {
3823 let store = SemanticStore::new();
3824 store
3825 .store("k1", "v1", vec!["a".into(), "b".into()])
3826 .unwrap();
3827 store.store("k2", "v2", vec!["a".into()]).unwrap();
3828 let results = store.retrieve(&["a", "b"]).unwrap();
3829 assert_eq!(results.len(), 1);
3830 }
3831
3832 #[test]
3833 fn test_semantic_store_is_empty_initially() {
3834 let store = SemanticStore::new();
3835 assert!(store.is_empty().unwrap());
3836 }
3837
3838 #[test]
3839 fn test_semantic_store_len_tracks_insertions() {
3840 let store = SemanticStore::new();
3841 store.store("k", "v", vec![]).unwrap();
3842 assert_eq!(store.len().unwrap(), 1);
3843 }
3844
3845 #[test]
3846 fn test_semantic_store_empty_embedding_is_rejected() {
3847 let store = SemanticStore::new();
3848 let result = store.store_with_embedding("k", "v", vec![], vec![]);
3849 assert!(result.is_err(), "empty embedding should be rejected");
3850 }
3851
3852 #[test]
3853 fn test_semantic_store_dimension_mismatch_is_rejected() {
3854 let store = SemanticStore::new();
3855 store
3856 .store_with_embedding("k1", "v1", vec![], vec![1.0, 0.0])
3857 .unwrap();
3858 let result = store.store_with_embedding("k2", "v2", vec![], vec![1.0, 0.0, 0.0]);
3860 assert!(
3861 result.is_err(),
3862 "embedding dimension mismatch should be rejected"
3863 );
3864 }
3865
3866 #[test]
3867 fn test_semantic_store_retrieve_similar_returns_closest() {
3868 let store = SemanticStore::new();
3869 store
3870 .store_with_embedding("close", "close value", vec![], vec![1.0, 0.0, 0.0])
3871 .unwrap();
3872 store
3873 .store_with_embedding("far", "far value", vec![], vec![0.0, 1.0, 0.0])
3874 .unwrap();
3875
3876 let query = vec![1.0, 0.0, 0.0];
3877 let results = store.retrieve_similar(&query, 2).unwrap();
3878 assert_eq!(results.len(), 2);
3879 assert_eq!(results[0].0, "close");
3880 assert!(
3881 (results[0].2 - 1.0).abs() < 1e-5,
3882 "expected similarity ~1.0, got {}",
3883 results[0].2
3884 );
3885 assert!(
3886 (results[1].2).abs() < 1e-5,
3887 "expected similarity ~0.0, got {}",
3888 results[1].2
3889 );
3890 }
3891
3892 #[test]
3893 fn test_semantic_store_retrieve_similar_ignores_unembedded_entries() {
3894 let store = SemanticStore::new();
3895 store.store("no-emb", "no embedding value", vec![]).unwrap();
3896 store
3897 .store_with_embedding("with-emb", "with embedding value", vec![], vec![1.0, 0.0])
3898 .unwrap();
3899
3900 let query = vec![1.0, 0.0];
3901 let results = store.retrieve_similar(&query, 10).unwrap();
3902 assert_eq!(results.len(), 1, "only the embedded entry should appear");
3903 assert_eq!(results[0].0, "with-emb");
3904 }
3905
3906 #[test]
3907 fn test_cosine_similarity_orthogonal_vectors_return_zero() {
3908 let store = SemanticStore::new();
3909 store
3910 .store_with_embedding("a", "va", vec![], vec![1.0, 0.0])
3911 .unwrap();
3912 store
3913 .store_with_embedding("b", "vb", vec![], vec![0.0, 1.0])
3914 .unwrap();
3915
3916 let query = vec![1.0, 0.0];
3917 let results = store.retrieve_similar(&query, 2).unwrap();
3918 assert_eq!(results.len(), 2);
3919 let b_result = results.iter().find(|(k, _, _)| k == "b").unwrap();
3920 assert!(
3921 b_result.2.abs() < 1e-5,
3922 "expected cosine similarity 0.0 for orthogonal vectors, got {}",
3923 b_result.2
3924 );
3925 }
3926
3927 #[test]
3930 fn test_working_memory_new_rejects_zero_capacity() {
3931 assert!(WorkingMemory::new(0).is_err());
3932 }
3933
3934 #[test]
3935 fn test_working_memory_set_and_get() {
3936 let wm = WorkingMemory::new(10).unwrap();
3937 wm.set("foo", "bar").unwrap();
3938 let val = wm.get("foo").unwrap();
3939 assert_eq!(val, Some("bar".into()));
3940 }
3941
3942 #[test]
3943 fn test_working_memory_get_missing_key_returns_none() {
3944 let wm = WorkingMemory::new(10).unwrap();
3945 assert_eq!(wm.get("missing").unwrap(), None);
3946 }
3947
3948 #[test]
3949 fn test_working_memory_bounded_evicts_oldest() {
3950 let wm = WorkingMemory::new(3).unwrap();
3951 wm.set("k1", "v1").unwrap();
3952 wm.set("k2", "v2").unwrap();
3953 wm.set("k3", "v3").unwrap();
3954 wm.set("k4", "v4").unwrap(); assert_eq!(wm.get("k1").unwrap(), None);
3956 assert_eq!(wm.get("k4").unwrap(), Some("v4".into()));
3957 }
3958
3959 #[test]
3960 fn test_working_memory_update_existing_key_no_eviction() {
3961 let wm = WorkingMemory::new(2).unwrap();
3962 wm.set("k1", "v1").unwrap();
3963 wm.set("k2", "v2").unwrap();
3964 wm.set("k1", "v1-updated").unwrap(); assert_eq!(wm.len().unwrap(), 2);
3966 assert_eq!(wm.get("k1").unwrap(), Some("v1-updated".into()));
3967 assert_eq!(wm.get("k2").unwrap(), Some("v2".into()));
3968 }
3969
3970 #[test]
3971 fn test_working_memory_clear_removes_all() {
3972 let wm = WorkingMemory::new(10).unwrap();
3973 wm.set("a", "1").unwrap();
3974 wm.set("b", "2").unwrap();
3975 wm.clear().unwrap();
3976 assert!(wm.is_empty().unwrap());
3977 }
3978
3979 #[test]
3980 fn test_working_memory_is_empty_initially() {
3981 let wm = WorkingMemory::new(5).unwrap();
3982 assert!(wm.is_empty().unwrap());
3983 }
3984
3985 #[test]
3986 fn test_working_memory_len_tracks_entries() {
3987 let wm = WorkingMemory::new(10).unwrap();
3988 wm.set("a", "1").unwrap();
3989 wm.set("b", "2").unwrap();
3990 assert_eq!(wm.len().unwrap(), 2);
3991 }
3992
3993 #[test]
3994 fn test_working_memory_capacity_never_exceeded() {
3995 let cap = 5usize;
3996 let wm = WorkingMemory::new(cap).unwrap();
3997 for i in 0..20 {
3998 wm.set(format!("key-{i}"), format!("val-{i}")).unwrap();
3999 assert!(wm.len().unwrap() <= cap);
4000 }
4001 }
4002
4003 #[test]
4006 fn test_semantic_dimension_mismatch_on_retrieve_returns_error() {
4007 let store = SemanticStore::new();
4008 store
4009 .store_with_embedding("k1", "v1", vec![], vec![1.0, 0.0, 0.0])
4010 .unwrap();
4011 let result = store.retrieve_similar(&[1.0, 0.0], 10);
4013 assert!(result.is_err(), "dimension mismatch on retrieve should error");
4014 }
4015
4016 #[test]
4021 fn test_clear_agent_memory_removes_all_episodes() {
4022 let store = EpisodicStore::new();
4023 let agent = AgentId::new("a");
4024 store.add_episode(agent.clone(), "ep1", 0.5).unwrap();
4025 store.add_episode(agent.clone(), "ep2", 0.9).unwrap();
4026 store.clear_agent_memory(&agent).unwrap();
4027 let items = store.recall(&agent, 10).unwrap();
4028 assert!(items.is_empty(), "all memories should be cleared");
4029 }
4030
4031 #[test]
4034 fn test_agent_id_as_str() {
4035 let id = AgentId::new("hello");
4036 assert_eq!(id.as_str(), "hello");
4037 }
4038
4039 #[test]
4042 fn test_export_import_agent_memory_round_trip() {
4043 let store = EpisodicStore::new();
4044 let agent = AgentId::new("export-agent");
4045 store.add_episode(agent.clone(), "fact1", 0.8).unwrap();
4046 store.add_episode(agent.clone(), "fact2", 0.6).unwrap();
4047
4048 let exported = store.export_agent_memory(&agent).unwrap();
4049 assert_eq!(exported.len(), 2);
4050
4051 let new_store = EpisodicStore::new();
4052 new_store.import_agent_memory(&agent, exported).unwrap();
4053 let recalled = new_store.recall(&agent, 10).unwrap();
4054 assert_eq!(recalled.len(), 2);
4055 }
4056
4057 #[test]
4060 fn test_working_memory_iter_matches_entries() {
4061 let wm = WorkingMemory::new(10).unwrap();
4062 wm.set("a", "1").unwrap();
4063 wm.set("b", "2").unwrap();
4064 let via_iter = wm.iter().unwrap();
4065 let via_entries = wm.entries().unwrap();
4066 assert_eq!(via_iter, via_entries);
4067 }
4068
4069 #[test]
4072 fn test_agent_id_as_ref_str() {
4073 let id = AgentId::new("ref-test");
4074 let s: &str = id.as_ref();
4075 assert_eq!(s, "ref-test");
4076 }
4077
4078 #[test]
4079 fn test_eviction_policy_oldest_evicts_first_inserted() {
4080 let store = EpisodicStore::with_eviction_policy(EvictionPolicy::Oldest);
4081 let store = {
4087 let inner = EpisodicInner {
4089 items: std::collections::HashMap::new(),
4090 decay: None,
4091 recall_policy: RecallPolicy::Importance,
4092 per_agent_capacity: Some(2),
4093 max_age_hours: None,
4094 eviction_policy: EvictionPolicy::Oldest,
4095 };
4096 EpisodicStore {
4097 inner: std::sync::Arc::new(std::sync::Mutex::new(inner)),
4098 }
4099 };
4100
4101 let agent = AgentId::new("agent");
4102 let t1 = chrono::Utc::now() - chrono::Duration::seconds(100);
4104 let t2 = chrono::Utc::now() - chrono::Duration::seconds(50);
4105 store.add_episode_at(agent.clone(), "oldest", 0.9, t1).unwrap();
4106 store.add_episode_at(agent.clone(), "newer", 0.8, t2).unwrap();
4107 store.add_episode(agent.clone(), "newest", 0.5).unwrap();
4109
4110 let items = store.recall(&agent, 10).unwrap();
4111 assert_eq!(items.len(), 2);
4112 let contents: Vec<&str> = items.iter().map(|i| i.content.as_str()).collect();
4113 assert!(!contents.contains(&"oldest"), "oldest item should have been evicted; got: {contents:?}");
4114 }
4115
4116 #[test]
4119 fn test_search_by_content_finds_matching_episodes() {
4120 let store = EpisodicStore::new();
4121 let agent = AgentId::new("a");
4122 store.add_episode(agent.clone(), "the quick brown fox", 0.9).unwrap();
4123 store.add_episode(agent.clone(), "jumps over the lazy dog", 0.5).unwrap();
4124 store.add_episode(agent.clone(), "hello world", 0.7).unwrap();
4125
4126 let results = store.search_by_content(&agent, "the", 10).unwrap();
4127 assert_eq!(results.len(), 2);
4128 assert_eq!(results[0].content, "the quick brown fox");
4130 }
4131
4132 #[test]
4133 fn test_search_by_content_returns_empty_on_no_match() {
4134 let store = EpisodicStore::new();
4135 let agent = AgentId::new("a");
4136 store.add_episode(agent.clone(), "hello", 0.5).unwrap();
4137 let results = store.search_by_content(&agent, "xyz", 10).unwrap();
4138 assert!(results.is_empty());
4139 }
4140
4141 #[test]
4142 fn test_recall_tagged_filters_by_all_tags() {
4143 let store = EpisodicStore::new();
4144 let agent = AgentId::new("a");
4145 let mut inner = store.inner.lock().unwrap();
4146 let item1 = MemoryItem { id: MemoryId::random(), agent_id: agent.clone(), content: "rust".into(), importance: 0.8, timestamp: Utc::now(), tags: vec!["lang".into(), "sys".into()], recall_count: 0 };
4147 let item2 = MemoryItem { id: MemoryId::random(), agent_id: agent.clone(), content: "python".into(), importance: 0.6, timestamp: Utc::now(), tags: vec!["lang".into()], recall_count: 0 };
4148 inner.items.entry(agent.clone()).or_default().push(item1);
4149 inner.items.entry(agent.clone()).or_default().push(item2);
4150 drop(inner);
4151
4152 let results = store.recall_tagged(&agent, &["lang", "sys"], 10).unwrap();
4153 assert_eq!(results.len(), 1);
4154 assert_eq!(results[0].content, "rust");
4155
4156 let all = store.recall_tagged(&agent, &["lang"], 10).unwrap();
4157 assert_eq!(all.len(), 2);
4158 }
4159
4160 #[test]
4161 fn test_recall_recent_returns_newest_first() {
4162 let store = EpisodicStore::new();
4163 let agent = AgentId::new("a");
4164 store.add_episode(agent.clone(), "first", 0.3).unwrap();
4165 store.add_episode(agent.clone(), "second", 0.5).unwrap();
4166 store.add_episode(agent.clone(), "third", 0.9).unwrap();
4167
4168 let recent = store.recall_recent(&agent, 2).unwrap();
4169 assert_eq!(recent.len(), 2);
4170 assert_eq!(recent[0].content, "third");
4171 assert_eq!(recent[1].content, "second");
4172 }
4173
4174 #[test]
4175 fn test_recall_by_id_finds_specific_episode() {
4176 let store = EpisodicStore::new();
4177 let agent = AgentId::new("a");
4178 let id = store.add_episode(agent.clone(), "specific", 0.7).unwrap();
4179 store.add_episode(agent.clone(), "other", 0.5).unwrap();
4180
4181 let found = store.recall_by_id(&agent, &id).unwrap();
4182 assert!(found.is_some());
4183 assert_eq!(found.unwrap().content, "specific");
4184 }
4185
4186 #[test]
4187 fn test_recall_by_id_returns_none_for_unknown_id() {
4188 let store = EpisodicStore::new();
4189 let agent = AgentId::new("a");
4190 let result = store.recall_by_id(&agent, &MemoryId::random()).unwrap();
4191 assert!(result.is_none());
4192 }
4193
4194 #[test]
4195 fn test_update_importance_changes_score() {
4196 let store = EpisodicStore::new();
4197 let agent = AgentId::new("a");
4198 let id = store.add_episode(agent.clone(), "item", 0.5).unwrap();
4199 let updated = store.update_importance(&agent, &id, 0.9).unwrap();
4200 assert!(updated);
4201 let item = store.recall_by_id(&agent, &id).unwrap().unwrap();
4202 assert!((item.importance - 0.9).abs() < 1e-5);
4203 }
4204
4205 #[test]
4206 fn test_merge_from_imports_episodes() {
4207 let src = EpisodicStore::new();
4208 let agent = AgentId::new("a");
4209 src.add_episode(agent.clone(), "ep1", 0.8).unwrap();
4210 src.add_episode(agent.clone(), "ep2", 0.6).unwrap();
4211
4212 let dst = EpisodicStore::new();
4213 let count = dst.merge_from(&src, &agent).unwrap();
4214 assert_eq!(count, 2);
4215 assert_eq!(dst.agent_memory_count(&agent).unwrap(), 2);
4216 }
4217
4218 #[test]
4219 fn test_memory_item_age_hours_is_non_negative() {
4220 let item = MemoryItem::new(AgentId::new("a"), "test", 0.5, vec![]);
4221 let age = item.age_hours();
4222 assert!(age >= 0.0);
4223 }
4224
4225 #[test]
4226 fn test_working_memory_remove_and_contains() {
4227 let wm = WorkingMemory::new(10).unwrap();
4228 wm.set("k", "v").unwrap();
4229 assert!(wm.contains("k").unwrap());
4230 let removed = wm.remove("k").unwrap();
4231 assert!(removed);
4232 assert!(!wm.contains("k").unwrap());
4233 assert!(!wm.remove("k").unwrap()); }
4235
4236 #[test]
4237 fn test_working_memory_keys_in_insertion_order() {
4238 let wm = WorkingMemory::new(10).unwrap();
4239 wm.set("b", "2").unwrap();
4240 wm.set("a", "1").unwrap();
4241 wm.set("c", "3").unwrap();
4242 assert_eq!(wm.keys().unwrap(), vec!["b", "a", "c"]);
4243 }
4244
4245 #[test]
4246 fn test_working_memory_set_many_batch_insert() {
4247 let wm = WorkingMemory::new(10).unwrap();
4248 wm.set_many([("x", "1"), ("y", "2"), ("z", "3")]).unwrap();
4249 assert_eq!(wm.len().unwrap(), 3);
4250 assert_eq!(wm.get("y").unwrap(), Some("2".into()));
4251 }
4252
4253 #[test]
4254 fn test_working_memory_get_or_default() {
4255 let wm = WorkingMemory::new(5).unwrap();
4256 wm.set("a", "val").unwrap();
4257 assert_eq!(wm.get_or_default("a", "fallback").unwrap(), "val");
4258 assert_eq!(wm.get_or_default("missing", "fallback").unwrap(), "fallback");
4259 }
4260
4261 #[test]
4262 fn test_semantic_store_remove_deletes_entry() {
4263 let store = SemanticStore::new();
4264 store.store("k", "v", vec![]).unwrap();
4265 assert_eq!(store.len().unwrap(), 1);
4266 let removed = store.remove("k").unwrap();
4267 assert!(removed);
4268 assert_eq!(store.len().unwrap(), 0);
4269 }
4270
4271 #[test]
4272 fn test_semantic_store_clear_empties_store() {
4273 let store = SemanticStore::new();
4274 store.store("a", "1", vec!["t".into()]).unwrap();
4275 store.store("b", "2", vec!["t".into()]).unwrap();
4276 store.clear().unwrap();
4277 assert!(store.is_empty().unwrap());
4278 }
4279
4280 #[test]
4281 fn test_semantic_store_update_changes_value() {
4282 let store = SemanticStore::new();
4283 store.store("k", "old", vec![]).unwrap();
4284 let updated = store.update("k", "new").unwrap();
4285 assert!(updated);
4286 let (val, _) = store.retrieve_by_key("k").unwrap().unwrap();
4287 assert_eq!(val, "new");
4288 }
4289
4290 #[test]
4291 fn test_semantic_store_retrieve_by_key() {
4292 let store = SemanticStore::new();
4293 store.store("key", "value", vec!["tag1".into()]).unwrap();
4294 let result = store.retrieve_by_key("key").unwrap().unwrap();
4295 assert_eq!(result.0, "value");
4296 assert_eq!(result.1, vec!["tag1".to_string()]);
4297 assert!(store.retrieve_by_key("missing").unwrap().is_none());
4298 }
4299
4300 #[test]
4301 fn test_semantic_store_list_tags() {
4302 let store = SemanticStore::new();
4303 store.store("a", "1", vec!["rust".into(), "sys".into()]).unwrap();
4304 store.store("b", "2", vec!["rust".into(), "ml".into()]).unwrap();
4305 let tags = store.list_tags().unwrap();
4306 assert_eq!(tags, vec!["ml", "rust", "sys"]); }
4308
4309 #[test]
4310 fn test_semantic_store_count_by_tag() {
4311 let store = SemanticStore::new();
4312 store.store("a", "1", vec!["rust".into(), "sys".into()]).unwrap();
4313 store.store("b", "2", vec!["rust".into(), "ml".into()]).unwrap();
4314 store.store("c", "3", vec!["ml".into()]).unwrap();
4315 assert_eq!(store.count_by_tag("rust").unwrap(), 2);
4316 assert_eq!(store.count_by_tag("ml").unwrap(), 2);
4317 assert_eq!(store.count_by_tag("sys").unwrap(), 1);
4318 assert_eq!(store.count_by_tag("absent").unwrap(), 0);
4319 }
4320
4321 #[test]
4322 fn test_working_memory_get_many_returns_present_values() {
4323 let wm = WorkingMemory::new(10).unwrap();
4324 wm.set("a", "alpha".to_string()).unwrap();
4325 wm.set("b", "beta".to_string()).unwrap();
4326 let results = wm.get_many(&["a", "b", "c"]).unwrap();
4328 assert_eq!(results[0].as_deref(), Some("alpha"));
4329 assert_eq!(results[1].as_deref(), Some("beta"));
4330 assert_eq!(results[2], None);
4331 }
4332
4333 #[test]
4334 fn test_working_memory_update_if_exists_changes_value() {
4335 let wm = WorkingMemory::new(5).unwrap();
4336 wm.set("x", "old".to_string()).unwrap();
4337 assert!(wm.update_if_exists("x", "new".to_string()).unwrap());
4338 assert_eq!(wm.get("x").unwrap().as_deref(), Some("new"));
4339 }
4340
4341 #[test]
4342 fn test_working_memory_update_if_exists_returns_false_for_missing() {
4343 let wm = WorkingMemory::new(5).unwrap();
4344 assert!(!wm.update_if_exists("nope", "val".to_string()).unwrap());
4345 }
4346
4347 #[test]
4348 fn test_episodic_total_recall_count_sums_all_accesses() {
4349 let store = EpisodicStore::new();
4350 let agent = AgentId::new("agent1");
4351 store.add_episode(agent.clone(), "first", 0.8).unwrap();
4352 store.add_episode(agent.clone(), "second", 0.5).unwrap();
4353 store.recall(&agent, 10).unwrap();
4355 store.recall(&agent, 10).unwrap();
4356 let total = store.total_recall_count(&agent).unwrap();
4357 assert_eq!(total, 4);
4359 }
4360
4361 #[test]
4362 fn test_episodic_total_recall_count_zero_before_recall() {
4363 let store = EpisodicStore::new();
4364 let agent = AgentId::new("fresh");
4365 store.add_episode(agent.clone(), "ep", 0.5).unwrap();
4366 assert_eq!(store.total_recall_count(&agent).unwrap(), 0);
4367 }
4368
4369 #[test]
4370 fn test_episodic_recall_all_returns_all_items() {
4371 let store = EpisodicStore::new();
4372 let agent = AgentId::new("agent-all");
4373 store.add_episode(agent.clone(), "one", 0.9).unwrap();
4374 store.add_episode(agent.clone(), "two", 0.5).unwrap();
4375 store.add_episode(agent.clone(), "three", 0.1).unwrap();
4376 let all = store.recall_all(&agent).unwrap();
4377 assert_eq!(all.len(), 3);
4378 }
4379
4380 #[test]
4381 fn test_episodic_recall_all_empty_agent_returns_empty() {
4382 let store = EpisodicStore::new();
4383 let agent = AgentId::new("nobody");
4384 let all = store.recall_all(&agent).unwrap();
4385 assert!(all.is_empty());
4386 }
4387
4388 #[test]
4389 fn test_episodic_clear_all_removes_all_agents() {
4390 let store = EpisodicStore::new();
4391 let a1 = AgentId::new("a1");
4392 let a2 = AgentId::new("a2");
4393 store.add_episode(a1.clone(), "ep", 0.5).unwrap();
4394 store.add_episode(a2.clone(), "ep", 0.5).unwrap();
4395 store.clear_all().unwrap();
4396 assert_eq!(store.len().unwrap(), 0);
4397 assert!(store.list_agents().unwrap().is_empty());
4398 }
4399
4400 #[test]
4401 fn test_working_memory_drain_returns_all_and_empties() {
4402 let wm = WorkingMemory::new(10).unwrap();
4403 wm.set("x", "1".to_string()).unwrap();
4404 wm.set("y", "2".to_string()).unwrap();
4405 let drained = wm.drain().unwrap();
4406 assert_eq!(drained.len(), 2);
4407 assert!(wm.is_empty().unwrap());
4408 }
4409
4410 #[test]
4411 fn test_working_memory_drain_preserves_insertion_order() {
4412 let wm = WorkingMemory::new(10).unwrap();
4413 wm.set("a", "1".to_string()).unwrap();
4414 wm.set("b", "2".to_string()).unwrap();
4415 wm.set("c", "3".to_string()).unwrap();
4416 let drained = wm.drain().unwrap();
4417 let keys: Vec<&str> = drained.iter().map(|(k, _)| k.as_str()).collect();
4418 assert_eq!(keys, vec!["a", "b", "c"]);
4419 }
4420
4421 #[test]
4422 fn test_semantic_store_tag_count() {
4423 let store = SemanticStore::new();
4424 store.store("a", "1", vec!["rust".into(), "sys".into()]).unwrap();
4425 store.store("b", "2", vec!["rust".into(), "ml".into()]).unwrap();
4426 assert_eq!(store.tag_count().unwrap(), 3); }
4428
4429 #[test]
4430 fn test_semantic_store_tag_count_empty() {
4431 let store = SemanticStore::new();
4432 assert_eq!(store.tag_count().unwrap(), 0);
4433 }
4434
4435 #[test]
4436 fn test_episodic_top_n_returns_highest_importance() {
4437 let store = EpisodicStore::new();
4438 let agent = AgentId::new("ag");
4439 store.add_episode(agent.clone(), "high", 0.9).unwrap();
4440 store.add_episode(agent.clone(), "med", 0.5).unwrap();
4441 store.add_episode(agent.clone(), "low", 0.1).unwrap();
4442 let top = store.top_n(&agent, 2).unwrap();
4443 assert_eq!(top.len(), 2);
4444 assert_eq!(top[0].content, "high");
4445 assert_eq!(top[1].content, "med");
4446 }
4447
4448 #[test]
4449 fn test_episodic_top_n_zero_returns_all() {
4450 let store = EpisodicStore::new();
4451 let agent = AgentId::new("ag");
4452 store.add_episode(agent.clone(), "a", 0.9).unwrap();
4453 store.add_episode(agent.clone(), "b", 0.5).unwrap();
4454 assert_eq!(store.top_n(&agent, 0).unwrap().len(), 2);
4455 }
4456
4457 #[test]
4458 fn test_episodic_search_by_importance_range() {
4459 let store = EpisodicStore::new();
4460 let agent = AgentId::new("ag");
4461 store.add_episode(agent.clone(), "high", 0.9).unwrap();
4462 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
4463 store.add_episode(agent.clone(), "low", 0.1).unwrap();
4464 let results = store.search_by_importance_range(&agent, 0.4, 1.0, 0).unwrap();
4465 assert_eq!(results.len(), 2);
4466 assert_eq!(results[0].content, "high");
4467 }
4468
4469 #[test]
4470 fn test_semantic_store_contains_true_for_stored_key() {
4471 let store = SemanticStore::new();
4472 store.store("k", "v", vec![]).unwrap();
4473 assert!(store.contains("k").unwrap());
4474 }
4475
4476 #[test]
4477 fn test_semantic_store_contains_false_for_missing_key() {
4478 let store = SemanticStore::new();
4479 assert!(!store.contains("missing").unwrap());
4480 }
4481
4482 #[test]
4483 fn test_working_memory_rename_changes_key() {
4484 let wm = WorkingMemory::new(10).unwrap();
4485 wm.set("old", "value".to_string()).unwrap();
4486 assert!(wm.rename("old", "new").unwrap());
4487 assert_eq!(wm.get("new").unwrap().as_deref(), Some("value"));
4488 assert!(wm.get("old").unwrap().is_none());
4489 }
4490
4491 #[test]
4492 fn test_working_memory_rename_preserves_order() {
4493 let wm = WorkingMemory::new(10).unwrap();
4494 wm.set("a", "1".to_string()).unwrap();
4495 wm.set("b", "2".to_string()).unwrap();
4496 wm.rename("a", "x").unwrap();
4497 assert_eq!(wm.keys().unwrap(), vec!["x", "b"]);
4498 }
4499
4500 #[test]
4501 fn test_working_memory_rename_missing_returns_false() {
4502 let wm = WorkingMemory::new(10).unwrap();
4503 assert!(!wm.rename("nope", "other").unwrap());
4504 }
4505
4506 #[test]
4507 fn test_episodic_importance_stats_basic() {
4508 let store = EpisodicStore::new();
4509 let agent = AgentId::new("ag");
4510 store.add_episode(agent.clone(), "a", 0.2).unwrap();
4511 store.add_episode(agent.clone(), "b", 0.6).unwrap();
4512 store.add_episode(agent.clone(), "c", 1.0).unwrap();
4513 let (count, min, max, mean) = store.importance_stats(&agent).unwrap();
4514 assert_eq!(count, 3);
4515 assert!((min - 0.2).abs() < 1e-5);
4516 assert!((max - 1.0).abs() < 1e-5);
4517 assert!((mean - 0.6).abs() < 1e-4);
4518 }
4519
4520 #[test]
4521 fn test_episodic_importance_stats_empty_agent() {
4522 let store = EpisodicStore::new();
4523 let agent = AgentId::new("nobody");
4524 let (count, min, max, mean) = store.importance_stats(&agent).unwrap();
4525 assert_eq!(count, 0);
4526 assert_eq!(min, 0.0);
4527 assert_eq!(max, 0.0);
4528 assert_eq!(mean, 0.0);
4529 }
4530
4531 #[test]
4532 fn test_semantic_store_keys_returns_stored_keys() {
4533 let store = SemanticStore::new();
4534 store.store("alpha", "v1", vec![]).unwrap();
4535 store.store("beta", "v2", vec![]).unwrap();
4536 let keys = store.keys().unwrap();
4537 assert_eq!(keys.len(), 2);
4538 assert!(keys.contains(&"alpha".to_string()));
4539 assert!(keys.contains(&"beta".to_string()));
4540 }
4541
4542 #[test]
4543 fn test_working_memory_snapshot_clones_contents() {
4544 let wm = WorkingMemory::new(10).unwrap();
4545 wm.set("x", "1".to_string()).unwrap();
4546 wm.set("y", "2".to_string()).unwrap();
4547 let snap = wm.snapshot().unwrap();
4548 assert_eq!(snap.get("x").map(|s| s.as_str()), Some("1"));
4549 assert_eq!(snap.get("y").map(|s| s.as_str()), Some("2"));
4550 assert_eq!(wm.len().unwrap(), 2);
4552 }
4553
4554 #[test]
4557 fn test_memory_item_display_includes_content() {
4558 let agent = AgentId::new("a");
4559 let item = MemoryItem::new(agent, "hello world", 0.75, vec![]);
4560 let s = format!("{item}");
4561 assert!(s.contains("hello world"), "Display should include content");
4562 assert!(s.contains("0.75"), "Display should include importance");
4563 }
4564
4565 #[test]
4566 fn test_decay_policy_half_life_hours_accessor() {
4567 let policy = DecayPolicy::exponential(12.5).unwrap();
4568 assert!((policy.half_life_hours() - 12.5).abs() < f64::EPSILON);
4569 }
4570
4571 #[test]
4572 fn test_semantic_store_list_keys_returns_all_keys() {
4573 let store = SemanticStore::new();
4574 store.store("k1", "v1", vec![]).unwrap();
4575 store.store("k2", "v2", vec![]).unwrap();
4576 let keys = store.list_keys().unwrap();
4577 assert_eq!(keys.len(), 2);
4578 assert!(keys.contains(&"k1".to_string()));
4579 assert!(keys.contains(&"k2".to_string()));
4580 }
4581
4582 #[test]
4583 fn test_semantic_store_update_tags_returns_true_on_found() {
4584 let store = SemanticStore::new();
4585 store.store("k", "v", vec!["old".into()]).unwrap();
4586 let updated = store.update_tags("k", vec!["new".into()]).unwrap();
4587 assert!(updated);
4588 let (_, tags) = store.retrieve_by_key("k").unwrap().unwrap();
4589 assert_eq!(tags, vec!["new".to_string()]);
4590 }
4591
4592 #[test]
4593 fn test_semantic_store_update_tags_returns_false_on_missing() {
4594 let store = SemanticStore::new();
4595 assert!(!store.update_tags("missing", vec![]).unwrap());
4596 }
4597
4598 #[test]
4599 fn test_episodic_store_recall_since_filters_old() {
4600 let store = EpisodicStore::new();
4601 let agent = AgentId::new("a");
4602 let old_ts = Utc::now() - chrono::Duration::hours(2);
4603 store.add_episode_at(agent.clone(), "old", 0.9, old_ts).unwrap();
4604 store.add_episode(agent.clone(), "new", 0.5).unwrap();
4605
4606 let cutoff = Utc::now() - chrono::Duration::minutes(30);
4607 let items = store.recall_since(&agent, cutoff, 0).unwrap();
4608 assert_eq!(items.len(), 1);
4609 assert_eq!(items[0].content, "new");
4610 }
4611
4612 #[test]
4613 fn test_episodic_store_recall_since_limit_respected() {
4614 let store = EpisodicStore::new();
4615 let agent = AgentId::new("a");
4616 for i in 0..5 {
4617 store.add_episode(agent.clone(), format!("item {i}"), 0.5).unwrap();
4618 }
4619 let cutoff = Utc::now() - chrono::Duration::hours(1);
4620 let items = store.recall_since(&agent, cutoff, 2).unwrap();
4621 assert_eq!(items.len(), 2);
4622 }
4623
4624 #[test]
4625 fn test_episodic_store_update_content_returns_true_on_found() {
4626 let store = EpisodicStore::new();
4627 let agent = AgentId::new("a");
4628 let id = store.add_episode(agent.clone(), "original", 0.5).unwrap();
4629 let updated = store.update_content(&agent, &id, "updated").unwrap();
4630 assert!(updated);
4631 let item = store.recall_by_id(&agent, &id).unwrap().unwrap();
4632 assert_eq!(item.content, "updated");
4633 }
4634
4635 #[test]
4636 fn test_episodic_store_update_content_returns_false_on_missing() {
4637 let store = EpisodicStore::new();
4638 let agent = AgentId::new("a");
4639 let fake_id = MemoryId::new("does-not-exist");
4640 assert!(!store.update_content(&agent, &fake_id, "x").unwrap());
4641 }
4642
4643 #[test]
4644 fn test_working_memory_capacity_matches_constructor() {
4645 let wm = WorkingMemory::new(7).unwrap();
4646 assert_eq!(wm.capacity(), 7);
4647 }
4648
4649 #[test]
4652 fn test_add_episode_with_tags_stores_and_recalls() {
4653 let store = EpisodicStore::new();
4654 let agent = AgentId::new("a");
4655 let id = store
4656 .add_episode_with_tags(agent.clone(), "tagged", 0.8, vec!["t1".to_string(), "t2".to_string()])
4657 .unwrap();
4658 let item = store.recall_by_id(&agent, &id).unwrap().unwrap();
4659 assert_eq!(item.content, "tagged");
4660 assert_eq!(item.tags, vec!["t1", "t2"]);
4661 }
4662
4663 #[test]
4664 fn test_remove_by_id_deletes_episode() {
4665 let store = EpisodicStore::new();
4666 let agent = AgentId::new("a");
4667 let id = store.add_episode(agent.clone(), "to-delete", 0.5).unwrap();
4668 assert!(store.remove_by_id(&agent, &id).unwrap());
4669 assert!(store.recall_by_id(&agent, &id).unwrap().is_none());
4670 }
4671
4672 #[test]
4673 fn test_remove_by_id_returns_false_for_missing() {
4674 let store = EpisodicStore::new();
4675 let agent = AgentId::new("a");
4676 let fake = MemoryId::new("does-not-exist");
4677 assert!(!store.remove_by_id(&agent, &fake).unwrap());
4678 }
4679
4680 #[test]
4681 fn test_update_tags_by_id_replaces_tags() {
4682 let store = EpisodicStore::new();
4683 let agent = AgentId::new("a");
4684 let id = store
4685 .add_episode_with_tags(agent.clone(), "x", 0.5, vec!["old".to_string()])
4686 .unwrap();
4687 let updated = store
4688 .update_tags_by_id(&agent, &id, vec!["new1".to_string(), "new2".to_string()])
4689 .unwrap();
4690 assert!(updated);
4691 let item = store.recall_by_id(&agent, &id).unwrap().unwrap();
4692 assert_eq!(item.tags, vec!["new1", "new2"]);
4693 }
4694
4695 #[test]
4696 fn test_update_tags_by_id_returns_false_for_missing() {
4697 let store = EpisodicStore::new();
4698 let agent = AgentId::new("a");
4699 let fake = MemoryId::new("nope");
4700 assert!(!store.update_tags_by_id(&agent, &fake, vec![]).unwrap());
4701 }
4702
4703 #[test]
4704 fn test_max_importance_for_returns_highest() {
4705 let store = EpisodicStore::new();
4706 let agent = AgentId::new("a");
4707 store.add_episode(agent.clone(), "low", 0.2).unwrap();
4708 store.add_episode(agent.clone(), "high", 0.9).unwrap();
4709 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
4710 let max = store.max_importance_for(&agent).unwrap().unwrap();
4711 assert!((max - 0.9).abs() < 1e-6);
4712 }
4713
4714 #[test]
4715 fn test_max_importance_for_returns_none_when_empty() {
4716 let store = EpisodicStore::new();
4717 let agent = AgentId::new("empty");
4718 assert!(store.max_importance_for(&agent).unwrap().is_none());
4719 }
4720
4721 #[test]
4724 fn test_working_memory_snapshot_returns_all_entries() {
4725 let wm = WorkingMemory::new(10).unwrap();
4726 wm.set("k1", "v1").unwrap();
4727 wm.set("k2", "v2").unwrap();
4728 let snap = wm.snapshot().unwrap();
4729 assert_eq!(snap.len(), 2);
4730 assert_eq!(snap.get("k1").unwrap(), "v1");
4731 assert_eq!(snap.get("k2").unwrap(), "v2");
4732 }
4733
4734 #[test]
4735 fn test_working_memory_pop_oldest_removes_first_inserted() {
4736 let wm = WorkingMemory::new(10).unwrap();
4737 wm.set("first", "1").unwrap();
4738 wm.set("second", "2").unwrap();
4739 let popped = wm.pop_oldest().unwrap().unwrap();
4740 assert_eq!(popped.0, "first");
4741 assert_eq!(popped.1, "1");
4742 assert_eq!(wm.len().unwrap(), 1);
4743 }
4744
4745 #[test]
4746 fn test_working_memory_pop_oldest_returns_none_when_empty() {
4747 let wm = WorkingMemory::new(5).unwrap();
4748 assert!(wm.pop_oldest().unwrap().is_none());
4749 }
4750
4751 #[test]
4754 fn test_semantic_store_keys_with_tag_returns_matching() {
4755 let store = SemanticStore::new();
4756 store
4757 .store("doc1", "content one", vec!["alpha".to_string(), "beta".to_string()])
4758 .unwrap();
4759 store
4760 .store("doc2", "content two", vec!["alpha".to_string()])
4761 .unwrap();
4762 store
4763 .store("doc3", "content three", vec!["gamma".to_string()])
4764 .unwrap();
4765 let mut keys = store.keys_with_tag("alpha").unwrap();
4766 keys.sort();
4767 assert_eq!(keys, vec!["doc1", "doc2"]);
4768 }
4769
4770 #[test]
4771 fn test_semantic_store_keys_with_tag_empty_when_no_match() {
4772 let store = SemanticStore::new();
4773 store
4774 .store("doc1", "content", vec!["x".to_string()])
4775 .unwrap();
4776 let keys = store.keys_with_tag("z").unwrap();
4777 assert!(keys.is_empty());
4778 }
4779
4780 #[test]
4783 fn test_episodic_oldest_returns_first_inserted() {
4784 let store = EpisodicStore::new();
4785 let agent = AgentId::new("agent-oldest");
4786 store.add_episode(agent.clone(), "first episode", 0.5).unwrap();
4787 store.add_episode(agent.clone(), "second episode", 0.9).unwrap();
4788 let oldest = store.oldest(&agent).unwrap().unwrap();
4789 assert_eq!(oldest.content, "first episode");
4790 }
4791
4792 #[test]
4793 fn test_episodic_oldest_none_when_no_episodes() {
4794 let store = EpisodicStore::new();
4795 let agent = AgentId::new("no-episodes");
4796 assert!(store.oldest(&agent).unwrap().is_none());
4797 }
4798
4799 #[test]
4800 fn test_working_memory_get_or_default_returns_value_when_present() {
4801 let wm = WorkingMemory::new(10).unwrap();
4802 wm.set("key", "value").unwrap();
4803 assert_eq!(wm.get_or_default("key", "fallback").unwrap(), "value");
4804 }
4805
4806 #[test]
4807 fn test_working_memory_get_or_default_returns_default_when_absent() {
4808 let wm = WorkingMemory::new(10).unwrap();
4809 assert_eq!(wm.get_or_default("missing", "fallback").unwrap(), "fallback");
4810 }
4811
4812 #[test]
4815 fn test_episodic_count_for_returns_correct_count() {
4816 let store = EpisodicStore::new();
4817 let agent = AgentId::new("a");
4818 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
4819 store.add_episode(agent.clone(), "e2", 0.5).unwrap();
4820 assert_eq!(store.count_for(&agent).unwrap(), 2);
4821 }
4822
4823 #[test]
4824 fn test_episodic_count_for_returns_zero_for_unknown_agent() {
4825 let store = EpisodicStore::new();
4826 let agent = AgentId::new("unknown");
4827 assert_eq!(store.count_for(&agent).unwrap(), 0);
4828 }
4829
4830 #[test]
4831 fn test_recall_by_tag_returns_matching_sorted_by_importance() {
4832 let store = EpisodicStore::new();
4833 let agent = AgentId::new("a");
4834 store
4835 .add_episode_with_tags(agent.clone(), "low", 0.1, vec!["news".to_string()])
4836 .unwrap();
4837 store
4838 .add_episode_with_tags(agent.clone(), "high", 0.9, vec!["news".to_string()])
4839 .unwrap();
4840 store
4841 .add_episode_with_tags(agent.clone(), "other", 0.8, vec!["other".to_string()])
4842 .unwrap();
4843 let items = store.recall_by_tag(&agent, "news", 0).unwrap();
4844 assert_eq!(items.len(), 2);
4845 assert_eq!(items[0].content, "high");
4846 }
4847
4848 #[test]
4849 fn test_recall_by_tag_respects_limit() {
4850 let store = EpisodicStore::new();
4851 let agent = AgentId::new("a");
4852 for i in 0..5 {
4853 store
4854 .add_episode_with_tags(agent.clone(), format!("ep{i}"), 0.5, vec!["t".to_string()])
4855 .unwrap();
4856 }
4857 let items = store.recall_by_tag(&agent, "t", 2).unwrap();
4858 assert_eq!(items.len(), 2);
4859 }
4860
4861 #[test]
4862 fn test_merge_from_copies_episodes() {
4863 let src = EpisodicStore::new();
4864 let dst = EpisodicStore::new();
4865 let agent = AgentId::new("a");
4866 src.add_episode(agent.clone(), "ep1", 0.5).unwrap();
4867 src.add_episode(agent.clone(), "ep2", 0.9).unwrap();
4868 let count = dst.merge_from(&src, &agent).unwrap();
4869 assert_eq!(count, 2);
4870 assert_eq!(dst.count_for(&agent).unwrap(), 2);
4871 }
4872
4873 #[test]
4876 fn test_working_memory_get_all_keys_preserves_insertion_order() {
4877 let wm = WorkingMemory::new(10).unwrap();
4878 wm.set("first", "1").unwrap();
4879 wm.set("second", "2").unwrap();
4880 wm.set("third", "3").unwrap();
4881 assert_eq!(wm.get_all_keys().unwrap(), vec!["first", "second", "third"]);
4882 }
4883
4884 #[test]
4885 fn test_working_memory_replace_all_replaces_contents() {
4886 let wm = WorkingMemory::new(10).unwrap();
4887 wm.set("old", "x").unwrap();
4888 let mut new_map = std::collections::HashMap::new();
4889 new_map.insert("a".to_string(), "1".to_string());
4890 new_map.insert("b".to_string(), "2".to_string());
4891 wm.replace_all(new_map).unwrap();
4892 assert_eq!(wm.len().unwrap(), 2);
4893 assert!(wm.get("old").unwrap().is_none());
4894 assert_eq!(wm.get("a").unwrap().unwrap(), "1");
4895 }
4896
4897 #[test]
4900 fn test_semantic_store_remove_returns_true_and_removes() {
4901 let store = SemanticStore::new();
4902 store.store("k1", "v1", vec![]).unwrap();
4903 store.store("k2", "v2", vec![]).unwrap();
4904 assert!(store.remove("k1").unwrap());
4905 assert_eq!(store.count().unwrap(), 1);
4906 let keys = store.list_keys().unwrap();
4907 assert_eq!(keys, vec!["k2"]);
4908 }
4909
4910 #[test]
4911 fn test_semantic_store_remove_returns_false_when_missing() {
4912 let store = SemanticStore::new();
4913 assert!(!store.remove("ghost").unwrap());
4914 }
4915
4916 #[test]
4917 fn test_semantic_store_count_matches_len() {
4918 let store = SemanticStore::new();
4919 store.store("a", "1", vec![]).unwrap();
4920 store.store("b", "2", vec![]).unwrap();
4921 assert_eq!(store.count().unwrap(), store.len().unwrap());
4922 }
4923
4924 #[test]
4927 fn test_episodic_newest_returns_last_inserted() {
4928 let store = EpisodicStore::new();
4929 let agent = AgentId::new("agent-newest");
4930 store.add_episode(agent.clone(), "first", 0.3).unwrap();
4931 store.add_episode(agent.clone(), "last", 0.8).unwrap();
4932 let newest = store.newest(&agent).unwrap().unwrap();
4933 assert_eq!(newest.content, "last");
4934 }
4935
4936 #[test]
4937 fn test_episodic_newest_none_when_empty() {
4938 let store = EpisodicStore::new();
4939 let agent = AgentId::new("no-ep");
4940 assert!(store.newest(&agent).unwrap().is_none());
4941 }
4942
4943 #[test]
4944 fn test_working_memory_values_returns_values_in_insertion_order() {
4945 let wm = WorkingMemory::new(10).unwrap();
4946 wm.set("a", "apple").unwrap();
4947 wm.set("b", "banana").unwrap();
4948 let vals = wm.values().unwrap();
4949 assert_eq!(vals, vec!["apple", "banana"]);
4950 }
4951
4952 #[test]
4953 fn test_working_memory_values_empty_when_no_entries() {
4954 let wm = WorkingMemory::new(10).unwrap();
4955 assert!(wm.values().unwrap().is_empty());
4956 }
4957
4958 #[test]
4961 fn test_episodic_clear_agent_removes_all_episodes() {
4962 let store = EpisodicStore::new();
4963 let agent = AgentId::new("agent-clear");
4964 store.add_episode(agent.clone(), "ep1", 0.5).unwrap();
4965 store.add_episode(agent.clone(), "ep2", 0.7).unwrap();
4966 let removed = store.clear_agent(&agent).unwrap();
4967 assert_eq!(removed, 2);
4968 assert_eq!(store.agent_memory_count(&agent).unwrap(), 0);
4969 }
4970
4971 #[test]
4972 fn test_episodic_clear_agent_returns_zero_when_none() {
4973 let store = EpisodicStore::new();
4974 let agent = AgentId::new("no-agent");
4975 assert_eq!(store.clear_agent(&agent).unwrap(), 0);
4976 }
4977
4978 #[test]
4981 fn test_episodic_max_importance_episode_returns_highest() {
4982 let store = EpisodicStore::new();
4983 let agent = AgentId::new("agent-max");
4984 store.add_episode(agent.clone(), "low", 0.2).unwrap();
4985 store.add_episode(agent.clone(), "high", 0.9).unwrap();
4986 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
4987 let top = store.max_importance_episode(&agent).unwrap().unwrap();
4988 assert_eq!(top.content, "high");
4989 }
4990
4991 #[test]
4992 fn test_episodic_max_importance_episode_none_when_empty() {
4993 let store = EpisodicStore::new();
4994 let agent = AgentId::new("empty-max");
4995 assert!(store.max_importance_episode(&agent).unwrap().is_none());
4996 }
4997
4998 #[test]
4999 fn test_semantic_store_update_replaces_value() {
5000 let store = SemanticStore::new();
5001 store.store("key1", "original", vec![]).unwrap();
5002 let updated = store.update("key1", "new value").unwrap();
5003 assert!(updated);
5004 let retrieved = store.retrieve_by_key("key1").unwrap().unwrap();
5005 assert_eq!(retrieved.0, "new value");
5006 }
5007
5008 #[test]
5009 fn test_semantic_store_update_returns_false_for_missing_key() {
5010 let store = SemanticStore::new();
5011 assert!(!store.update("missing", "value").unwrap());
5012 }
5013
5014 #[test]
5017 fn test_episodic_clear_for_removes_all_episodes_for_agent() {
5018 let store = EpisodicStore::new();
5019 let agent = AgentId::new("a");
5020 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
5021 store.add_episode(agent.clone(), "e2", 0.9).unwrap();
5022 let removed = store.clear_for(&agent).unwrap();
5023 assert_eq!(removed, 2);
5024 assert_eq!(store.count_for(&agent).unwrap(), 0);
5025 }
5026
5027 #[test]
5028 fn test_episodic_clear_for_returns_zero_for_unknown_agent() {
5029 let store = EpisodicStore::new();
5030 let agent = AgentId::new("ghost");
5031 assert_eq!(store.clear_for(&agent).unwrap(), 0);
5032 }
5033
5034 #[test]
5035 fn test_episodic_importance_sum_correct() {
5036 let store = EpisodicStore::new();
5037 let agent = AgentId::new("a");
5038 store.add_episode(agent.clone(), "e1", 0.3).unwrap();
5039 store.add_episode(agent.clone(), "e2", 0.5).unwrap();
5040 let sum = store.importance_sum(&agent).unwrap();
5041 assert!((sum - 0.8).abs() < 1e-5);
5042 }
5043
5044 #[test]
5045 fn test_episodic_importance_sum_zero_when_empty() {
5046 let store = EpisodicStore::new();
5047 let agent = AgentId::new("empty");
5048 assert_eq!(store.importance_sum(&agent).unwrap(), 0.0);
5049 }
5050
5051 #[test]
5054 fn test_working_memory_merge_from_copies_entries() {
5055 let src = WorkingMemory::new(10).unwrap();
5056 src.set("x", "1").unwrap();
5057 src.set("y", "2").unwrap();
5058 let dst = WorkingMemory::new(10).unwrap();
5059 let count = dst.merge_from(&src).unwrap();
5060 assert_eq!(count, 2);
5061 assert_eq!(dst.get("x").unwrap().unwrap(), "1");
5062 assert_eq!(dst.get("y").unwrap().unwrap(), "2");
5063 }
5064
5065 #[test]
5066 fn test_working_memory_entry_count_satisfying_counts_matching() {
5067 let wm = WorkingMemory::new(10).unwrap();
5068 wm.set("a", "hello").unwrap();
5069 wm.set("b", "world").unwrap();
5070 wm.set("c", "hello world").unwrap();
5071 let count = wm
5072 .entry_count_satisfying(|_, v| v.contains("hello"))
5073 .unwrap();
5074 assert_eq!(count, 2);
5075 }
5076
5077 #[test]
5080 fn test_semantic_update_value_replaces_stored_value() {
5081 let store = SemanticStore::new();
5082 store.store("k", "old", vec![]).unwrap();
5083 assert!(store.update_value("k", "new").unwrap());
5084 let pairs = store.retrieve(&[]).unwrap();
5085 assert!(pairs.iter().any(|(_, v)| v == "new"));
5086 }
5087
5088 #[test]
5089 fn test_semantic_update_value_returns_false_when_missing() {
5090 let store = SemanticStore::new();
5091 assert!(!store.update_value("nope", "x").unwrap());
5092 }
5093
5094 #[test]
5097 fn test_episodic_agent_ids_returns_all_agents() {
5098 let store = EpisodicStore::new();
5099 let a1 = AgentId::new("agent-1");
5100 let a2 = AgentId::new("agent-2");
5101 store.add_episode(a1.clone(), "e", 0.5).unwrap();
5102 store.add_episode(a2.clone(), "e", 0.5).unwrap();
5103 let mut ids = store.agent_ids().unwrap();
5104 ids.sort_by_key(|id| id.0.clone());
5105 assert_eq!(ids, vec![a1, a2]);
5106 }
5107
5108 #[test]
5109 fn test_episodic_agent_ids_empty_for_new_store() {
5110 let store = EpisodicStore::new();
5111 assert!(store.agent_ids().unwrap().is_empty());
5112 }
5113
5114 #[test]
5115 fn test_find_by_content_returns_matching_episodes() {
5116 let store = EpisodicStore::new();
5117 let agent = AgentId::new("a");
5118 store.add_episode(agent.clone(), "hello world", 0.5).unwrap();
5119 store.add_episode(agent.clone(), "goodbye world", 0.8).unwrap();
5120 store.add_episode(agent.clone(), "something else", 0.9).unwrap();
5121 let results = store.find_by_content(&agent, "world").unwrap();
5122 assert_eq!(results.len(), 2);
5123 assert_eq!(results[0].content, "goodbye world");
5125 }
5126
5127 #[test]
5128 fn test_find_by_content_returns_empty_when_no_match() {
5129 let store = EpisodicStore::new();
5130 let agent = AgentId::new("a");
5131 store.add_episode(agent.clone(), "hello", 0.5).unwrap();
5132 let results = store.find_by_content(&agent, "xyz").unwrap();
5133 assert!(results.is_empty());
5134 }
5135
5136 #[test]
5139 fn test_add_episode_at_stores_with_given_timestamp() {
5140 let store = EpisodicStore::new();
5141 let agent = AgentId::new("agent-ts");
5142 let ts = chrono::Utc::now() - chrono::Duration::hours(2);
5143 let id = store.add_episode_at(agent.clone(), "past event", 0.7, ts).unwrap();
5144 let items = store.recall_all(&agent).unwrap();
5145 assert_eq!(items.len(), 1);
5146 assert_eq!(items[0].id, id);
5147 assert_eq!(items[0].content, "past event");
5148 assert!((items[0].timestamp - ts).num_seconds().abs() < 1);
5149 }
5150
5151 #[test]
5152 fn test_add_episodes_batch_returns_all_ids() {
5153 let store = EpisodicStore::new();
5154 let agent = AgentId::new("batch-agent");
5155 let episodes = vec![
5156 ("first", 0.5f32),
5157 ("second", 0.8f32),
5158 ("third", 0.3f32),
5159 ];
5160 let ids = store.add_episodes_batch(agent.clone(), episodes).unwrap();
5161 assert_eq!(ids.len(), 3);
5162 let all = store.recall_all(&agent).unwrap();
5163 assert_eq!(all.len(), 3);
5164 }
5165
5166 #[test]
5167 fn test_add_episodes_batch_empty_iter_returns_empty_ids() {
5168 let store = EpisodicStore::new();
5169 let agent = AgentId::new("empty-batch");
5170 let ids = store
5171 .add_episodes_batch(agent.clone(), Vec::<(String, f32)>::new())
5172 .unwrap();
5173 assert!(ids.is_empty());
5174 assert_eq!(store.count_for(&agent).unwrap(), 0);
5175 }
5176
5177 #[test]
5178 fn test_semantic_keys_matching_returns_substring_matches() {
5179 let store = SemanticStore::new();
5180 store.store("rust_intro", "value1", vec![]).unwrap();
5181 store.store("rust_advanced", "value2", vec![]).unwrap();
5182 store.store("python_basics", "value3", vec![]).unwrap();
5183 let matches = store.keys_matching("rust").unwrap();
5184 assert_eq!(matches.len(), 2);
5185 assert!(matches.contains(&"rust_intro".to_string()));
5186 assert!(matches.contains(&"rust_advanced".to_string()));
5187 }
5188
5189 #[test]
5190 fn test_semantic_keys_matching_is_case_insensitive() {
5191 let store = SemanticStore::new();
5192 store.store("UPPER_KEY", "v", vec![]).unwrap();
5193 let matches = store.keys_matching("upper").unwrap();
5194 assert_eq!(matches.len(), 1);
5195 }
5196
5197 #[test]
5198 fn test_semantic_keys_matching_empty_when_no_match() {
5199 let store = SemanticStore::new();
5200 store.store("abc", "val", vec![]).unwrap();
5201 let matches = store.keys_matching("xyz").unwrap();
5202 assert!(matches.is_empty());
5203 }
5204
5205 #[test]
5208 fn test_importance_avg_correct() {
5209 let store = EpisodicStore::new();
5210 let agent = AgentId::new("a");
5211 store.add_episode(agent.clone(), "e1", 0.2).unwrap();
5212 store.add_episode(agent.clone(), "e2", 0.8).unwrap();
5213 let avg = store.importance_avg(&agent).unwrap();
5214 assert!((avg - 0.5).abs() < 1e-5);
5215 }
5216
5217 #[test]
5218 fn test_importance_avg_zero_for_empty() {
5219 let store = EpisodicStore::new();
5220 let agent = AgentId::new("empty");
5221 assert_eq!(store.importance_avg(&agent).unwrap(), 0.0);
5222 }
5223
5224 #[test]
5225 fn test_deduplicate_content_removes_lower_importance_duplicate() {
5226 let store = EpisodicStore::new();
5227 let agent = AgentId::new("a");
5228 store.add_episode(agent.clone(), "same", 0.3).unwrap();
5229 store.add_episode(agent.clone(), "same", 0.9).unwrap();
5230 store.add_episode(agent.clone(), "different", 0.5).unwrap();
5231 let removed = store.deduplicate_content(&agent).unwrap();
5232 assert_eq!(removed, 1);
5233 assert_eq!(store.count_for(&agent).unwrap(), 2);
5234 }
5235
5236 #[test]
5237 fn test_deduplicate_content_keeps_highest_importance() {
5238 let store = EpisodicStore::new();
5239 let agent = AgentId::new("a");
5240 store.add_episode(agent.clone(), "dup", 0.1).unwrap();
5241 store.add_episode(agent.clone(), "dup", 0.7).unwrap();
5242 store.deduplicate_content(&agent).unwrap();
5243 let items = store.recall(&agent, 10).unwrap();
5244 assert_eq!(items.len(), 1);
5245 assert!((items[0].importance - 0.7).abs() < 1e-5);
5246 }
5247
5248 #[test]
5251 fn test_working_memory_iter_sorted_alphabetical_order() {
5252 let wm = WorkingMemory::new(10).unwrap();
5253 wm.set("c", "3").unwrap();
5254 wm.set("a", "1").unwrap();
5255 wm.set("b", "2").unwrap();
5256 let sorted = wm.iter_sorted().unwrap();
5257 let keys: Vec<&str> = sorted.iter().map(|(k, _)| k.as_str()).collect();
5258 assert_eq!(keys, vec!["a", "b", "c"]);
5259 }
5260
5261 #[test]
5262 fn test_semantic_get_value_returns_value_for_existing_key() {
5263 let store = SemanticStore::new();
5264 store.store("mykey", "myvalue", vec![]).unwrap();
5265 assert_eq!(store.get_value("mykey").unwrap(), Some("myvalue".to_string()));
5266 }
5267
5268 #[test]
5269 fn test_semantic_get_value_returns_none_for_missing_key() {
5270 let store = SemanticStore::new();
5271 assert!(store.get_value("ghost").unwrap().is_none());
5272 }
5273
5274 #[test]
5277 fn test_recall_top_n_returns_highest_importance_items() {
5278 let store = EpisodicStore::new();
5279 let agent = AgentId::new("a");
5280 store.add_episode(agent.clone(), "low", 0.1).unwrap();
5281 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
5282 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5283 let top = store.recall_top_n(&agent, 2).unwrap();
5284 assert_eq!(top.len(), 2);
5285 assert!((top[0].importance - 0.9).abs() < 1e-5);
5286 }
5287
5288 #[test]
5289 fn test_recall_top_n_clamps_to_available_items() {
5290 let store = EpisodicStore::new();
5291 let agent = AgentId::new("a");
5292 store.add_episode(agent.clone(), "only", 0.5).unwrap();
5293 let top = store.recall_top_n(&agent, 100).unwrap();
5294 assert_eq!(top.len(), 1);
5295 }
5296
5297 #[test]
5298 fn test_filter_by_importance_returns_items_in_range() {
5299 let store = EpisodicStore::new();
5300 let agent = AgentId::new("b");
5301 store.add_episode(agent.clone(), "low", 0.1).unwrap();
5302 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
5303 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5304 let mid_range = store.filter_by_importance(&agent, 0.3, 0.7).unwrap();
5305 assert_eq!(mid_range.len(), 1);
5306 assert!((mid_range[0].importance - 0.5).abs() < 1e-5);
5307 }
5308
5309 #[test]
5310 fn test_update_many_sets_multiple_keys() {
5311 let wm = WorkingMemory::new(10).unwrap();
5312 wm.set("x", "old_x").unwrap();
5313 wm.set("y", "old_y").unwrap();
5314 let updated = wm
5315 .update_many(vec![("x".to_string(), "new_x".to_string()), ("y".to_string(), "new_y".to_string())])
5316 .unwrap();
5317 assert_eq!(updated, 2);
5318 assert_eq!(wm.get("x").unwrap(), Some("new_x".to_string()));
5319 assert_eq!(wm.get("y").unwrap(), Some("new_y".to_string()));
5320 }
5321
5322 #[test]
5323 fn test_update_many_returns_zero_for_empty_iter() {
5324 let wm = WorkingMemory::new(5).unwrap();
5325 let updated = wm.update_many(Vec::<(String, String)>::new()).unwrap();
5326 assert_eq!(updated, 0);
5327 }
5328
5329 #[test]
5330 fn test_entry_count_with_embedding_counts_only_embedded_entries() {
5331 let store = SemanticStore::new();
5332 store.store_with_embedding("has_emb", "v1", vec![], vec![0.1_f32, 0.2_f32]).unwrap();
5333 store.store("no_emb", "v2", vec![]).unwrap();
5334 assert_eq!(store.entry_count_with_embedding().unwrap(), 1);
5335 }
5336
5337 #[test]
5340 fn test_retain_top_n_removes_low_importance_episodes() {
5341 let store = EpisodicStore::new();
5342 let agent = AgentId::new("a");
5343 store.add_episode(agent.clone(), "low", 0.1).unwrap();
5344 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
5345 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5346 let removed = store.retain_top_n(&agent, 2).unwrap();
5347 assert_eq!(removed, 1);
5348 let remaining = store.recall(&agent, 10).unwrap();
5349 assert_eq!(remaining.len(), 2);
5350 assert!(remaining.iter().all(|i| i.importance >= 0.5));
5351 }
5352
5353 #[test]
5354 fn test_retain_top_n_noop_when_fewer_than_n() {
5355 let store = EpisodicStore::new();
5356 let agent = AgentId::new("b");
5357 store.add_episode(agent.clone(), "only", 0.7).unwrap();
5358 let removed = store.retain_top_n(&agent, 5).unwrap();
5359 assert_eq!(removed, 0);
5360 assert_eq!(store.recall(&agent, 10).unwrap().len(), 1);
5361 }
5362
5363 #[test]
5364 fn test_keys_starting_with_returns_matching_keys() {
5365 let wm = WorkingMemory::new(10).unwrap();
5366 wm.set("user:name", "alice").unwrap();
5367 wm.set("user:email", "alice@example.com").unwrap();
5368 wm.set("session:id", "abc").unwrap();
5369 let mut keys = wm.keys_starting_with("user:").unwrap();
5370 keys.sort();
5371 assert_eq!(keys, vec!["user:email", "user:name"]);
5372 }
5373
5374 #[test]
5375 fn test_keys_starting_with_returns_empty_when_no_match() {
5376 let wm = WorkingMemory::new(5).unwrap();
5377 wm.set("foo", "bar").unwrap();
5378 assert!(wm.keys_starting_with("xyz:").unwrap().is_empty());
5379 }
5380
5381 #[test]
5384 fn test_episodic_most_recent_returns_last_inserted() {
5385 let store = EpisodicStore::new();
5386 let agent = AgentId::new("a");
5387 store.add_episode(agent.clone(), "first", 0.5).unwrap();
5388 store.add_episode(agent.clone(), "second", 0.3).unwrap();
5389 let recent = store.most_recent(&agent).unwrap().unwrap();
5390 assert_eq!(recent.content, "second");
5391 }
5392
5393 #[test]
5394 fn test_episodic_most_recent_none_when_empty() {
5395 let store = EpisodicStore::new();
5396 let agent = AgentId::new("nobody");
5397 assert!(store.most_recent(&agent).unwrap().is_none());
5398 }
5399
5400 #[test]
5401 fn test_working_memory_contains_all_true_when_all_present() {
5402 let wm = WorkingMemory::new(10).unwrap();
5403 wm.set("x", "1").unwrap();
5404 wm.set("y", "2").unwrap();
5405 assert!(wm.contains_all(["x", "y"]).unwrap());
5406 }
5407
5408 #[test]
5409 fn test_working_memory_contains_all_false_when_one_missing() {
5410 let wm = WorkingMemory::new(10).unwrap();
5411 wm.set("x", "1").unwrap();
5412 assert!(!wm.contains_all(["x", "missing"]).unwrap());
5413 }
5414
5415 #[test]
5416 fn test_working_memory_contains_all_vacuously_true_for_empty() {
5417 let wm = WorkingMemory::new(5).unwrap();
5418 assert!(wm.contains_all(std::iter::empty::<&str>()).unwrap());
5419 }
5420
5421 #[test]
5422 fn test_working_memory_has_any_key_true_when_at_least_one_present() {
5423 let wm = WorkingMemory::new(5).unwrap();
5424 wm.set("present", "v").unwrap();
5425 assert!(wm.has_any_key(["missing", "present"]).unwrap());
5426 }
5427
5428 #[test]
5429 fn test_working_memory_has_any_key_false_when_none_present() {
5430 let wm = WorkingMemory::new(5).unwrap();
5431 assert!(!wm.has_any_key(["a", "b"]).unwrap());
5432 }
5433
5434 #[test]
5435 fn test_working_memory_has_any_key_false_for_empty_iter() {
5436 let wm = WorkingMemory::new(5).unwrap();
5437 assert!(!wm.has_any_key(std::iter::empty::<&str>()).unwrap());
5438 }
5439
5440 #[test]
5441 fn test_semantic_to_map_returns_key_value_pairs() {
5442 let store = SemanticStore::new();
5443 store.store("key1", "val1", vec![]).unwrap();
5444 store.store("key2", "val2", vec![]).unwrap();
5445 let map = store.to_map().unwrap();
5446 assert_eq!(map.get("key1").map(String::as_str), Some("val1"));
5447 assert_eq!(map.get("key2").map(String::as_str), Some("val2"));
5448 assert_eq!(map.len(), 2);
5449 }
5450
5451 #[test]
5452 fn test_semantic_to_map_empty_when_no_entries() {
5453 let store = SemanticStore::new();
5454 assert!(store.to_map().unwrap().is_empty());
5455 }
5456
5457 #[test]
5458 fn test_working_memory_fill_ratio_zero_when_empty() {
5459 let wm = WorkingMemory::new(10).unwrap();
5460 assert_eq!(wm.fill_ratio().unwrap(), 0.0);
5461 }
5462
5463 #[test]
5464 fn test_working_memory_fill_ratio_correct_proportion() {
5465 let wm = WorkingMemory::new(4).unwrap();
5466 wm.set("a", "1").unwrap();
5467 wm.set("b", "2").unwrap();
5468 assert!((wm.fill_ratio().unwrap() - 0.5).abs() < 1e-9);
5470 }
5471
5472 #[test]
5475 fn test_most_recent_returns_last_inserted_episode() {
5476 let store = EpisodicStore::new();
5477 let agent = AgentId::new("a");
5478 store.add_episode(agent.clone(), "first", 0.5).unwrap();
5479 store.add_episode(agent.clone(), "second", 0.8).unwrap();
5480 let recent = store.most_recent(&agent).unwrap().unwrap();
5481 assert_eq!(recent.content, "second");
5482 }
5483
5484 #[test]
5485 fn test_most_recent_returns_none_for_new_agent() {
5486 let store = EpisodicStore::new();
5487 let agent = AgentId::new("empty");
5488 assert!(store.most_recent(&agent).unwrap().is_none());
5489 }
5490
5491 #[test]
5492 fn test_contains_all_true_when_all_keys_present() {
5493 let wm = WorkingMemory::new(10).unwrap();
5494 wm.set("a", "1").unwrap();
5495 wm.set("b", "2").unwrap();
5496 assert!(wm.contains_all(["a", "b"]).unwrap());
5497 }
5498
5499 #[test]
5500 fn test_contains_all_false_when_one_key_missing() {
5501 let wm = WorkingMemory::new(10).unwrap();
5502 wm.set("a", "1").unwrap();
5503 assert!(!wm.contains_all(["a", "missing"]).unwrap());
5504 }
5505
5506 #[test]
5507 fn test_contains_all_true_for_empty_iter() {
5508 let wm = WorkingMemory::new(5).unwrap();
5509 assert!(wm.contains_all([]).unwrap());
5510 }
5511
5512 #[test]
5513 fn test_has_any_key_true_when_one_key_present() {
5514 let wm = WorkingMemory::new(10).unwrap();
5515 wm.set("x", "v").unwrap();
5516 assert!(wm.has_any_key(["x", "y"]).unwrap());
5517 }
5518
5519 #[test]
5520 fn test_has_any_key_false_when_none_present() {
5521 let wm = WorkingMemory::new(5).unwrap();
5522 assert!(!wm.has_any_key(["nope", "also_nope"]).unwrap());
5523 }
5524
5525 #[test]
5526 fn test_has_any_key_false_for_empty_iter() {
5527 let wm = WorkingMemory::new(5).unwrap();
5528 wm.set("a", "1").unwrap();
5529 assert!(!wm.has_any_key([]).unwrap());
5530 }
5531
5532 #[test]
5535 fn test_most_recalled_returns_none_for_new_agent() {
5536 let store = EpisodicStore::new();
5537 let agent = AgentId::new("fresh");
5538 assert!(store.most_recalled(&agent).unwrap().is_none());
5539 }
5540
5541 #[test]
5542 fn test_most_recalled_returns_highest_recall_count() {
5543 use std::sync::Arc;
5544 let store = EpisodicStore::new();
5545 let agent = AgentId::new("a");
5546 store.add_episode(agent.clone(), "low", 0.3).unwrap();
5547 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5548 store.add_episode(agent.clone(), "mid", 0.6).unwrap();
5549 let items = store.recall(&agent, 3).unwrap();
5551 store.recall(&agent, 1).unwrap();
5556 let top = store.most_recalled(&agent).unwrap();
5557 assert!(top.is_some());
5558 assert!(top.unwrap().recall_count >= 1);
5560 }
5561
5562 #[test]
5563 fn test_most_recalled_single_episode() {
5564 let store = EpisodicStore::new();
5565 let agent = AgentId::new("solo");
5566 store.add_episode(agent.clone(), "only one", 0.7).unwrap();
5567 store.recall(&agent, 1).unwrap();
5568 let top = store.most_recalled(&agent).unwrap();
5569 assert_eq!(top.unwrap().content, "only one");
5570 }
5571
5572 #[test]
5575 fn test_max_importance_returns_highest_score() {
5576 let store = EpisodicStore::new();
5577 let agent = AgentId::new("a");
5578 store.add_episode(agent.clone(), "low", 0.1).unwrap();
5579 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5580 let max = store.max_importance(&agent).unwrap().unwrap();
5581 assert!((max - 0.9).abs() < 1e-5);
5582 }
5583
5584 #[test]
5585 fn test_min_importance_returns_lowest_score() {
5586 let store = EpisodicStore::new();
5587 let agent = AgentId::new("b");
5588 store.add_episode(agent.clone(), "low", 0.1).unwrap();
5589 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5590 let min = store.min_importance(&agent).unwrap().unwrap();
5591 assert!((min - 0.1).abs() < 1e-5);
5592 }
5593
5594 #[test]
5595 fn test_max_importance_none_for_empty_agent() {
5596 let store = EpisodicStore::new();
5597 let agent = AgentId::new("empty");
5598 assert!(store.max_importance(&agent).unwrap().is_none());
5599 }
5600
5601 #[test]
5602 fn test_values_matching_returns_pairs_with_pattern() {
5603 let wm = WorkingMemory::new(10).unwrap();
5604 wm.set("name", "alice wonder").unwrap();
5605 wm.set("city", "wonderland").unwrap();
5606 wm.set("role", "engineer").unwrap();
5607 let mut matches = wm.values_matching("wonder").unwrap();
5608 matches.sort_by_key(|(k, _)| k.clone());
5609 assert_eq!(
5610 matches,
5611 vec![
5612 ("city".to_string(), "wonderland".to_string()),
5613 ("name".to_string(), "alice wonder".to_string()),
5614 ]
5615 );
5616 }
5617
5618 #[test]
5619 fn test_values_matching_returns_empty_when_no_match() {
5620 let wm = WorkingMemory::new(5).unwrap();
5621 wm.set("a", "foo").unwrap();
5622 assert!(wm.values_matching("xyz").unwrap().is_empty());
5623 }
5624
5625 #[test]
5628 fn test_count_above_importance_correct_count() {
5629 let store = EpisodicStore::new();
5630 let agent = AgentId::new("agent");
5631 store.add_episode(agent.clone(), "low", 0.2).unwrap();
5632 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
5633 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5634 assert_eq!(store.count_above_importance(&agent, 0.4).unwrap(), 2);
5635 }
5636
5637 #[test]
5638 fn test_count_above_importance_zero_for_empty_agent() {
5639 let store = EpisodicStore::new();
5640 let agent = AgentId::new("nobody");
5641 assert_eq!(store.count_above_importance(&agent, 0.0).unwrap(), 0);
5642 }
5643
5644 #[test]
5645 fn test_count_above_importance_threshold_is_exclusive() {
5646 let store = EpisodicStore::new();
5647 let agent = AgentId::new("ex");
5648 store.add_episode(agent.clone(), "exact", 0.5).unwrap();
5649 assert_eq!(store.count_above_importance(&agent, 0.5).unwrap(), 0);
5651 }
5652
5653 #[test]
5654 fn test_working_memory_value_length_some_for_existing_key() {
5655 let wm = WorkingMemory::new(10).unwrap();
5656 wm.set("greeting", "hello").unwrap();
5657 assert_eq!(wm.value_length("greeting").unwrap(), Some(5));
5658 }
5659
5660 #[test]
5661 fn test_working_memory_value_length_none_for_absent_key() {
5662 let wm = WorkingMemory::new(10).unwrap();
5663 assert!(wm.value_length("missing").unwrap().is_none());
5664 }
5665
5666 #[test]
5667 fn test_semantic_store_tags_for_returns_tags() {
5668 let store = SemanticStore::new();
5669 store
5670 .store("key1", "value1", vec!["alpha".to_string(), "beta".to_string()])
5671 .unwrap();
5672 let tags = store.tags_for("key1").unwrap().unwrap();
5673 assert_eq!(tags, vec!["alpha".to_string(), "beta".to_string()]);
5674 }
5675
5676 #[test]
5677 fn test_semantic_store_tags_for_none_for_missing_key() {
5678 let store = SemanticStore::new();
5679 assert!(store.tags_for("ghost").unwrap().is_none());
5680 }
5681
5682 #[test]
5683 fn test_semantic_store_tags_for_empty_tags() {
5684 let store = SemanticStore::new();
5685 store.store("k", "v", vec![]).unwrap();
5686 let tags = store.tags_for("k").unwrap().unwrap();
5687 assert!(tags.is_empty());
5688 }
5689
5690 #[test]
5693 fn test_peek_oldest_returns_oldest_without_removing() {
5694 let wm = WorkingMemory::new(10).unwrap();
5695 wm.set("first", "alpha").unwrap();
5696 wm.set("second", "beta").unwrap();
5697 let peeked = wm.peek_oldest().unwrap();
5698 assert_eq!(peeked, Some(("first".into(), "alpha".into())));
5699 assert_eq!(wm.len().unwrap(), 2);
5701 }
5702
5703 #[test]
5704 fn test_peek_oldest_empty_returns_none() {
5705 let wm = WorkingMemory::new(5).unwrap();
5706 assert_eq!(wm.peek_oldest().unwrap(), None);
5707 }
5708
5709 #[test]
5710 fn test_peek_oldest_does_not_remove_entry() {
5711 let wm = WorkingMemory::new(5).unwrap();
5712 wm.set("k", "v").unwrap();
5713 wm.peek_oldest().unwrap();
5714 assert_eq!(wm.get("k").unwrap(), Some("v".into()));
5715 }
5716
5717 #[test]
5718 fn test_semantic_store_values_returns_all_values() {
5719 let store = SemanticStore::new();
5720 store.store("k1", "hello", vec![]).unwrap();
5721 store.store("k2", "world", vec![]).unwrap();
5722 let vals = store.values().unwrap();
5723 assert_eq!(vals.len(), 2);
5724 assert!(vals.contains(&"hello".to_string()));
5725 assert!(vals.contains(&"world".to_string()));
5726 }
5727
5728 #[test]
5729 fn test_semantic_store_values_empty() {
5730 let store = SemanticStore::new();
5731 assert!(store.values().unwrap().is_empty());
5732 }
5733
5734 #[test]
5735 fn test_semantic_store_get_tags_returns_tags() {
5736 let store = SemanticStore::new();
5737 store.store("k1", "val", vec!["tag-a".to_string(), "tag-b".to_string()]).unwrap();
5738 let tags = store.get_tags("k1").unwrap();
5739 assert!(tags.is_some());
5740 let tags = tags.unwrap();
5741 assert!(tags.contains(&"tag-a".to_string()));
5742 assert!(tags.contains(&"tag-b".to_string()));
5743 }
5744
5745 #[test]
5746 fn test_semantic_store_get_tags_missing_key_returns_none() {
5747 let store = SemanticStore::new();
5748 assert!(store.get_tags("no-such-key").unwrap().is_none());
5749 }
5750
5751 #[test]
5754 fn test_working_memory_value_length_returns_char_count_r27() {
5755 let wm = WorkingMemory::new(10).unwrap();
5756 wm.set("k", "hello").unwrap();
5757 assert_eq!(wm.value_length("k").unwrap(), Some(5));
5758 }
5759
5760 #[test]
5761 fn test_working_memory_value_length_none_for_missing_key_r27() {
5762 let wm = WorkingMemory::new(10).unwrap();
5763 assert!(wm.value_length("nope").unwrap().is_none());
5764 }
5765
5766 #[test]
5767 fn test_working_memory_iter_sorted_returns_sorted_pairs() {
5768 let wm = WorkingMemory::new(10).unwrap();
5769 wm.set("b", "2").unwrap();
5770 wm.set("a", "1").unwrap();
5771 let pairs = wm.iter_sorted().unwrap();
5772 assert_eq!(pairs[0].0, "a");
5773 assert_eq!(pairs[1].0, "b");
5774 }
5775
5776 #[test]
5777 fn test_working_memory_drain_empties_store() {
5778 let wm = WorkingMemory::new(10).unwrap();
5779 wm.set("x", "1").unwrap();
5780 wm.set("y", "2").unwrap();
5781 let drained = wm.drain().unwrap();
5782 assert_eq!(drained.len(), 2);
5783 assert!(wm.is_empty().unwrap());
5784 }
5785
5786 #[test]
5787 fn test_working_memory_snapshot_returns_all_entries_r27() {
5788 let wm = WorkingMemory::new(10).unwrap();
5789 wm.set("a", "alpha").unwrap();
5790 wm.set("b", "beta").unwrap();
5791 let snap = wm.snapshot().unwrap();
5792 assert_eq!(snap.get("a").map(String::as_str), Some("alpha"));
5793 assert_eq!(snap.get("b").map(String::as_str), Some("beta"));
5794 }
5795
5796 #[test]
5797 fn test_working_memory_snapshot_empty_when_no_entries_r27() {
5798 let wm = WorkingMemory::new(5).unwrap();
5799 assert!(wm.snapshot().unwrap().is_empty());
5800 }
5801
5802 #[test]
5806 fn test_episodic_store_agent_count_zero_when_empty() {
5807 let store = EpisodicStore::new();
5808 assert_eq!(store.agent_count().unwrap(), 0);
5809 }
5810
5811 #[test]
5812 fn test_episodic_store_agent_count_distinct_agents() {
5813 let store = EpisodicStore::new();
5814 store.add_episode(AgentId::new("a"), "ep1", 0.5).unwrap();
5815 store.add_episode(AgentId::new("b"), "ep2", 0.7).unwrap();
5816 store.add_episode(AgentId::new("a"), "ep3", 0.3).unwrap();
5817 assert_eq!(store.agent_count().unwrap(), 2);
5818 }
5819
5820 #[test]
5821 fn test_working_memory_is_at_capacity_true_when_full() {
5822 let wm = WorkingMemory::new(2).unwrap();
5823 wm.set("k1", "v1").unwrap();
5824 wm.set("k2", "v2").unwrap();
5825 assert!(wm.is_at_capacity().unwrap());
5826 }
5827
5828 #[test]
5829 fn test_working_memory_is_at_capacity_false_when_not_full() {
5830 let wm = WorkingMemory::new(5).unwrap();
5831 wm.set("k1", "v1").unwrap();
5832 assert!(!wm.is_at_capacity().unwrap());
5833 }
5834
5835 #[test]
5836 fn test_remove_keys_starting_with_removes_matching_keys() {
5837 let wm = WorkingMemory::new(10).unwrap();
5838 wm.set("prefix_a", "1").unwrap();
5839 wm.set("prefix_b", "2").unwrap();
5840 wm.set("other", "3").unwrap();
5841 let removed = wm.remove_keys_starting_with("prefix_").unwrap();
5842 assert_eq!(removed, 2);
5843 assert!(wm.get("prefix_a").unwrap().is_none());
5844 assert!(wm.get("prefix_b").unwrap().is_none());
5845 assert_eq!(wm.get("other").unwrap(), Some("3".into()));
5846 }
5847
5848 #[test]
5849 fn test_remove_keys_starting_with_returns_zero_when_no_match() {
5850 let wm = WorkingMemory::new(5).unwrap();
5851 wm.set("foo", "bar").unwrap();
5852 assert_eq!(wm.remove_keys_starting_with("xyz_").unwrap(), 0);
5853 }
5854
5855 #[test]
5856 fn test_semantic_store_has_key_true_when_exists() {
5857 let store = SemanticStore::new();
5858 store.store("mykey", "val", vec![]).unwrap();
5859 assert!(store.has_key("mykey").unwrap());
5860 }
5861
5862 #[test]
5863 fn test_semantic_store_has_key_false_when_absent() {
5864 let store = SemanticStore::new();
5865 assert!(!store.has_key("ghost").unwrap());
5866 }
5867
5868 #[test]
5869 fn test_entry_count_with_tag_counts_correctly() {
5870 let store = SemanticStore::new();
5871 store.store("k1", "v1", vec!["rust".into(), "async".into()]).unwrap();
5872 store.store("k2", "v2", vec!["rust".into()]).unwrap();
5873 store.store("k3", "v3", vec!["python".into()]).unwrap();
5874 assert_eq!(store.entry_count_with_tag("rust").unwrap(), 2);
5875 assert_eq!(store.entry_count_with_tag("async").unwrap(), 1);
5876 assert_eq!(store.entry_count_with_tag("absent").unwrap(), 0);
5877 }
5878
5879 #[test]
5882 fn test_importance_sum_returns_sum_of_all_importances() {
5883 let store = EpisodicStore::new();
5884 let agent = AgentId::new("a");
5885 store.add_episode(agent.clone(), "e1", 0.2).unwrap();
5886 store.add_episode(agent.clone(), "e2", 0.3).unwrap();
5887 store.add_episode(agent.clone(), "e3", 0.5).unwrap();
5888 let sum = store.importance_sum(&agent).unwrap();
5889 assert!((sum - 1.0).abs() < 1e-5);
5890 }
5891
5892 #[test]
5893 fn test_importance_sum_zero_for_unknown_agent() {
5894 let store = EpisodicStore::new();
5895 let agent = AgentId::new("nobody");
5896 assert!((store.importance_sum(&agent).unwrap() - 0.0).abs() < 1e-9);
5897 }
5898
5899 #[test]
5900 fn test_update_content_changes_stored_content() {
5901 let store = EpisodicStore::new();
5902 let agent = AgentId::new("a");
5903 let id = store.add_episode(agent.clone(), "old content", 0.5).unwrap();
5904 let updated = store.update_content(&agent, &id, "new content").unwrap();
5905 assert!(updated);
5906 let items = store.recall_all(&agent).unwrap();
5907 assert_eq!(items[0].content, "new content");
5908 }
5909
5910 #[test]
5911 fn test_update_content_false_for_missing_id() {
5912 let store = EpisodicStore::new();
5913 let agent = AgentId::new("a");
5914 let fake_id = MemoryId::new("nonexistent");
5915 assert!(!store.update_content(&agent, &fake_id, "x").unwrap());
5916 }
5917
5918 #[test]
5919 fn test_recall_all_returns_all_episodes() {
5920 let store = EpisodicStore::new();
5921 let agent = AgentId::new("a");
5922 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
5923 store.add_episode(agent.clone(), "e2", 0.3).unwrap();
5924 store.add_episode(agent.clone(), "e3", 0.9).unwrap();
5925 let all = store.recall_all(&agent).unwrap();
5926 assert_eq!(all.len(), 3);
5927 }
5928
5929 #[test]
5930 fn test_top_n_returns_top_by_importance() {
5931 let store = EpisodicStore::new();
5932 let agent = AgentId::new("a");
5933 store.add_episode(agent.clone(), "low", 0.1).unwrap();
5934 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5935 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
5936 let top = store.top_n(&agent, 2).unwrap();
5937 assert_eq!(top.len(), 2);
5938 assert_eq!(top[0].content, "high");
5939 assert_eq!(top[1].content, "mid");
5940 }
5941
5942 #[test]
5943 fn test_search_by_importance_range_filters_correctly() {
5944 let store = EpisodicStore::new();
5945 let agent = AgentId::new("a");
5946 store.add_episode(agent.clone(), "low", 0.1).unwrap();
5947 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
5948 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5949 let results = store.search_by_importance_range(&agent, 0.4, 0.8, 0).unwrap();
5950 assert_eq!(results.len(), 1);
5951 assert_eq!(results[0].content, "mid");
5952 }
5953
5954 #[test]
5957 fn test_oldest_episode_returns_none_for_new_agent() {
5958 let store = EpisodicStore::new();
5959 let agent = AgentId::new("fresh");
5960 assert!(store.oldest_episode(&agent).unwrap().is_none());
5961 }
5962
5963 #[test]
5964 fn test_oldest_episode_returns_earliest_timestamp() {
5965 let store = EpisodicStore::new();
5966 let agent = AgentId::new("a");
5967 store.add_episode(agent.clone(), "first", 0.5).unwrap();
5968 store.add_episode(agent.clone(), "second", 0.7).unwrap();
5969 store.add_episode(agent.clone(), "third", 0.3).unwrap();
5970 let oldest = store.oldest_episode(&agent).unwrap().unwrap();
5971 assert_eq!(oldest.content, "first");
5973 }
5974
5975 #[test]
5976 fn test_semantic_store_remove_by_key_removes_all_matching() {
5977 let store = SemanticStore::new();
5978 store.store("target", "v1", vec![]).unwrap();
5979 store.store("target", "v2", vec![]).unwrap();
5980 store.store("keep", "vk", vec![]).unwrap();
5981 let removed = store.remove_by_key("target").unwrap();
5982 assert_eq!(removed, 2);
5983 assert!(!store.has_key("target").unwrap());
5984 assert!(store.has_key("keep").unwrap());
5985 }
5986
5987 #[test]
5988 fn test_semantic_store_remove_by_key_zero_for_absent_key() {
5989 let store = SemanticStore::new();
5990 assert_eq!(store.remove_by_key("ghost").unwrap(), 0);
5991 }
5992
5993 #[test]
5994 fn test_working_memory_total_value_bytes_sums_lengths() {
5995 let wm = WorkingMemory::new(10).unwrap();
5996 wm.set("a", "hello").unwrap(); wm.set("b", "world!").unwrap(); assert_eq!(wm.total_value_bytes().unwrap(), 11);
5999 }
6000
6001 #[test]
6002 fn test_working_memory_total_value_bytes_zero_when_empty() {
6003 let wm = WorkingMemory::new(5).unwrap();
6004 assert_eq!(wm.total_value_bytes().unwrap(), 0);
6005 }
6006
6007 #[test]
6010 fn test_agent_ids_returns_all_tracked_agents() {
6011 let store = EpisodicStore::new();
6012 let a1 = AgentId::new("alice");
6013 let a2 = AgentId::new("bob");
6014 store.add_episode(a1.clone(), "x", 0.5).unwrap();
6015 store.add_episode(a2.clone(), "y", 0.5).unwrap();
6016 let mut ids = store.agent_ids().unwrap();
6017 ids.sort_by_key(|id| id.as_str().to_string());
6018 assert_eq!(ids.len(), 2);
6019 assert_eq!(ids[0].as_str(), "alice");
6020 assert_eq!(ids[1].as_str(), "bob");
6021 }
6022
6023 #[test]
6024 fn test_agent_ids_empty_for_new_store() {
6025 let store = EpisodicStore::new();
6026 assert!(store.agent_ids().unwrap().is_empty());
6027 }
6028
6029 #[test]
6030 fn test_clear_for_removes_all_episodes_for_agent() {
6031 let store = EpisodicStore::new();
6032 let agent = AgentId::new("a");
6033 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
6034 store.add_episode(agent.clone(), "e2", 0.3).unwrap();
6035 let removed = store.clear_for(&agent).unwrap();
6036 assert_eq!(removed, 2);
6037 assert_eq!(store.count_for(&agent).unwrap(), 0);
6038 }
6039
6040 #[test]
6041 fn test_clear_for_returns_zero_for_unknown_agent() {
6042 let store = EpisodicStore::new();
6043 let agent = AgentId::new("ghost");
6044 assert_eq!(store.clear_for(&agent).unwrap(), 0);
6045 }
6046
6047 #[test]
6048 fn test_recall_since_returns_episodes_after_cutoff() {
6049 let store = EpisodicStore::new();
6050 let agent = AgentId::new("a");
6051 let past = chrono::Utc::now() - chrono::Duration::hours(2);
6052 let future_cutoff = chrono::Utc::now() + chrono::Duration::seconds(1);
6053 store.add_episode_at(agent.clone(), "old", 0.5, past).unwrap();
6055 store.add_episode(agent.clone(), "new", 0.5).unwrap();
6057 let future = store.recall_since(&agent, future_cutoff, 0).unwrap();
6059 assert!(future.is_empty());
6060 let all = store.recall_since(&agent, past - chrono::Duration::seconds(1), 0).unwrap();
6062 assert_eq!(all.len(), 2);
6063 }
6064
6065 #[test]
6068 fn test_sum_recall_counts_zero_for_new_agent() {
6069 let store = EpisodicStore::new();
6070 let agent = AgentId::new("new");
6071 assert_eq!(store.sum_recall_counts(&agent).unwrap(), 0);
6072 }
6073
6074 #[test]
6075 fn test_sum_recall_counts_increases_with_recalls() {
6076 let store = EpisodicStore::new();
6077 let agent = AgentId::new("a");
6078 store.add_episode(agent.clone(), "ep1", 0.5).unwrap();
6079 store.add_episode(agent.clone(), "ep2", 0.8).unwrap();
6080 store.recall(&agent, 2).unwrap();
6082 let total = store.sum_recall_counts(&agent).unwrap();
6083 assert!(total >= 2);
6084 }
6085
6086 #[test]
6089 fn test_max_recall_count_for_none_for_new_agent() {
6090 let store = EpisodicStore::new();
6091 let agent = AgentId::new("ghost");
6092 assert_eq!(store.max_recall_count_for(&agent).unwrap(), None);
6093 }
6094
6095 #[test]
6096 fn test_max_recall_count_for_returns_highest_after_recalls() {
6097 let store = EpisodicStore::new();
6098 let agent = AgentId::new("a");
6099 store.add_episode(agent.clone(), "ep1", 0.9).unwrap();
6100 store.add_episode(agent.clone(), "ep2", 0.1).unwrap();
6101 store.recall(&agent, 2).unwrap();
6103 store.recall(&agent, 1).unwrap();
6104 let max = store.max_recall_count_for(&agent).unwrap().unwrap();
6105 assert!(max >= 1);
6106 }
6107
6108 #[test]
6109 fn test_semantic_most_recent_key_none_when_empty() {
6110 let store = SemanticStore::new();
6111 assert_eq!(store.most_recent_key().unwrap(), None);
6112 }
6113
6114 #[test]
6115 fn test_semantic_most_recent_key_returns_last_inserted() {
6116 let store = SemanticStore::new();
6117 store.store("first", "v1", vec![]).unwrap();
6118 store.store("second", "v2", vec![]).unwrap();
6119 assert_eq!(store.most_recent_key().unwrap(), Some("second".to_string()));
6120 }
6121
6122 #[test]
6123 fn test_working_memory_max_key_length_zero_when_empty() {
6124 let mem = WorkingMemory::new(10).unwrap();
6125 assert_eq!(mem.max_key_length().unwrap(), 0);
6126 }
6127
6128 #[test]
6129 fn test_working_memory_max_key_length_returns_longest() {
6130 let mem = WorkingMemory::new(10).unwrap();
6131 mem.set("ab", "v1").unwrap();
6132 mem.set("abcde", "v2").unwrap();
6133 mem.set("abc", "v3").unwrap();
6134 assert_eq!(mem.max_key_length().unwrap(), 5);
6135 }
6136
6137 #[test]
6140 fn test_working_memory_set_if_absent_inserts_new_key() {
6141 let mem = WorkingMemory::new(10).unwrap();
6142 let inserted = mem.set_if_absent("fresh", "value").unwrap();
6143 assert!(inserted);
6144 assert_eq!(mem.get("fresh").unwrap(), Some("value".to_string()));
6145 }
6146
6147 #[test]
6148 fn test_working_memory_set_if_absent_does_not_overwrite_existing() {
6149 let mem = WorkingMemory::new(10).unwrap();
6150 mem.set("key", "original").unwrap();
6151 let inserted = mem.set_if_absent("key", "replacement").unwrap();
6152 assert!(!inserted);
6153 assert_eq!(mem.get("key").unwrap(), Some("original".to_string()));
6154 }
6155
6156 #[test]
6157 fn test_working_memory_set_if_absent_second_call_returns_false() {
6158 let mem = WorkingMemory::new(10).unwrap();
6159 assert!(mem.set_if_absent("k", "v1").unwrap());
6160 assert!(!mem.set_if_absent("k", "v2").unwrap());
6161 }
6162
6163 #[test]
6166 fn test_memory_item_has_tag_true_when_tag_present() {
6167 let item = MemoryItem::new(
6168 AgentId::new("a"),
6169 "content",
6170 0.5,
6171 vec!["important".to_string(), "work".to_string()],
6172 );
6173 assert!(item.has_tag("important"));
6174 assert!(item.has_tag("work"));
6175 }
6176
6177 #[test]
6178 fn test_memory_item_has_tag_false_when_tag_absent() {
6179 let item = MemoryItem::new(AgentId::new("a"), "content", 0.5, vec![]);
6180 assert!(!item.has_tag("missing"));
6181 }
6182
6183 #[test]
6184 fn test_memory_item_word_count_counts_words() {
6185 let item = MemoryItem::new(AgentId::new("a"), "one two three", 0.5, vec![]);
6186 assert_eq!(item.word_count(), 3);
6187 }
6188
6189 #[test]
6190 fn test_memory_item_word_count_zero_for_empty_content() {
6191 let item = MemoryItem::new(AgentId::new("a"), "", 0.5, vec![]);
6192 assert_eq!(item.word_count(), 0);
6193 }
6194
6195 #[test]
6196 fn test_decay_policy_apply_reduces_importance_after_one_half_life() {
6197 let policy = DecayPolicy::exponential(1.0).unwrap(); let decayed = policy.apply(1.0, 1.0); assert!((decayed - 0.5).abs() < 1e-5);
6200 }
6201
6202 #[test]
6203 fn test_decay_policy_apply_no_change_for_zero_age() {
6204 let policy = DecayPolicy::exponential(10.0).unwrap();
6205 let decayed = policy.apply(0.8, 0.0);
6206 assert!((decayed - 0.8).abs() < 1e-5);
6207 }
6208
6209 #[test]
6210 fn test_decay_policy_decay_item_reduces_importance_for_old_item() {
6211 let policy = DecayPolicy::exponential(0.0001).unwrap(); let mut item = MemoryItem::new(AgentId::new("a"), "old memory", 1.0, vec![]);
6213 item.timestamp = Utc::now() - chrono::Duration::hours(1);
6214 policy.decay_item(&mut item);
6215 assert!(item.importance < 1.0);
6216 }
6217
6218 #[test]
6219 fn test_episodic_export_returns_empty_for_unknown_agent() {
6220 let store = EpisodicStore::new();
6221 let agent = AgentId::new("ghost");
6222 let exported = store.export_agent_memory(&agent).unwrap();
6223 assert!(exported.is_empty());
6224 }
6225
6226 #[test]
6227 fn test_episodic_export_returns_all_stored_items() {
6228 let store = EpisodicStore::new();
6229 let agent = AgentId::new("a");
6230 store.add_episode(agent.clone(), "memory1", 0.9).unwrap();
6231 store.add_episode(agent.clone(), "memory2", 0.5).unwrap();
6232 let exported = store.export_agent_memory(&agent).unwrap();
6233 assert_eq!(exported.len(), 2);
6234 }
6235
6236 #[test]
6237 fn test_bump_recall_count_increases_by_amount() {
6238 let store = EpisodicStore::new();
6239 let agent = AgentId::new("a");
6240 store.add_episode(agent.clone(), "the content", 0.7).unwrap();
6241 store.bump_recall_count_by_content("the content", 5);
6242 let max = store.max_recall_count_for(&agent).unwrap().unwrap();
6243 assert_eq!(max, 5);
6244 }
6245
6246 #[test]
6247 fn test_bump_recall_count_no_effect_for_absent_content() {
6248 let store = EpisodicStore::new();
6249 let agent = AgentId::new("a");
6250 store.add_episode(agent.clone(), "existing", 0.5).unwrap();
6251 store.bump_recall_count_by_content("not here", 10);
6252 let max = store.max_recall_count_for(&agent).unwrap().unwrap();
6253 assert_eq!(max, 0);
6254 }
6255
6256 #[test]
6259 fn test_latest_episode_none_for_new_agent() {
6260 let store = EpisodicStore::new();
6261 let agent = AgentId::new("ghost");
6262 assert!(store.latest_episode(&agent).unwrap().is_none());
6263 }
6264
6265 #[test]
6266 fn test_latest_episode_returns_most_recent_by_timestamp() {
6267 let store = EpisodicStore::new();
6268 let agent = AgentId::new("a");
6269 let old = chrono::Utc::now() - chrono::Duration::hours(1);
6270 store.add_episode_at(agent.clone(), "old ep", 0.5, old).unwrap();
6271 store.add_episode(agent.clone(), "new ep", 0.8).unwrap();
6272 let latest = store.latest_episode(&agent).unwrap().unwrap();
6273 assert_eq!(latest.content, "new ep");
6274 }
6275
6276 #[test]
6277 fn test_semantic_oldest_key_none_when_empty() {
6278 let store = SemanticStore::new();
6279 assert!(store.oldest_key().unwrap().is_none());
6280 }
6281
6282 #[test]
6283 fn test_semantic_oldest_key_returns_first_inserted() {
6284 let store = SemanticStore::new();
6285 store.store("first", "value1", vec![]).unwrap();
6286 store.store("second", "value2", vec![]).unwrap();
6287 assert_eq!(store.oldest_key().unwrap().as_deref(), Some("first"));
6288 }
6289
6290 #[test]
6291 fn test_working_memory_key_count_matching_zero_when_empty() {
6292 let mem = WorkingMemory::new(10).unwrap();
6293 assert_eq!(mem.key_count_matching("foo").unwrap(), 0);
6294 }
6295
6296 #[test]
6297 fn test_working_memory_key_count_matching_counts_correctly() {
6298 let mem = WorkingMemory::new(10).unwrap();
6299 mem.set("foo_bar", "v1").unwrap();
6300 mem.set("foo_baz", "v2").unwrap();
6301 mem.set("other", "v3").unwrap();
6302 assert_eq!(mem.key_count_matching("foo").unwrap(), 2);
6303 }
6304
6305 #[test]
6306 fn test_working_memory_avg_value_length_zero_when_empty() {
6307 let mem = WorkingMemory::new(10).unwrap();
6308 assert!((mem.avg_value_length().unwrap() - 0.0).abs() < 1e-9);
6309 }
6310
6311 #[test]
6312 fn test_working_memory_avg_value_length_correct_mean() {
6313 let mem = WorkingMemory::new(10).unwrap();
6314 mem.set("k1", "ab").unwrap(); mem.set("k2", "abcd").unwrap(); assert!((mem.avg_value_length().unwrap() - 3.0).abs() < 1e-9);
6318 }
6319
6320 #[test]
6323 fn test_episodic_store_has_agent_false_when_empty() {
6324 let store = EpisodicStore::new();
6325 assert!(!store.has_agent(&AgentId::new("nobody")).unwrap());
6326 }
6327
6328 #[test]
6329 fn test_episodic_store_has_agent_true_after_episode() {
6330 let store = EpisodicStore::new();
6331 let agent = AgentId::new("agent-ha");
6332 store.add_episode(agent.clone(), "event", 0.5).unwrap();
6333 assert!(store.has_agent(&agent).unwrap());
6334 }
6335
6336 #[test]
6337 fn test_episodic_store_export_agent_memory_empty_for_unknown() {
6338 let store = EpisodicStore::new();
6339 let exported = store.export_agent_memory(&AgentId::new("ghost")).unwrap();
6340 assert!(exported.is_empty());
6341 }
6342
6343 #[test]
6344 fn test_episodic_store_export_agent_memory_returns_episodes() {
6345 let store = EpisodicStore::new();
6346 let agent = AgentId::new("agent-exp");
6347 store.add_episode(agent.clone(), "ep1", 0.9).unwrap();
6348 store.add_episode(agent.clone(), "ep2", 0.7).unwrap();
6349 let exported = store.export_agent_memory(&agent).unwrap();
6350 assert_eq!(exported.len(), 2);
6351 }
6352
6353 #[test]
6356 fn test_semantic_store_store_with_embedding_rejects_empty_vec() {
6357 let store = SemanticStore::new();
6358 let result = store.store_with_embedding("k", "v", vec![], vec![]);
6359 assert!(result.is_err());
6360 }
6361
6362 #[test]
6363 fn test_semantic_store_store_with_embedding_stores_entry() {
6364 let store = SemanticStore::new();
6365 let emb = vec![1.0f32, 0.0, 0.0];
6366 store.store_with_embedding("k1", "v1", vec![], emb).unwrap();
6367 assert_eq!(store.len().unwrap(), 1);
6368 }
6369
6370 #[test]
6371 fn test_semantic_store_store_with_embedding_rejects_dimension_mismatch() {
6372 let store = SemanticStore::new();
6373 store.store_with_embedding("k1", "v1", vec![], vec![1.0f32, 0.0]).unwrap();
6374 let result = store.store_with_embedding("k2", "v2", vec![], vec![1.0f32, 0.0, 0.0]);
6376 assert!(result.is_err());
6377 }
6378
6379 #[test]
6383 fn test_avg_importance_zero_for_new_agent() {
6384 let store = EpisodicStore::new();
6385 let agent = AgentId::new("ghost");
6386 assert!((store.avg_importance(&agent).unwrap() - 0.0).abs() < 1e-9);
6387 }
6388
6389 #[test]
6390 fn test_avg_importance_correct_mean() {
6391 let store = EpisodicStore::new();
6392 let agent = AgentId::new("a");
6393 store.add_episode(agent.clone(), "ep1", 0.2).unwrap();
6394 store.add_episode(agent.clone(), "ep2", 0.8).unwrap();
6395 assert!((store.avg_importance(&agent).unwrap() - 0.5).abs() < 1e-6);
6397 }
6398
6399 #[test]
6400 fn test_importance_range_none_for_new_agent() {
6401 let store = EpisodicStore::new();
6402 let agent = AgentId::new("ghost");
6403 assert!(store.importance_range(&agent).unwrap().is_none());
6404 }
6405
6406 #[test]
6407 fn test_importance_range_returns_min_max() {
6408 let store = EpisodicStore::new();
6409 let agent = AgentId::new("a");
6410 store.add_episode(agent.clone(), "ep1", 0.1).unwrap();
6411 store.add_episode(agent.clone(), "ep2", 0.9).unwrap();
6412 let (min, max) = store.importance_range(&agent).unwrap().unwrap();
6413 assert!((min - 0.1_f32).abs() < 1e-6);
6414 assert!((max - 0.9_f32).abs() < 1e-6);
6415 }
6416
6417 #[test]
6418 fn test_semantic_entries_without_tags_all_untagged() {
6419 let store = SemanticStore::new();
6420 store.store("k1", "v1", vec![]).unwrap();
6421 store.store("k2", "v2", vec![]).unwrap();
6422 assert_eq!(store.entries_without_tags().unwrap(), 2);
6423 }
6424
6425 #[test]
6426 fn test_semantic_entries_without_tags_mixed() {
6427 let store = SemanticStore::new();
6428 store.store("k1", "v1", vec!["tag".to_string()]).unwrap();
6429 store.store("k2", "v2", vec![]).unwrap();
6430 assert_eq!(store.entries_without_tags().unwrap(), 1);
6431 }
6432
6433 #[test]
6434 fn test_semantic_avg_tag_count_zero_when_empty() {
6435 let store = SemanticStore::new();
6436 assert!((store.avg_tag_count_per_entry().unwrap() - 0.0).abs() < 1e-9);
6437 }
6438
6439 #[test]
6440 fn test_semantic_avg_tag_count_correct_mean() {
6441 let store = SemanticStore::new();
6442 store.store("k1", "v1", vec!["a".to_string(), "b".to_string()]).unwrap(); store.store("k2", "v2", vec![]).unwrap(); assert!((store.avg_tag_count_per_entry().unwrap() - 1.0).abs() < 1e-9);
6446 }
6447
6448 #[test]
6449 fn test_working_memory_longest_key_none_when_empty() {
6450 let mem = WorkingMemory::new(10).unwrap();
6451 assert!(mem.longest_key().unwrap().is_none());
6452 }
6453
6454 #[test]
6455 fn test_working_memory_longest_key_returns_longest() {
6456 let mem = WorkingMemory::new(10).unwrap();
6457 mem.set("ab", "v1").unwrap();
6458 mem.set("abcde", "v2").unwrap();
6459 assert_eq!(mem.longest_key().unwrap().as_deref(), Some("abcde"));
6460 }
6461
6462 #[test]
6463 fn test_working_memory_longest_value_none_when_empty() {
6464 let mem = WorkingMemory::new(10).unwrap();
6465 assert!(mem.longest_value().unwrap().is_none());
6466 }
6467
6468 #[test]
6469 fn test_working_memory_longest_value_returns_longest() {
6470 let mem = WorkingMemory::new(10).unwrap();
6471 mem.set("k1", "short").unwrap();
6472 mem.set("k2", "much longer value").unwrap();
6473 assert_eq!(mem.longest_value().unwrap().as_deref(), Some("much longer value"));
6474 }
6475
6476 #[test]
6479 fn test_semantic_store_with_embedding_rejects_empty_vector() {
6480 let store = SemanticStore::new();
6481 let result = store.store_with_embedding("k", "v", vec![], vec![]);
6482 assert!(result.is_err());
6483 }
6484
6485 #[test]
6486 fn test_semantic_store_with_embedding_stores_and_retrievable() {
6487 let store = SemanticStore::new();
6488 store.store_with_embedding("k", "v", vec![], vec![1.0, 0.0]).unwrap();
6489 let entry = store.retrieve_by_key("k").unwrap();
6490 assert_eq!(entry.map(|(val, _)| val), Some("v".to_string()));
6491 }
6492
6493 #[test]
6494 fn test_semantic_store_with_embedding_dimension_mismatch_errors() {
6495 let store = SemanticStore::new();
6496 store.store_with_embedding("k1", "v1", vec![], vec![1.0, 0.0]).unwrap();
6497 let result = store.store_with_embedding("k2", "v2", vec![], vec![1.0, 0.0, 0.0]);
6498 assert!(result.is_err());
6499 }
6500
6501 #[test]
6504 fn test_episodic_store_has_episodes_false_when_empty() {
6505 let store = EpisodicStore::new();
6506 let id = AgentId::new("agent-x");
6507 assert!(!store.has_episodes(&id).unwrap());
6508 }
6509
6510 #[test]
6511 fn test_episodic_store_has_episodes_true_after_recording() {
6512 let store = EpisodicStore::new();
6513 let id = AgentId::new("agent-y");
6514 store.add_episode(id.clone(), "e1", 0.8).unwrap();
6515 assert!(store.has_episodes(&id).unwrap());
6516 }
6517
6518 #[test]
6519 fn test_semantic_store_value_for_none_when_missing() {
6520 let store = SemanticStore::new();
6521 assert!(store.value_for("missing-key").unwrap().is_none());
6522 }
6523
6524 #[test]
6525 fn test_semantic_store_value_for_returns_stored_value() {
6526 let store = SemanticStore::new();
6527 store.store("mykey", "myvalue", vec![]).unwrap();
6528 assert_eq!(store.value_for("mykey").unwrap(), Some("myvalue".to_string()));
6529 }
6530
6531 #[test]
6532 fn test_working_memory_count_above_value_length_zero_when_empty() {
6533 let wm = WorkingMemory::new(10).unwrap();
6534 assert_eq!(wm.count_above_value_length(5).unwrap(), 0);
6535 }
6536
6537 #[test]
6538 fn test_working_memory_count_above_value_length_counts_correctly() {
6539 let wm = WorkingMemory::new(10).unwrap();
6540 wm.set("k1", "short").unwrap(); wm.set("k2", "a longer value").unwrap(); wm.set("k3", "hi").unwrap(); assert_eq!(wm.count_above_value_length(5).unwrap(), 1);
6545 }
6546
6547 #[test]
6550 fn test_total_episode_count_zero_when_empty() {
6551 let store = EpisodicStore::new();
6552 assert_eq!(store.total_episode_count().unwrap(), 0);
6553 }
6554
6555 #[test]
6556 fn test_total_episode_count_sums_across_agents() {
6557 let store = EpisodicStore::new();
6558 let a1 = AgentId::new("a1");
6559 let a2 = AgentId::new("a2");
6560 store.add_episode(a1.clone(), "e1", 0.5).unwrap();
6561 store.add_episode(a1.clone(), "e2", 0.6).unwrap();
6562 store.add_episode(a2.clone(), "e3", 0.7).unwrap();
6563 assert_eq!(store.total_episode_count().unwrap(), 3);
6564 }
6565
6566 #[test]
6569 fn test_agents_with_min_episodes_empty_when_below_min() {
6570 let store = EpisodicStore::new();
6571 let a = AgentId::new("a1");
6572 store.add_episode(a.clone(), "e1", 0.5).unwrap();
6573 assert!(store.agents_with_min_episodes(2).unwrap().is_empty());
6575 }
6576
6577 #[test]
6578 fn test_agents_with_min_episodes_includes_qualifying_agents() {
6579 let store = EpisodicStore::new();
6580 let a1 = AgentId::new("a1");
6581 let a2 = AgentId::new("a2");
6582 store.add_episode(a1.clone(), "e1", 0.5).unwrap();
6583 store.add_episode(a1.clone(), "e2", 0.6).unwrap();
6584 store.add_episode(a2.clone(), "only-one", 0.7).unwrap();
6585 let result = store.agents_with_min_episodes(2).unwrap();
6586 assert_eq!(result, vec![a1]);
6587 }
6588
6589 #[test]
6590 fn test_entries_with_no_tags_returns_empty_list_when_all_have_tags() {
6591 let store = SemanticStore::new();
6592 store.store("k1", "v1", vec!["tag".to_string()]).unwrap();
6593 assert!(store.entries_with_no_tags().unwrap().is_empty());
6594 }
6595
6596 #[test]
6597 fn test_entries_with_no_tags_returns_untagged_keys() {
6598 let store = SemanticStore::new();
6599 store.store("k1", "v1", vec![]).unwrap();
6600 store.store("k2", "v2", vec!["tag".to_string()]).unwrap();
6601 let result = store.entries_with_no_tags().unwrap();
6602 assert_eq!(result, vec!["k1".to_string()]);
6603 }
6604
6605 #[test]
6606 fn test_working_memory_longest_value_key_none_when_empty() {
6607 let wm = WorkingMemory::new(10).unwrap();
6608 assert!(wm.longest_value_key().unwrap().is_none());
6609 }
6610
6611 #[test]
6612 fn test_working_memory_longest_value_key_returns_key_with_longest_value() {
6613 let wm = WorkingMemory::new(10).unwrap();
6614 wm.set("short_key", "hi").unwrap();
6615 wm.set("long_key", "a much longer value string").unwrap();
6616 assert_eq!(wm.longest_value_key().unwrap(), Some("long_key".to_string()));
6617 }
6618
6619 #[test]
6622 fn test_agent_with_most_episodes_none_when_empty() {
6623 let store = EpisodicStore::new();
6624 assert!(store.agent_with_most_episodes().unwrap().is_none());
6625 }
6626
6627 #[test]
6628 fn test_agent_with_most_episodes_returns_agent_with_most() {
6629 let store = EpisodicStore::new();
6630 let a1 = AgentId::new("a1");
6631 let a2 = AgentId::new("a2");
6632 store.add_episode(a1.clone(), "e1", 0.5).unwrap();
6633 store.add_episode(a2.clone(), "e1", 0.5).unwrap();
6634 store.add_episode(a2.clone(), "e2", 0.6).unwrap();
6635 assert_eq!(store.agent_with_most_episodes().unwrap(), Some(a2));
6636 }
6637
6638 #[test]
6639 fn test_most_tagged_key_none_when_empty() {
6640 let store = SemanticStore::new();
6641 assert!(store.most_tagged_key().unwrap().is_none());
6642 }
6643
6644 #[test]
6645 fn test_most_tagged_key_returns_key_with_most_tags() {
6646 let store = SemanticStore::new();
6647 store.store("k1", "v1", vec!["a".to_string()]).unwrap();
6648 store.store("k2", "v2", vec!["a".to_string(), "b".to_string(), "c".to_string()]).unwrap();
6649 store.store("k3", "v3", vec![]).unwrap();
6650 assert_eq!(store.most_tagged_key().unwrap(), Some("k2".to_string()));
6651 }
6652
6653 #[test]
6654 fn test_value_lengths_empty_when_empty() {
6655 let wm = WorkingMemory::new(10).unwrap();
6656 assert!(wm.value_lengths().unwrap().is_empty());
6657 }
6658
6659 #[test]
6660 fn test_value_lengths_returns_all_pairs() {
6661 let wm = WorkingMemory::new(10).unwrap();
6662 wm.set("k", "hello").unwrap();
6663 let lengths = wm.value_lengths().unwrap();
6664 assert_eq!(lengths.len(), 1);
6665 assert_eq!(lengths[0], ("k".to_string(), 5));
6666 }
6667
6668 #[test]
6671 fn test_importance_variance_for_zero_when_fewer_than_two() {
6672 let store = EpisodicStore::new();
6673 let id = AgentId::new("a");
6674 store.add_episode(id.clone(), "e", 0.5).unwrap();
6675 assert!((store.importance_variance_for(&id).unwrap() - 0.0).abs() < 1e-6);
6676 }
6677
6678 #[test]
6679 fn test_importance_variance_for_nonzero_with_spread() {
6680 let store = EpisodicStore::new();
6681 let id = AgentId::new("a");
6682 store.add_episode(id.clone(), "e1", 0.0).unwrap();
6683 store.add_episode(id.clone(), "e2", 1.0).unwrap();
6684 let v = store.importance_variance_for(&id).unwrap();
6686 assert!((v - 0.25).abs() < 1e-5);
6687 }
6688
6689 #[test]
6690 fn test_count_matching_value_zero_when_no_match() {
6691 let store = SemanticStore::new();
6692 store.store("k", "hello world", vec![]).unwrap();
6693 assert_eq!(store.count_matching_value("xyz").unwrap(), 0);
6694 }
6695
6696 #[test]
6697 fn test_count_matching_value_counts_containing_entries() {
6698 let store = SemanticStore::new();
6699 store.store("k1", "hello world", vec![]).unwrap();
6700 store.store("k2", "world peace", vec![]).unwrap();
6701 store.store("k3", "goodbye", vec![]).unwrap();
6702 assert_eq!(store.count_matching_value("world").unwrap(), 2);
6703 }
6704
6705 #[test]
6706 fn test_keys_with_value_longer_than_empty_when_all_short() {
6707 let wm = WorkingMemory::new(10).unwrap();
6708 wm.set("k", "hi").unwrap();
6709 assert!(wm.keys_with_value_longer_than(10).unwrap().is_empty());
6710 }
6711
6712 #[test]
6713 fn test_keys_with_value_longer_than_returns_qualifying_keys() {
6714 let wm = WorkingMemory::new(10).unwrap();
6715 wm.set("short", "hi").unwrap();
6716 wm.set("long", "this is a longer value").unwrap();
6717 let keys = wm.keys_with_value_longer_than(5).unwrap();
6718 assert_eq!(keys, vec!["long".to_string()]);
6719 }
6720
6721 #[test]
6722 fn test_episodic_store_max_importance_overall_returns_highest() {
6723 let store = EpisodicStore::new();
6724 let agent = AgentId::new("a");
6725 store.add_episode(agent.clone(), "low", 0.2).unwrap();
6726 store.add_episode(agent.clone(), "high", 0.9).unwrap();
6727 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
6728 let max = store.max_importance_overall().unwrap();
6729 assert!((max.unwrap() - 0.9).abs() < 1e-6);
6730 }
6731
6732 #[test]
6733 fn test_episodic_store_max_importance_overall_empty_returns_none() {
6734 let store = EpisodicStore::new();
6735 assert!(store.max_importance_overall().unwrap().is_none());
6736 }
6737
6738 #[test]
6739 fn test_semantic_store_rename_tag_updates_all_occurrences() {
6740 let store = SemanticStore::new();
6741 store.store("k1", "v1", vec!["old".to_string(), "x".to_string()]).unwrap();
6742 store.store("k2", "v2", vec!["old".to_string()]).unwrap();
6743 let count = store.rename_tag("old", "new").unwrap();
6744 assert_eq!(count, 2);
6745 }
6746
6747 #[test]
6748 fn test_semantic_store_rename_tag_nonexistent_returns_zero() {
6749 let store = SemanticStore::new();
6750 store.store("k", "v", vec!["alpha".to_string()]).unwrap();
6751 assert_eq!(store.rename_tag("missing", "new").unwrap(), 0);
6752 }
6753
6754 #[test]
6755 fn test_working_memory_entry_count_reflects_stored_entries() {
6756 let wm = WorkingMemory::new(10).unwrap();
6757 assert_eq!(wm.entry_count().unwrap(), 0);
6758 wm.set("a", "1").unwrap();
6759 wm.set("b", "2").unwrap();
6760 assert_eq!(wm.entry_count().unwrap(), 2);
6761 }
6762
6763 #[test]
6764 fn test_episode_count_for_returns_correct_count() {
6765 let store = EpisodicStore::new();
6766 let agent = AgentId::new("a");
6767 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
6768 store.add_episode(agent.clone(), "e2", 0.5).unwrap();
6769 assert_eq!(store.episode_count_for(&agent).unwrap(), 2);
6770 }
6771
6772 #[test]
6773 fn test_episode_count_for_unknown_agent_returns_zero() {
6774 let store = EpisodicStore::new();
6775 assert_eq!(store.episode_count_for(&AgentId::new("x")).unwrap(), 0);
6776 }
6777
6778 #[test]
6779 fn test_semantic_store_unique_tags_returns_sorted_distinct_tags() {
6780 let store = SemanticStore::new();
6781 store.store("k1", "v1", vec!["b".to_string(), "a".to_string()]).unwrap();
6782 store.store("k2", "v2", vec!["a".to_string(), "c".to_string()]).unwrap();
6783 assert_eq!(store.unique_tags().unwrap(), vec!["a", "b", "c"]);
6784 }
6785
6786 #[test]
6787 fn test_semantic_store_unique_tags_empty_returns_empty() {
6788 let store = SemanticStore::new();
6789 assert!(store.unique_tags().unwrap().is_empty());
6790 }
6791
6792 #[test]
6793 fn test_working_memory_count_matching_prefix_counts_correctly() {
6794 let wm = WorkingMemory::new(10).unwrap();
6795 wm.set("user:a", "1").unwrap();
6796 wm.set("user:b", "2").unwrap();
6797 wm.set("other", "3").unwrap();
6798 assert_eq!(wm.count_matching_prefix("user:").unwrap(), 2);
6799 assert_eq!(wm.count_matching_prefix("other").unwrap(), 1);
6800 assert_eq!(wm.count_matching_prefix("none").unwrap(), 0);
6801 }
6802
6803 #[test]
6804 fn test_episodic_store_total_content_bytes_sums_lengths() {
6805 let store = EpisodicStore::new();
6806 let agent = AgentId::new("a");
6807 store.add_episode(agent.clone(), "hi", 0.5).unwrap(); store.add_episode(agent.clone(), "hello", 0.5).unwrap(); assert_eq!(store.total_content_bytes(&agent).unwrap(), 7);
6810 }
6811
6812 #[test]
6813 fn test_episodic_store_total_content_bytes_unknown_agent_returns_zero() {
6814 let store = EpisodicStore::new();
6815 assert_eq!(store.total_content_bytes(&AgentId::new("x")).unwrap(), 0);
6816 }
6817
6818 #[test]
6819 fn test_episodic_store_avg_content_length_correct_mean() {
6820 let store = EpisodicStore::new();
6821 let agent = AgentId::new("a");
6822 store.add_episode(agent.clone(), "hi", 0.5).unwrap(); store.add_episode(agent.clone(), "hello", 0.5).unwrap(); assert!((store.avg_content_length(&agent).unwrap() - 3.5).abs() < 1e-9);
6825 }
6826
6827 #[test]
6828 fn test_episodic_store_avg_content_length_empty_returns_zero() {
6829 let store = EpisodicStore::new();
6830 assert_eq!(store.avg_content_length(&AgentId::new("x")).unwrap(), 0.0);
6831 }
6832
6833 #[test]
6834 fn test_semantic_store_keys_for_tag_returns_matching_keys() {
6835 let store = SemanticStore::new();
6836 store.store("k1", "v1", vec!["rust".to_string()]).unwrap();
6837 store.store("k2", "v2", vec!["python".to_string()]).unwrap();
6838 store.store("k3", "v3", vec!["rust".to_string(), "async".to_string()]).unwrap();
6839 let mut keys = store.keys_for_tag("rust").unwrap();
6840 keys.sort_unstable();
6841 assert_eq!(keys, vec!["k1", "k3"]);
6842 }
6843
6844 #[test]
6845 fn test_semantic_store_keys_for_tag_nonexistent_tag_returns_empty() {
6846 let store = SemanticStore::new();
6847 store.store("k", "v", vec!["rust".to_string()]).unwrap();
6848 assert!(store.keys_for_tag("missing").unwrap().is_empty());
6849 }
6850
6851 #[test]
6852 fn test_count_episodes_with_tag_returns_correct_count() {
6853 let store = EpisodicStore::new();
6854 let agent = AgentId::new("a");
6855 store.add_episode_with_tags(agent.clone(), "e1", 0.5, vec!["ai".to_string()]).unwrap();
6856 store.add_episode_with_tags(agent.clone(), "e2", 0.5, vec!["ai".to_string(), "ml".to_string()]).unwrap();
6857 store.add_episode_with_tags(agent.clone(), "e3", 0.5, vec!["ml".to_string()]).unwrap();
6858 assert_eq!(store.count_episodes_with_tag(&agent, "ai").unwrap(), 2);
6859 assert_eq!(store.count_episodes_with_tag(&agent, "ml").unwrap(), 2);
6860 }
6861
6862 #[test]
6863 fn test_episodes_with_content_returns_matching_content() {
6864 let store = EpisodicStore::new();
6865 let agent = AgentId::new("a");
6866 store.add_episode(agent.clone(), "rust is great", 0.5).unwrap();
6867 store.add_episode(agent.clone(), "python is fun", 0.5).unwrap();
6868 store.add_episode(agent.clone(), "rust and python", 0.5).unwrap();
6869 let matches = store.episodes_with_content(&agent, "rust").unwrap();
6870 assert_eq!(matches.len(), 2);
6871 }
6872
6873 #[test]
6874 fn test_semantic_store_most_common_tag_returns_most_frequent() {
6875 let store = SemanticStore::new();
6876 store.store("k1", "v1", vec!["a".to_string(), "b".to_string()]).unwrap();
6877 store.store("k2", "v2", vec!["a".to_string()]).unwrap();
6878 store.store("k3", "v3", vec!["b".to_string()]).unwrap();
6879 let tag = store.most_common_tag().unwrap();
6881 assert!(tag.is_some());
6882 }
6883
6884 #[test]
6885 fn test_semantic_store_most_common_tag_empty_returns_none() {
6886 let store = SemanticStore::new();
6887 assert!(store.most_common_tag().unwrap().is_none());
6888 }
6889
6890 #[test]
6891 fn test_working_memory_pairs_starting_with_returns_matching_pairs() {
6892 let wm = WorkingMemory::new(10).unwrap();
6893 wm.set("user:name", "alice").unwrap();
6894 wm.set("user:age", "30").unwrap();
6895 wm.set("sys:mode", "prod").unwrap();
6896 let pairs = wm.pairs_starting_with("user:").unwrap();
6897 assert_eq!(pairs.len(), 2);
6898 assert!(pairs.iter().all(|(k, _)| k.starts_with("user:")));
6899 }
6900
6901 #[test]
6902 fn test_working_memory_total_key_bytes_sums_key_lengths() {
6903 let wm = WorkingMemory::new(10).unwrap();
6904 wm.set("ab", "x").unwrap(); wm.set("cde", "y").unwrap(); assert_eq!(wm.total_key_bytes().unwrap(), 5);
6907 }
6908
6909 #[test]
6910 fn test_working_memory_min_key_length_returns_shortest() {
6911 let wm = WorkingMemory::new(10).unwrap();
6912 wm.set("ab", "x").unwrap();
6913 wm.set("abcd", "y").unwrap();
6914 assert_eq!(wm.min_key_length().unwrap(), 2);
6915 }
6916
6917 #[test]
6918 fn test_working_memory_min_key_length_empty_returns_zero() {
6919 let wm = WorkingMemory::new(10).unwrap();
6920 assert_eq!(wm.min_key_length().unwrap(), 0);
6921 }
6922
6923 #[test]
6924 fn test_episodic_store_content_lengths_returns_lengths_in_order() {
6925 let store = EpisodicStore::new();
6926 let agent = AgentId::new("a");
6927 store.add_episode(agent.clone(), "hi", 0.5).unwrap(); store.add_episode(agent.clone(), "hello", 0.5).unwrap(); assert_eq!(store.content_lengths(&agent).unwrap(), vec![2, 5]);
6930 }
6931
6932 #[test]
6933 fn test_semantic_store_remove_entries_with_tag_removes_matching() {
6934 let store = SemanticStore::new();
6935 store.store("k1", "v1", vec!["old".to_string()]).unwrap();
6936 store.store("k2", "v2", vec!["keep".to_string()]).unwrap();
6937 store.store("k3", "v3", vec!["old".to_string(), "keep".to_string()]).unwrap();
6938 let removed = store.remove_entries_with_tag("old").unwrap();
6939 assert_eq!(removed, 2);
6940 assert_eq!(store.len().unwrap(), 1);
6941 }
6942
6943 #[test]
6946 fn test_episodic_store_max_content_length_returns_longest() {
6947 let store = EpisodicStore::new();
6948 let agent = AgentId::new("a");
6949 store.add_episode(agent.clone(), "hi", 0.5).unwrap(); store.add_episode(agent.clone(), "hello!", 0.5).unwrap(); store.add_episode(agent.clone(), "yo", 0.5).unwrap(); assert_eq!(store.max_content_length(&agent).unwrap(), 6);
6953 }
6954
6955 #[test]
6956 fn test_episodic_store_max_content_length_unknown_agent_returns_zero() {
6957 let store = EpisodicStore::new();
6958 assert_eq!(store.max_content_length(&AgentId::new("x")).unwrap(), 0);
6959 }
6960
6961 #[test]
6962 fn test_episodic_store_min_content_length_returns_shortest() {
6963 let store = EpisodicStore::new();
6964 let agent = AgentId::new("a");
6965 store.add_episode(agent.clone(), "hi", 0.5).unwrap(); store.add_episode(agent.clone(), "hello!", 0.5).unwrap(); assert_eq!(store.min_content_length(&agent).unwrap(), 2);
6968 }
6969
6970 #[test]
6971 fn test_episodic_store_min_content_length_unknown_agent_returns_zero() {
6972 let store = EpisodicStore::new();
6973 assert_eq!(store.min_content_length(&AgentId::new("x")).unwrap(), 0);
6974 }
6975
6976 #[test]
6977 fn test_semantic_store_total_value_bytes_sums_value_lengths() {
6978 let store = SemanticStore::new();
6979 store.store("k1", "ab", vec![]).unwrap(); store.store("k2", "cde", vec![]).unwrap(); store.store("k3", "fghij", vec![]).unwrap(); assert_eq!(store.total_value_bytes().unwrap(), 10);
6983 }
6984
6985 #[test]
6986 fn test_semantic_store_total_value_bytes_empty_returns_zero() {
6987 let store = SemanticStore::new();
6988 assert_eq!(store.total_value_bytes().unwrap(), 0);
6989 }
6990
6991 #[test]
6994 fn test_episodic_store_agents_with_episodes_returns_sorted_ids() {
6995 let store = EpisodicStore::new();
6996 let b = AgentId::new("b");
6997 let a = AgentId::new("a");
6998 store.add_episode(b.clone(), "episode b", 0.5).unwrap();
6999 store.add_episode(a.clone(), "episode a", 0.5).unwrap();
7000 let agents = store.agents_with_episodes().unwrap();
7001 assert_eq!(agents, vec![a, b]);
7002 }
7003
7004 #[test]
7005 fn test_episodic_store_agents_with_episodes_empty_returns_empty() {
7006 let store = EpisodicStore::new();
7007 assert!(store.agents_with_episodes().unwrap().is_empty());
7008 }
7009
7010 #[test]
7011 fn test_episodic_store_high_importance_count_counts_above_threshold() {
7012 let store = EpisodicStore::new();
7013 let agent = AgentId::new("a");
7014 store.add_episode(agent.clone(), "low", 0.3).unwrap();
7015 store.add_episode(agent.clone(), "high", 0.8).unwrap();
7016 store.add_episode(agent.clone(), "med", 0.6).unwrap();
7017 assert_eq!(store.high_importance_count(&agent, 0.5).unwrap(), 2);
7018 }
7019
7020 #[test]
7021 fn test_episodic_store_high_importance_count_unknown_agent_returns_zero() {
7022 let store = EpisodicStore::new();
7023 assert_eq!(store.high_importance_count(&AgentId::new("x"), 0.5).unwrap(), 0);
7024 }
7025
7026 #[test]
7027 fn test_semantic_store_avg_value_bytes_returns_mean() {
7028 let store = SemanticStore::new();
7029 store.store("k1", "ab", vec![]).unwrap(); store.store("k2", "abcd", vec![]).unwrap(); let avg = store.avg_value_bytes().unwrap();
7032 assert!((avg - 3.0).abs() < 1e-9);
7033 }
7034
7035 #[test]
7036 fn test_semantic_store_avg_value_bytes_empty_returns_zero() {
7037 let store = SemanticStore::new();
7038 assert_eq!(store.avg_value_bytes().unwrap(), 0.0);
7039 }
7040
7041 #[test]
7042 fn test_semantic_store_max_value_bytes_returns_longest() {
7043 let store = SemanticStore::new();
7044 store.store("k1", "hi", vec![]).unwrap(); store.store("k2", "hello!", vec![]).unwrap(); assert_eq!(store.max_value_bytes().unwrap(), 6);
7047 }
7048
7049 #[test]
7050 fn test_semantic_store_max_value_bytes_empty_returns_zero() {
7051 let store = SemanticStore::new();
7052 assert_eq!(store.max_value_bytes().unwrap(), 0);
7053 }
7054
7055 #[test]
7058 fn test_episodic_store_content_contains_count_counts_matches() {
7059 let store = EpisodicStore::new();
7060 let agent = AgentId::new("a");
7061 store.add_episode(agent.clone(), "rust is great", 0.5).unwrap();
7062 store.add_episode(agent.clone(), "python is ok", 0.5).unwrap();
7063 store.add_episode(agent.clone(), "rust rocks", 0.5).unwrap();
7064 assert_eq!(store.content_contains_count(&agent, "rust").unwrap(), 2);
7065 assert_eq!(store.content_contains_count(&agent, "java").unwrap(), 0);
7066 }
7067
7068 #[test]
7069 fn test_episodic_store_content_contains_count_unknown_agent_returns_zero() {
7070 let store = EpisodicStore::new();
7071 assert_eq!(store.content_contains_count(&AgentId::new("x"), "anything").unwrap(), 0);
7072 }
7073
7074 #[test]
7075 fn test_semantic_store_min_value_bytes_returns_shortest() {
7076 let store = SemanticStore::new();
7077 store.store("k1", "hello world", vec![]).unwrap(); store.store("k2", "hi", vec![]).unwrap(); assert_eq!(store.min_value_bytes().unwrap(), 2);
7080 }
7081
7082 #[test]
7083 fn test_semantic_store_min_value_bytes_empty_returns_zero() {
7084 let store = SemanticStore::new();
7085 assert_eq!(store.min_value_bytes().unwrap(), 0);
7086 }
7087
7088 #[test]
7089 fn test_working_memory_max_value_length_returns_longest() {
7090 let wm = WorkingMemory::new(10).unwrap();
7091 wm.set("k1", "ab").unwrap(); wm.set("k2", "abcde").unwrap(); assert_eq!(wm.max_value_length().unwrap(), 5);
7094 }
7095
7096 #[test]
7097 fn test_working_memory_max_value_length_empty_returns_zero() {
7098 let wm = WorkingMemory::new(10).unwrap();
7099 assert_eq!(wm.max_value_length().unwrap(), 0);
7100 }
7101
7102 #[test]
7105 fn test_episodic_store_episodes_by_importance_returns_desc_order() {
7106 let store = EpisodicStore::new();
7107 let agent = AgentId::new("a");
7108 store.add_episode(agent.clone(), "low", 0.2).unwrap();
7109 store.add_episode(agent.clone(), "high", 0.9).unwrap();
7110 store.add_episode(agent.clone(), "med", 0.5).unwrap();
7111 let contents = store.episodes_by_importance(&agent).unwrap();
7112 assert_eq!(contents[0], "high");
7113 assert_eq!(contents[2], "low");
7114 }
7115
7116 #[test]
7117 fn test_episodic_store_episodes_by_importance_empty_agent_returns_empty() {
7118 let store = EpisodicStore::new();
7119 assert!(store.episodes_by_importance(&AgentId::new("x")).unwrap().is_empty());
7120 }
7121
7122 #[test]
7123 fn test_semantic_store_all_keys_returns_sorted_keys() {
7124 let store = SemanticStore::new();
7125 store.store("banana", "v1", vec![]).unwrap();
7126 store.store("apple", "v2", vec![]).unwrap();
7127 store.store("cherry", "v3", vec![]).unwrap();
7128 assert_eq!(store.all_keys().unwrap(), vec!["apple", "banana", "cherry"]);
7129 }
7130
7131 #[test]
7132 fn test_semantic_store_all_keys_empty_returns_empty() {
7133 let store = SemanticStore::new();
7134 assert!(store.all_keys().unwrap().is_empty());
7135 }
7136
7137 #[test]
7138 fn test_working_memory_min_value_length_returns_shortest() {
7139 let wm = WorkingMemory::new(10).unwrap();
7140 wm.set("k1", "ab").unwrap(); wm.set("k2", "abcde").unwrap(); assert_eq!(wm.min_value_length().unwrap(), 2);
7143 }
7144
7145 #[test]
7146 fn test_working_memory_min_value_length_empty_returns_zero() {
7147 let wm = WorkingMemory::new(10).unwrap();
7148 assert_eq!(wm.min_value_length().unwrap(), 0);
7149 }
7150}