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 pub fn content_len(&self) -> usize {
104 self.content.len()
105 }
106
107 pub fn tag_count(&self) -> usize {
109 self.tags.len()
110 }
111
112 pub fn add_tag(&mut self, tag: impl Into<String>) -> bool {
116 let tag = tag.into();
117 if self.tags.iter().any(|t| t == &tag) {
118 return false;
119 }
120 self.tags.push(tag);
121 true
122 }
123
124 pub fn remove_tag(&mut self, tag: &str) -> bool {
128 if let Some(pos) = self.tags.iter().position(|t| t == tag) {
129 self.tags.swap_remove(pos);
130 true
131 } else {
132 false
133 }
134 }
135
136 pub fn is_high_importance(&self, threshold: f32) -> bool {
143 self.importance > threshold
144 }
145}
146
147impl std::fmt::Display for MemoryItem {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 write!(
150 f,
151 "[{}] importance={:.2} recalls={} content=\"{}\"",
152 self.id,
153 self.importance,
154 self.recall_count,
155 self.content
156 )
157 }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct DecayPolicy {
165 half_life_hours: f64,
167}
168
169impl DecayPolicy {
170 pub fn exponential(half_life_hours: f64) -> Result<Self, AgentRuntimeError> {
179 if half_life_hours <= 0.0 {
180 return Err(AgentRuntimeError::Memory(
181 "half_life_hours must be positive".into(),
182 ));
183 }
184 Ok(Self { half_life_hours })
185 }
186
187 pub fn apply(&self, importance: f32, age_hours: f64) -> f32 {
196 let decay = (-age_hours * std::f64::consts::LN_2 / self.half_life_hours).exp();
197 (importance as f64 * decay).clamp(0.0, 1.0) as f32
198 }
199
200 pub fn half_life_hours(&self) -> f64 {
202 self.half_life_hours
203 }
204
205 pub fn decay_item(&self, item: &mut MemoryItem) {
207 let age_hours = (Utc::now() - item.timestamp).num_seconds().max(0) as f64 / 3600.0;
208 item.importance = self.apply(item.importance, age_hours);
209 }
210}
211
212impl std::fmt::Display for DecayPolicy {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 write!(f, "Exponential(half_life={:.1}h)", self.half_life_hours)
216 }
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
247pub enum RecallPolicy {
248 Importance,
250 Hybrid {
256 recency_weight: f32,
258 frequency_weight: f32,
260 },
261}
262
263impl Default for RecallPolicy {
264 fn default() -> Self {
265 RecallPolicy::Importance
266 }
267}
268
269fn compute_hybrid_score(
272 item: &MemoryItem,
273 recency_weight: f32,
274 frequency_weight: f32,
275 max_recall: u64,
276 now: chrono::DateTime<Utc>,
277) -> f32 {
278 let age_hours = (now - item.timestamp).num_seconds().max(0) as f64 / 3600.0;
279 let recency_score = (-age_hours / 24.0).exp() as f32;
280 let frequency_score = item.recall_count as f32 / (max_recall as f32 + 1.0);
281 item.importance + recency_score * recency_weight + frequency_score * frequency_weight
282}
283
284#[derive(Debug, Clone, PartialEq, Eq, Default)]
288pub enum EvictionPolicy {
289 #[default]
291 LowestImportance,
292 Oldest,
294}
295
296#[derive(Default)]
313pub struct EpisodicStoreBuilder {
314 decay: Option<DecayPolicy>,
315 recall_policy: Option<RecallPolicy>,
316 per_agent_capacity: Option<usize>,
317 max_age_hours: Option<f64>,
318 eviction_policy: Option<EvictionPolicy>,
319}
320
321impl EpisodicStoreBuilder {
322 pub fn decay(mut self, policy: DecayPolicy) -> Self {
324 self.decay = Some(policy);
325 self
326 }
327
328 pub fn recall_policy(mut self, policy: RecallPolicy) -> Self {
330 self.recall_policy = Some(policy);
331 self
332 }
333
334 pub fn per_agent_capacity(mut self, capacity: usize) -> Self {
336 assert!(capacity > 0, "per_agent_capacity must be > 0");
337 self.per_agent_capacity = Some(capacity);
338 self
339 }
340
341 pub fn try_per_agent_capacity(
349 mut self,
350 capacity: usize,
351 ) -> Result<Self, crate::error::AgentRuntimeError> {
352 if capacity == 0 {
353 return Err(crate::error::AgentRuntimeError::Memory(
354 "per_agent_capacity must be > 0".into(),
355 ));
356 }
357 self.per_agent_capacity = Some(capacity);
358 Ok(self)
359 }
360
361 pub fn max_age_hours(mut self, hours: f64) -> Result<Self, crate::error::AgentRuntimeError> {
363 if hours <= 0.0 {
364 return Err(crate::error::AgentRuntimeError::Memory(
365 "max_age_hours must be positive".into(),
366 ));
367 }
368 self.max_age_hours = Some(hours);
369 Ok(self)
370 }
371
372 pub fn eviction_policy(mut self, policy: EvictionPolicy) -> Self {
374 self.eviction_policy = Some(policy);
375 self
376 }
377
378 pub fn build(self) -> EpisodicStore {
380 if self.decay.is_some() {
385 if let Some(RecallPolicy::Hybrid { .. }) = &self.recall_policy {
386 tracing::warn!(
387 "EpisodicStore configured with both DecayPolicy and RecallPolicy::Hybrid \
388 — time-based decay is applied before hybrid scoring, resulting in a \
389 double time penalty. Set one or the other unless this is intentional."
390 );
391 }
392 }
393 EpisodicStore {
394 inner: Arc::new(Mutex::new(EpisodicInner {
395 items: HashMap::new(),
396 decay: self.decay,
397 recall_policy: self.recall_policy.unwrap_or(RecallPolicy::Importance),
398 per_agent_capacity: self.per_agent_capacity,
399 max_age_hours: self.max_age_hours,
400 eviction_policy: self.eviction_policy.unwrap_or_default(),
401 })),
402 }
403 }
404}
405
406#[derive(Debug, Clone)]
417pub struct EpisodicStore {
418 inner: Arc<Mutex<EpisodicInner>>,
419}
420
421#[derive(Debug)]
422struct EpisodicInner {
423 items: HashMap<AgentId, Vec<MemoryItem>>,
425 decay: Option<DecayPolicy>,
426 recall_policy: RecallPolicy,
427 per_agent_capacity: Option<usize>,
429 max_age_hours: Option<f64>,
431 eviction_policy: EvictionPolicy,
433}
434
435impl EpisodicInner {
436 fn purge_stale(&mut self, agent_id: &AgentId) {
438 if let Some(max_age_h) = self.max_age_hours {
439 let cutoff = Utc::now()
440 - chrono::Duration::seconds((max_age_h * 3600.0) as i64);
441 if let Some(agent_items) = self.items.get_mut(agent_id) {
442 agent_items.retain(|i| i.timestamp >= cutoff);
443 }
444 }
445 }
446}
447
448fn evict_if_over_capacity(
453 agent_items: &mut Vec<MemoryItem>,
454 cap: usize,
455 policy: &EvictionPolicy,
456) {
457 if agent_items.len() <= cap {
458 return;
459 }
460 let pos = match policy {
461 EvictionPolicy::LowestImportance => {
462 let len = agent_items.len();
463 agent_items[..len - 1]
464 .iter()
465 .enumerate()
466 .min_by(|(_, a), (_, b)| {
467 a.importance
468 .partial_cmp(&b.importance)
469 .unwrap_or(std::cmp::Ordering::Equal)
470 })
471 .map(|(pos, _)| pos)
472 }
473 EvictionPolicy::Oldest => {
474 let len = agent_items.len();
475 agent_items[..len - 1]
476 .iter()
477 .enumerate()
478 .min_by_key(|(_, item)| item.timestamp)
479 .map(|(pos, _)| pos)
480 }
481 };
482 if let Some(pos) = pos {
483 agent_items.remove(pos);
484 }
485}
486
487impl EpisodicStore {
488 pub fn new() -> Self {
490 Self {
491 inner: Arc::new(Mutex::new(EpisodicInner {
492 items: HashMap::new(),
493 decay: None,
494 recall_policy: RecallPolicy::Importance,
495 per_agent_capacity: None,
496 max_age_hours: None,
497 eviction_policy: EvictionPolicy::LowestImportance,
498 })),
499 }
500 }
501
502 pub fn builder() -> EpisodicStoreBuilder {
504 EpisodicStoreBuilder::default()
505 }
506
507 pub fn with_decay(policy: DecayPolicy) -> Self {
509 Self {
510 inner: Arc::new(Mutex::new(EpisodicInner {
511 items: HashMap::new(),
512 decay: Some(policy),
513 recall_policy: RecallPolicy::Importance,
514 per_agent_capacity: None,
515 max_age_hours: None,
516 eviction_policy: EvictionPolicy::LowestImportance,
517 })),
518 }
519 }
520
521 pub fn with_decay_and_recall_policy(decay: DecayPolicy, recall: RecallPolicy) -> Self {
530 if let RecallPolicy::Hybrid { .. } = &recall {
531 tracing::warn!(
532 "EpisodicStore::with_decay_and_recall_policy called with RecallPolicy::Hybrid \
533 — this applies a double time penalty. Set DecayPolicy OR Hybrid recency \
534 weighting, not both, unless the double penalty is intentional."
535 );
536 }
537 Self {
538 inner: Arc::new(Mutex::new(EpisodicInner {
539 items: HashMap::new(),
540 decay: Some(decay),
541 recall_policy: recall,
542 per_agent_capacity: None,
543 max_age_hours: None,
544 eviction_policy: EvictionPolicy::LowestImportance,
545 })),
546 }
547 }
548
549 pub fn with_recall_policy(policy: RecallPolicy) -> Self {
551 Self {
552 inner: Arc::new(Mutex::new(EpisodicInner {
553 items: HashMap::new(),
554 decay: None,
555 recall_policy: policy,
556 per_agent_capacity: None,
557 max_age_hours: None,
558 eviction_policy: EvictionPolicy::LowestImportance,
559 })),
560 }
561 }
562
563 pub fn with_per_agent_capacity(capacity: usize) -> Self {
582 assert!(capacity > 0, "per_agent_capacity must be > 0");
583 Self {
584 inner: Arc::new(Mutex::new(EpisodicInner {
585 items: HashMap::new(),
586 decay: None,
587 recall_policy: RecallPolicy::Importance,
588 per_agent_capacity: Some(capacity),
589 max_age_hours: None,
590 eviction_policy: EvictionPolicy::LowestImportance,
591 })),
592 }
593 }
594
595 pub fn try_with_per_agent_capacity(
603 capacity: usize,
604 ) -> Result<Self, AgentRuntimeError> {
605 if capacity == 0 {
606 return Err(AgentRuntimeError::Memory(
607 "per_agent_capacity must be > 0".into(),
608 ));
609 }
610 Ok(Self {
611 inner: Arc::new(Mutex::new(EpisodicInner {
612 items: HashMap::new(),
613 decay: None,
614 recall_policy: RecallPolicy::Importance,
615 per_agent_capacity: Some(capacity),
616 max_age_hours: None,
617 eviction_policy: EvictionPolicy::LowestImportance,
618 })),
619 })
620 }
621
622 pub fn with_max_age(max_age_hours: f64) -> Result<Self, AgentRuntimeError> {
630 if max_age_hours <= 0.0 {
631 return Err(AgentRuntimeError::Memory(
632 "max_age_hours must be positive".into(),
633 ));
634 }
635 Ok(Self {
636 inner: Arc::new(Mutex::new(EpisodicInner {
637 items: HashMap::new(),
638 decay: None,
639 recall_policy: RecallPolicy::Importance,
640 per_agent_capacity: None,
641 max_age_hours: Some(max_age_hours),
642 eviction_policy: EvictionPolicy::LowestImportance,
643 })),
644 })
645 }
646
647 pub fn with_eviction_policy(policy: EvictionPolicy) -> Self {
649 Self {
650 inner: Arc::new(Mutex::new(EpisodicInner {
651 items: HashMap::new(),
652 decay: None,
653 recall_policy: RecallPolicy::Importance,
654 per_agent_capacity: None,
655 max_age_hours: None,
656 eviction_policy: policy,
657 })),
658 }
659 }
660
661 #[tracing::instrument(skip(self))]
679 pub fn add_episode(
680 &self,
681 agent_id: AgentId,
682 content: impl Into<String> + std::fmt::Debug,
683 importance: f32,
684 ) -> Result<MemoryId, AgentRuntimeError> {
685 let item = MemoryItem::new(agent_id.clone(), content, importance, Vec::new());
686 let id = item.id.clone();
687 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::add_episode");
688
689 inner.purge_stale(&agent_id);
690 let cap = inner.per_agent_capacity; let eviction_policy = inner.eviction_policy.clone();
692 let agent_items = inner.items.entry(agent_id).or_default();
693 agent_items.push(item);
694
695 if let Some(cap) = cap {
696 evict_if_over_capacity(agent_items, cap, &eviction_policy);
697 }
698 Ok(id)
699 }
700
701 #[tracing::instrument(skip(self))]
708 pub fn add_episode_with_tags(
709 &self,
710 agent_id: AgentId,
711 content: impl Into<String> + std::fmt::Debug,
712 importance: f32,
713 tags: Vec<String>,
714 ) -> Result<MemoryId, AgentRuntimeError> {
715 let item = MemoryItem::new(agent_id.clone(), content, importance, tags);
716 let id = item.id.clone();
717 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::add_episode_with_tags");
718 inner.purge_stale(&agent_id);
719 let cap = inner.per_agent_capacity;
720 let eviction_policy = inner.eviction_policy.clone();
721 let agent_items = inner.items.entry(agent_id).or_default();
722 agent_items.push(item);
723 if let Some(cap) = cap {
724 evict_if_over_capacity(agent_items, cap, &eviction_policy);
725 }
726 Ok(id)
727 }
728
729 pub fn remove_by_id(
734 &self,
735 agent_id: &AgentId,
736 id: &MemoryId,
737 ) -> Result<bool, AgentRuntimeError> {
738 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::remove_by_id");
739 if let Some(items) = inner.items.get_mut(agent_id) {
740 if let Some(pos) = items.iter().position(|i| &i.id == id) {
741 items.remove(pos);
742 return Ok(true);
743 }
744 }
745 Ok(false)
746 }
747
748 pub fn update_tags_by_id(
752 &self,
753 agent_id: &AgentId,
754 id: &MemoryId,
755 new_tags: Vec<String>,
756 ) -> Result<bool, AgentRuntimeError> {
757 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::update_tags_by_id");
758 if let Some(items) = inner.items.get_mut(agent_id) {
759 if let Some(item) = items.iter_mut().find(|i| &i.id == id) {
760 item.tags = new_tags;
761 return Ok(true);
762 }
763 }
764 Ok(false)
765 }
766
767 pub fn max_importance_for(
771 &self,
772 agent_id: &AgentId,
773 ) -> Result<Option<f32>, AgentRuntimeError> {
774 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_importance_for");
775 let max = inner
776 .items
777 .get(agent_id)
778 .and_then(|items| {
779 items
780 .iter()
781 .map(|i| i.importance)
782 .reduce(f32::max)
783 });
784 Ok(max)
785 }
786
787 pub fn count_for(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
792 let inner = recover_lock(self.inner.lock(), "EpisodicStore::count_for");
793 Ok(inner.items.get(agent_id).map_or(0, |v| v.len()))
794 }
795
796 pub fn has_agent(&self, agent_id: &AgentId) -> Result<bool, AgentRuntimeError> {
800 let inner = recover_lock(self.inner.lock(), "EpisodicStore::has_agent");
801 Ok(inner.items.get(agent_id).map_or(false, |v| !v.is_empty()))
802 }
803
804 pub fn agents_with_min_episodes(&self, min: usize) -> Result<Vec<AgentId>, AgentRuntimeError> {
806 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agents_with_min_episodes");
807 let mut ids: Vec<AgentId> = inner
808 .items
809 .iter()
810 .filter(|(_, v)| v.len() >= min)
811 .map(|(id, _)| id.clone())
812 .collect();
813 ids.sort_by(|a, b| a.0.cmp(&b.0));
814 Ok(ids)
815 }
816
817 pub fn total_episode_count(&self) -> Result<usize, AgentRuntimeError> {
819 let inner = recover_lock(self.inner.lock(), "EpisodicStore::total_episode_count");
820 Ok(inner.items.values().map(|v| v.len()).sum())
821 }
822
823 pub fn agent_ids_with_episodes(&self) -> Result<Vec<AgentId>, AgentRuntimeError> {
827 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_ids_with_episodes");
828 let mut ids: Vec<AgentId> = inner
829 .items
830 .iter()
831 .filter(|(_, v)| !v.is_empty())
832 .map(|(k, _)| k.clone())
833 .collect();
834 ids.sort_by(|a, b| a.as_str().cmp(b.as_str()));
835 Ok(ids)
836 }
837
838 pub fn episode_importance_avg(&self, agent_id: &AgentId) -> Result<f64, AgentRuntimeError> {
842 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_importance_avg");
843 let items = match inner.items.get(agent_id) {
844 Some(v) if !v.is_empty() => v,
845 _ => return Ok(0.0),
846 };
847 let sum: f64 = items.iter().map(|m| m.importance as f64).sum();
848 Ok(sum / items.len() as f64)
849 }
850
851 pub fn agent_episode_counts(
855 &self,
856 ) -> Result<std::collections::HashMap<AgentId, usize>, AgentRuntimeError> {
857 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_episode_counts");
858 Ok(inner
859 .items
860 .iter()
861 .map(|(id, items)| (id.clone(), items.len()))
862 .collect())
863 }
864
865 pub fn episodes_for_agent_sorted_by_importance(
870 &self,
871 agent_id: &AgentId,
872 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
873 let inner =
874 recover_lock(self.inner.lock(), "EpisodicStore::episodes_for_agent_sorted_by_importance");
875 let mut items: Vec<MemoryItem> = inner
876 .items
877 .get(agent_id)
878 .map(|v| v.iter().cloned().collect())
879 .unwrap_or_default();
880 items.sort_by(|a, b| {
881 b.importance
882 .partial_cmp(&a.importance)
883 .unwrap_or(std::cmp::Ordering::Equal)
884 });
885 Ok(items)
886 }
887
888 pub fn episodes_after_timestamp(
893 &self,
894 agent_id: &AgentId,
895 after: DateTime<Utc>,
896 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
897 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_after_timestamp");
898 Ok(inner
899 .items
900 .get(agent_id)
901 .map(|v| v.iter().filter(|m| m.timestamp > after).cloned().collect())
902 .unwrap_or_default())
903 }
904
905 pub fn agent_with_min_importance_avg(&self) -> Result<Option<AgentId>, AgentRuntimeError> {
908 let inner =
909 recover_lock(self.inner.lock(), "EpisodicStore::agent_with_min_importance_avg");
910 Ok(inner
911 .items
912 .iter()
913 .filter(|(_, v)| !v.is_empty())
914 .map(|(id, items)| {
915 let avg = items.iter().map(|m| m.importance as f64).sum::<f64>()
916 / items.len() as f64;
917 (id.clone(), avg)
918 })
919 .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
920 .map(|(id, _)| id))
921 }
922
923 pub fn episodes_matching_content(
927 &self,
928 agent_id: &AgentId,
929 substr: &str,
930 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
931 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_matching_content");
932 Ok(inner
933 .items
934 .get(agent_id)
935 .map(|v| v.iter().filter(|m| m.content.contains(substr)).cloned().collect())
936 .unwrap_or_default())
937 }
938
939 pub fn top_agent_by_importance(&self) -> Result<Option<AgentId>, AgentRuntimeError> {
942 let inner = recover_lock(self.inner.lock(), "EpisodicStore::top_agent_by_importance");
943 Ok(inner
944 .items
945 .iter()
946 .map(|(id, items)| {
947 let total: f32 = items.iter().map(|m| m.importance).sum();
948 (id.clone(), total)
949 })
950 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
951 .map(|(id, _)| id))
952 }
953
954 pub fn highest_importance_episode(
957 &self,
958 agent_id: &AgentId,
959 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
960 let inner = recover_lock(self.inner.lock(), "EpisodicStore::highest_importance_episode");
961 Ok(inner
962 .items
963 .get(agent_id)
964 .and_then(|v| {
965 v.iter().max_by(|a, b| {
966 a.importance
967 .partial_cmp(&b.importance)
968 .unwrap_or(std::cmp::Ordering::Equal)
969 })
970 })
971 .cloned())
972 }
973
974 pub fn avg_episode_content_words(
979 &self,
980 agent_id: &AgentId,
981 ) -> Result<f64, AgentRuntimeError> {
982 let inner = recover_lock(self.inner.lock(), "EpisodicStore::avg_episode_content_words");
983 let items = match inner.items.get(agent_id) {
984 Some(v) if !v.is_empty() => v,
985 _ => return Ok(0.0),
986 };
987 let total: usize = items
988 .iter()
989 .map(|m| m.content.split_whitespace().count())
990 .sum();
991 Ok(total as f64 / items.len() as f64)
992 }
993
994 pub fn has_duplicate_content(
999 &self,
1000 agent_id: &AgentId,
1001 ) -> Result<bool, AgentRuntimeError> {
1002 let inner = recover_lock(self.inner.lock(), "EpisodicStore::has_duplicate_content");
1003 let items = match inner.items.get(agent_id) {
1004 Some(v) => v,
1005 None => return Ok(false),
1006 };
1007 let mut seen = std::collections::HashSet::new();
1008 Ok(items.iter().any(|m| !seen.insert(m.content.as_str())))
1009 }
1010
1011 pub fn episode_count_for(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1015 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_count_for");
1016 Ok(inner.items.get(agent_id).map_or(0, |v| v.len()))
1017 }
1018
1019 pub fn episodes_with_importance_above(
1022 &self,
1023 agent_id: &AgentId,
1024 min_importance: f32,
1025 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1026 let inner = recover_lock(
1027 self.inner.lock(),
1028 "EpisodicStore::episodes_with_importance_above",
1029 );
1030 Ok(inner
1031 .items
1032 .get(agent_id)
1033 .map(|v| v.iter().filter(|m| m.importance > min_importance).cloned().collect())
1034 .unwrap_or_default())
1035 }
1036
1037 pub fn agent_episode_importance_sum(
1042 &self,
1043 agent_id: &AgentId,
1044 ) -> Result<f64, AgentRuntimeError> {
1045 let inner = recover_lock(
1046 self.inner.lock(),
1047 "EpisodicStore::agent_episode_importance_sum",
1048 );
1049 Ok(inner
1050 .items
1051 .get(agent_id)
1052 .map(|v| v.iter().map(|m| m.importance as f64).sum())
1053 .unwrap_or(0.0))
1054 }
1055
1056 pub fn episodes_in_range(
1059 &self,
1060 agent_id: &AgentId,
1061 min_importance: f32,
1062 max_importance: f32,
1063 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1064 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_in_range");
1065 Ok(inner
1066 .items
1067 .get(agent_id)
1068 .map(|v| {
1069 v.iter()
1070 .filter(|m| m.importance >= min_importance && m.importance <= max_importance)
1071 .cloned()
1072 .collect()
1073 })
1074 .unwrap_or_default())
1075 }
1076
1077 pub fn agent_importance_range(
1080 &self,
1081 agent_id: &AgentId,
1082 ) -> Result<Option<(f32, f32)>, AgentRuntimeError> {
1083 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_importance_range");
1084 Ok(inner.items.get(agent_id).and_then(|v| {
1085 if v.is_empty() {
1086 return None;
1087 }
1088 let min = v.iter().map(|m| m.importance).fold(f32::INFINITY, f32::min);
1089 let max = v.iter().map(|m| m.importance).fold(f32::NEG_INFINITY, f32::max);
1090 Some((min, max))
1091 }))
1092 }
1093
1094 pub fn episodes_min_importance(
1097 &self,
1098 agent_id: &AgentId,
1099 ) -> Result<Option<f32>, AgentRuntimeError> {
1100 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_min_importance");
1101 Ok(inner.items.get(agent_id).and_then(|v| {
1102 v.iter()
1103 .map(|m| m.importance)
1104 .reduce(f32::min)
1105 }))
1106 }
1107
1108 pub fn episode_min_content_words(
1113 &self,
1114 agent_id: &AgentId,
1115 ) -> Result<usize, AgentRuntimeError> {
1116 let inner = recover_lock(
1117 self.inner.lock(),
1118 "EpisodicStore::episode_min_content_words",
1119 );
1120 Ok(inner
1121 .items
1122 .get(agent_id)
1123 .and_then(|v| v.iter().map(|m| m.content.split_whitespace().count()).min())
1124 .unwrap_or(0))
1125 }
1126
1127 pub fn agent_with_most_episodes(&self) -> Result<Option<AgentId>, AgentRuntimeError> {
1130 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_with_most_episodes");
1131 Ok(inner
1132 .items
1133 .iter()
1134 .max_by_key(|(_, v)| v.len())
1135 .map(|(id, _)| id.clone()))
1136 }
1137
1138 pub fn episode_max_content_words(
1143 &self,
1144 agent_id: &AgentId,
1145 ) -> Result<usize, AgentRuntimeError> {
1146 let inner = recover_lock(
1147 self.inner.lock(),
1148 "EpisodicStore::episode_max_content_words",
1149 );
1150 Ok(inner
1151 .items
1152 .get(agent_id)
1153 .and_then(|v| v.iter().map(|m| m.content.split_whitespace().count()).max())
1154 .unwrap_or(0))
1155 }
1156
1157 pub fn total_items(&self) -> Result<usize, AgentRuntimeError> {
1159 let inner = recover_lock(self.inner.lock(), "EpisodicStore::total_items");
1160 Ok(inner.items.values().map(|v| v.len()).sum())
1161 }
1162
1163 pub fn all_episodes(&self) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1166 let inner = recover_lock(self.inner.lock(), "EpisodicStore::all_episodes");
1167 Ok(inner.items.values().flat_map(|v| v.iter().cloned()).collect())
1168 }
1169
1170 pub fn agents(&self) -> Result<Vec<AgentId>, AgentRuntimeError> {
1172 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agents");
1173 let mut ids: Vec<AgentId> = inner
1174 .items
1175 .keys()
1176 .filter(|id| !inner.items[id].is_empty())
1177 .cloned()
1178 .collect();
1179 ids.sort_unstable_by(|a, b| a.0.cmp(&b.0));
1180 Ok(ids)
1181 }
1182
1183 pub fn min_episode_count(&self) -> Result<Option<(AgentId, usize)>, AgentRuntimeError> {
1186 let inner = recover_lock(self.inner.lock(), "EpisodicStore::min_episode_count");
1187 Ok(inner
1188 .items
1189 .iter()
1190 .filter(|(_, v)| !v.is_empty())
1191 .min_by_key(|(_, v)| v.len())
1192 .map(|(id, v)| (id.clone(), v.len())))
1193 }
1194
1195 pub fn max_importance_overall(&self) -> Result<Option<f32>, AgentRuntimeError> {
1198 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_importance_overall");
1199 let max = inner
1200 .items
1201 .values()
1202 .flat_map(|v| v.iter())
1203 .map(|e| e.importance)
1204 .reduce(f32::max);
1205 Ok(max)
1206 }
1207
1208 pub fn importance_variance_for(&self, agent_id: &AgentId) -> Result<f64, AgentRuntimeError> {
1212 let inner = recover_lock(self.inner.lock(), "EpisodicStore::importance_variance_for");
1213 let vals: Vec<f64> = inner
1214 .items
1215 .get(agent_id)
1216 .map(|v| v.iter().map(|e| e.importance as f64).collect())
1217 .unwrap_or_default();
1218 if vals.len() < 2 {
1219 return Ok(0.0);
1220 }
1221 let mean = vals.iter().sum::<f64>() / vals.len() as f64;
1222 let variance = vals.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / vals.len() as f64;
1223 Ok(variance)
1224 }
1225
1226 pub fn recall_top_n(
1231 &self,
1232 agent_id: &AgentId,
1233 n: usize,
1234 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1235 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_top_n");
1236 let mut items: Vec<MemoryItem> = inner
1237 .items
1238 .get(agent_id)
1239 .cloned()
1240 .unwrap_or_default();
1241 items.sort_unstable_by(|a, b| {
1242 b.importance
1243 .partial_cmp(&a.importance)
1244 .unwrap_or(std::cmp::Ordering::Equal)
1245 });
1246 items.truncate(n);
1247 Ok(items)
1248 }
1249
1250 pub fn filter_by_importance(
1253 &self,
1254 agent_id: &AgentId,
1255 min: f32,
1256 max: f32,
1257 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1258 let inner = recover_lock(self.inner.lock(), "EpisodicStore::filter_by_importance");
1259 let mut items: Vec<MemoryItem> = inner
1260 .items
1261 .get(agent_id)
1262 .map(|v| {
1263 v.iter()
1264 .filter(|i| i.importance >= min && i.importance <= max)
1265 .cloned()
1266 .collect()
1267 })
1268 .unwrap_or_default();
1269 items.sort_unstable_by(|a, b| {
1270 b.importance
1271 .partial_cmp(&a.importance)
1272 .unwrap_or(std::cmp::Ordering::Equal)
1273 });
1274 Ok(items)
1275 }
1276
1277 pub fn retain_top_n(&self, agent_id: &AgentId, n: usize) -> Result<usize, AgentRuntimeError> {
1282 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::retain_top_n");
1283 let items = inner.items.entry(agent_id.clone()).or_default();
1284 if items.len() <= n {
1285 return Ok(0);
1286 }
1287 items.sort_unstable_by(|a, b| {
1288 b.importance
1289 .partial_cmp(&a.importance)
1290 .unwrap_or(std::cmp::Ordering::Equal)
1291 });
1292 let removed = items.len() - n;
1293 items.truncate(n);
1294 Ok(removed)
1295 }
1296
1297 pub fn most_recent(&self, agent_id: &AgentId) -> Result<Option<MemoryItem>, AgentRuntimeError> {
1303 let inner = recover_lock(self.inner.lock(), "EpisodicStore::most_recent");
1304 Ok(inner
1305 .items
1306 .get(agent_id)
1307 .and_then(|v| v.last().cloned()))
1308 }
1309
1310 pub fn max_importance(&self, agent_id: &AgentId) -> Result<Option<f32>, AgentRuntimeError> {
1313 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_importance");
1314 Ok(inner
1315 .items
1316 .get(agent_id)
1317 .and_then(|v| {
1318 v.iter()
1319 .map(|i| i.importance)
1320 .reduce(f32::max)
1321 }))
1322 }
1323
1324 pub fn min_importance(&self, agent_id: &AgentId) -> Result<Option<f32>, AgentRuntimeError> {
1327 let inner = recover_lock(self.inner.lock(), "EpisodicStore::min_importance");
1328 Ok(inner
1329 .items
1330 .get(agent_id)
1331 .and_then(|v| {
1332 v.iter()
1333 .map(|i| i.importance)
1334 .reduce(f32::min)
1335 }))
1336 }
1337
1338 pub fn count_above_importance(
1343 &self,
1344 agent_id: &AgentId,
1345 threshold: f32,
1346 ) -> Result<usize, AgentRuntimeError> {
1347 let inner = recover_lock(self.inner.lock(), "EpisodicStore::count_above_importance");
1348 Ok(inner
1349 .items
1350 .get(agent_id)
1351 .map(|v| v.iter().filter(|i| i.importance > threshold).count())
1352 .unwrap_or(0))
1353 }
1354
1355 pub fn most_recalled(&self, agent_id: &AgentId) -> Result<Option<MemoryItem>, AgentRuntimeError> {
1360 let inner = recover_lock(self.inner.lock(), "EpisodicStore::most_recalled");
1361 Ok(inner
1362 .items
1363 .get(agent_id)
1364 .and_then(|v| v.iter().max_by_key(|i| i.recall_count))
1365 .cloned())
1366 }
1367
1368 pub fn importance_avg(&self, agent_id: &AgentId) -> Result<f32, AgentRuntimeError> {
1371 let inner = recover_lock(self.inner.lock(), "EpisodicStore::importance_avg");
1372 match inner.items.get(agent_id) {
1373 None => Ok(0.0),
1374 Some(items) if items.is_empty() => Ok(0.0),
1375 Some(items) => {
1376 let sum: f32 = items.iter().map(|i| i.importance).sum();
1377 Ok(sum / items.len() as f32)
1378 }
1379 }
1380 }
1381
1382 pub fn deduplicate_content(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1387 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::deduplicate_content");
1388 let Some(items) = inner.items.get_mut(agent_id) else {
1389 return Ok(0);
1390 };
1391 let before = items.len();
1392 let mut seen: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
1394 let mut keepers: Vec<usize> = Vec::new();
1395 for (idx, item) in items.iter().enumerate() {
1396 match seen.get(&item.content) {
1397 None => {
1398 seen.insert(item.content.clone(), keepers.len());
1399 keepers.push(idx);
1400 }
1401 Some(&pos) => {
1402 let kept_idx = keepers[pos];
1403 if item.importance > items[kept_idx].importance {
1404 keepers[pos] = idx;
1405 }
1406 }
1407 }
1408 }
1409 let kept: std::collections::HashSet<usize> = keepers.into_iter().collect();
1410 let mut i = 0;
1411 items.retain(|_| {
1412 let keep = kept.contains(&i);
1413 i += 1;
1414 keep
1415 });
1416 Ok(before - items.len())
1417 }
1418
1419 pub fn agent_ids(&self) -> Result<Vec<AgentId>, AgentRuntimeError> {
1421 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_ids");
1422 Ok(inner.items.keys().cloned().collect())
1423 }
1424
1425 pub fn agents_with_episodes_above_count(
1429 &self,
1430 min_count: usize,
1431 ) -> Result<Vec<AgentId>, AgentRuntimeError> {
1432 let inner = recover_lock(
1433 self.inner.lock(),
1434 "EpisodicStore::agents_with_episodes_above_count",
1435 );
1436 Ok(inner
1437 .items
1438 .iter()
1439 .filter(|(_, episodes)| episodes.len() > min_count)
1440 .map(|(id, _)| id.clone())
1441 .collect())
1442 }
1443
1444 pub fn find_by_content(
1447 &self,
1448 agent_id: &AgentId,
1449 pattern: &str,
1450 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1451 let inner = recover_lock(self.inner.lock(), "EpisodicStore::find_by_content");
1452 let mut matches: Vec<MemoryItem> = inner
1453 .items
1454 .get(agent_id)
1455 .map(|items| {
1456 items
1457 .iter()
1458 .filter(|i| i.content.contains(pattern))
1459 .cloned()
1460 .collect()
1461 })
1462 .unwrap_or_default();
1463 matches.sort_unstable_by(|a, b| {
1464 b.importance
1465 .partial_cmp(&a.importance)
1466 .unwrap_or(std::cmp::Ordering::Equal)
1467 });
1468 Ok(matches)
1469 }
1470
1471 pub fn clear_for(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1475 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::clear_for");
1476 let count = inner.items.remove(agent_id).map_or(0, |v| v.len());
1477 Ok(count)
1478 }
1479
1480 pub fn count_episodes_with_tag(
1482 &self,
1483 agent_id: &AgentId,
1484 tag: &str,
1485 ) -> Result<usize, AgentRuntimeError> {
1486 let inner = recover_lock(self.inner.lock(), "EpisodicStore::count_episodes_with_tag");
1487 let count = inner
1488 .items
1489 .get(agent_id)
1490 .map_or(0, |items| items.iter().filter(|i| i.has_tag(tag)).count());
1491 Ok(count)
1492 }
1493
1494 pub fn episodes_with_content(
1496 &self,
1497 agent_id: &AgentId,
1498 substring: &str,
1499 ) -> Result<Vec<String>, AgentRuntimeError> {
1500 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_with_content");
1501 let items = inner
1502 .items
1503 .get(agent_id)
1504 .map_or_else(Vec::new, |v| {
1505 v.iter()
1506 .filter(|i| i.content.contains(substring))
1507 .map(|i| i.content.clone())
1508 .collect()
1509 });
1510 Ok(items)
1511 }
1512
1513 pub fn max_content_length(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1517 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_content_length");
1518 Ok(inner
1519 .items
1520 .get(agent_id)
1521 .and_then(|v| v.iter().map(|i| i.content.len()).max())
1522 .unwrap_or(0))
1523 }
1524
1525 pub fn min_content_length(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1529 let inner = recover_lock(self.inner.lock(), "EpisodicStore::min_content_length");
1530 Ok(inner
1531 .items
1532 .get(agent_id)
1533 .and_then(|v| v.iter().map(|i| i.content.len()).min())
1534 .unwrap_or(0))
1535 }
1536
1537 pub fn episodes_by_importance(
1540 &self,
1541 agent_id: &AgentId,
1542 ) -> Result<Vec<String>, AgentRuntimeError> {
1543 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_by_importance");
1544 let mut items: Vec<(f32, String)> = inner
1545 .items
1546 .get(agent_id)
1547 .map_or_else(Vec::new, |v| {
1548 v.iter().map(|i| (i.importance, i.content.clone())).collect()
1549 });
1550 items.sort_unstable_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal));
1551 Ok(items.into_iter().map(|(_, c)| c).collect())
1552 }
1553
1554 pub fn content_contains_count(
1558 &self,
1559 agent_id: &AgentId,
1560 substring: &str,
1561 ) -> Result<usize, AgentRuntimeError> {
1562 let inner = recover_lock(self.inner.lock(), "EpisodicStore::content_contains_count");
1563 Ok(inner
1564 .items
1565 .get(agent_id)
1566 .map_or(0, |v| v.iter().filter(|i| i.content.contains(substring)).count()))
1567 }
1568
1569 pub fn agents_with_episodes(&self) -> Result<Vec<AgentId>, AgentRuntimeError> {
1571 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agents_with_episodes");
1572 let mut ids: Vec<AgentId> = inner
1573 .items
1574 .iter()
1575 .filter(|(_, v)| !v.is_empty())
1576 .map(|(k, _)| k.clone())
1577 .collect();
1578 ids.sort_unstable_by(|a, b| a.as_str().cmp(b.as_str()));
1579 Ok(ids)
1580 }
1581
1582 pub fn episode_count_all_agents(&self) -> Result<usize, AgentRuntimeError> {
1587 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_count_all_agents");
1588 Ok(inner.items.values().map(|v| v.len()).sum())
1589 }
1590
1591 pub fn high_importance_count(
1593 &self,
1594 agent_id: &AgentId,
1595 threshold: f32,
1596 ) -> Result<usize, AgentRuntimeError> {
1597 let inner = recover_lock(self.inner.lock(), "EpisodicStore::high_importance_count");
1598 Ok(inner
1599 .items
1600 .get(agent_id)
1601 .map_or(0, |v| v.iter().filter(|i| i.importance > threshold).count()))
1602 }
1603
1604 pub fn content_lengths(&self, agent_id: &AgentId) -> Result<Vec<usize>, AgentRuntimeError> {
1606 let inner = recover_lock(self.inner.lock(), "EpisodicStore::content_lengths");
1607 let lengths = inner
1608 .items
1609 .get(agent_id)
1610 .map_or_else(Vec::new, |v| v.iter().map(|i| i.content.len()).collect());
1611 Ok(lengths)
1612 }
1613
1614 pub fn total_content_bytes(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
1618 let inner = recover_lock(self.inner.lock(), "EpisodicStore::total_content_bytes");
1619 let total = inner
1620 .items
1621 .get(agent_id)
1622 .map_or(0, |items| items.iter().map(|i| i.content.len()).sum());
1623 Ok(total)
1624 }
1625
1626 pub fn avg_content_length(&self, agent_id: &AgentId) -> Result<f64, AgentRuntimeError> {
1630 let inner = recover_lock(self.inner.lock(), "EpisodicStore::avg_content_length");
1631 let items = match inner.items.get(agent_id) {
1632 Some(v) if !v.is_empty() => v,
1633 _ => return Ok(0.0),
1634 };
1635 let total: usize = items.iter().map(|i| i.content.len()).sum();
1636 Ok(total as f64 / items.len() as f64)
1637 }
1638
1639 pub fn importance_sum(&self, agent_id: &AgentId) -> Result<f32, AgentRuntimeError> {
1643 let inner = recover_lock(self.inner.lock(), "EpisodicStore::importance_sum");
1644 let sum = inner
1645 .items
1646 .get(agent_id)
1647 .map_or(0.0, |items| items.iter().map(|i| i.importance).sum());
1648 Ok(sum)
1649 }
1650
1651 pub fn recall_by_tag(
1654 &self,
1655 agent_id: &AgentId,
1656 tag: &str,
1657 limit: usize,
1658 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1659 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_by_tag");
1660 let mut matches: Vec<MemoryItem> = inner
1661 .items
1662 .get(agent_id)
1663 .map(|items| {
1664 items
1665 .iter()
1666 .filter(|i| i.tags.iter().any(|t| t == tag))
1667 .cloned()
1668 .collect()
1669 })
1670 .unwrap_or_default();
1671 matches.sort_unstable_by(|a, b| {
1672 b.importance
1673 .partial_cmp(&a.importance)
1674 .unwrap_or(std::cmp::Ordering::Equal)
1675 });
1676 if limit > 0 {
1677 matches.truncate(limit);
1678 }
1679 Ok(matches)
1680 }
1681
1682 #[tracing::instrument(skip(self))]
1684 pub fn add_episode_at(
1685 &self,
1686 agent_id: AgentId,
1687 content: impl Into<String> + std::fmt::Debug,
1688 importance: f32,
1689 timestamp: chrono::DateTime<chrono::Utc>,
1690 ) -> Result<MemoryId, AgentRuntimeError> {
1691 let mut item = MemoryItem::new(agent_id.clone(), content, importance, Vec::new());
1692 item.timestamp = timestamp;
1693 let id = item.id.clone();
1694 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::add_episode_at");
1695
1696 inner.purge_stale(&agent_id);
1697 let cap = inner.per_agent_capacity; let eviction_policy = inner.eviction_policy.clone();
1699 let agent_items = inner.items.entry(agent_id).or_default();
1700 agent_items.push(item);
1701
1702 if let Some(cap) = cap {
1703 evict_if_over_capacity(agent_items, cap, &eviction_policy);
1704 }
1705 Ok(id)
1706 }
1707
1708 pub fn add_episodes_batch(
1716 &self,
1717 agent_id: AgentId,
1718 episodes: impl IntoIterator<Item = (impl Into<String>, f32)>,
1719 ) -> Result<Vec<MemoryId>, AgentRuntimeError> {
1720 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::add_episodes_batch");
1721 inner.purge_stale(&agent_id);
1722 let cap = inner.per_agent_capacity;
1723 let eviction_policy = inner.eviction_policy.clone();
1724 let agent_items = inner.items.entry(agent_id.clone()).or_default();
1725
1726 let mut ids = Vec::new();
1727 for (content, importance) in episodes {
1728 let item = MemoryItem::new(agent_id.clone(), content, importance, Vec::new());
1729 ids.push(item.id.clone());
1730 agent_items.push(item);
1731 }
1732 if let Some(cap) = cap {
1733 evict_if_over_capacity(agent_items, cap, &eviction_policy);
1734 }
1735 Ok(ids)
1736 }
1737
1738 #[tracing::instrument(skip(self))]
1748 pub fn recall(
1749 &self,
1750 agent_id: &AgentId,
1751 limit: usize,
1752 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1753 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::recall");
1754
1755 let decay = inner.decay.clone();
1757 let max_age = inner.max_age_hours;
1758 let recall_policy = inner.recall_policy.clone();
1759
1760 if !inner.items.contains_key(agent_id) {
1762 return Ok(Vec::new());
1763 }
1764 let agent_items = inner.items.get_mut(agent_id).unwrap();
1765
1766 if let Some(ref policy) = decay {
1768 for item in agent_items.iter_mut() {
1769 policy.decay_item(item);
1770 }
1771 }
1772
1773 if let Some(max_age_h) = max_age {
1775 let cutoff =
1776 Utc::now() - chrono::Duration::seconds((max_age_h * 3600.0) as i64);
1777 agent_items.retain(|i| i.timestamp >= cutoff);
1778 }
1779
1780 let mut indices: Vec<usize> = (0..agent_items.len()).collect();
1789
1790 match recall_policy {
1791 RecallPolicy::Importance => {
1792 let cmp = |&a: &usize, &b: &usize| {
1793 agent_items[b]
1794 .importance
1795 .partial_cmp(&agent_items[a].importance)
1796 .unwrap_or(std::cmp::Ordering::Equal)
1797 };
1798 if limit > 0 && limit < indices.len() {
1799 indices.select_nth_unstable_by(limit - 1, cmp);
1800 indices[..limit].sort_unstable_by(cmp);
1801 } else {
1802 indices.sort_unstable_by(cmp);
1803 }
1804 }
1805 RecallPolicy::Hybrid {
1806 recency_weight,
1807 frequency_weight,
1808 } => {
1809 let max_recall = agent_items
1810 .iter()
1811 .map(|i| i.recall_count)
1812 .max()
1813 .unwrap_or(1)
1814 .max(1);
1815 let now = Utc::now();
1816 let cmp = |&a: &usize, &b: &usize| {
1817 let score_a = compute_hybrid_score(
1818 &agent_items[a],
1819 recency_weight,
1820 frequency_weight,
1821 max_recall,
1822 now,
1823 );
1824 let score_b = compute_hybrid_score(
1825 &agent_items[b],
1826 recency_weight,
1827 frequency_weight,
1828 max_recall,
1829 now,
1830 );
1831 score_b
1832 .partial_cmp(&score_a)
1833 .unwrap_or(std::cmp::Ordering::Equal)
1834 };
1835 if limit > 0 && limit < indices.len() {
1836 indices.select_nth_unstable_by(limit - 1, cmp);
1837 indices[..limit].sort_unstable_by(cmp);
1838 } else {
1839 indices.sort_unstable_by(cmp);
1840 }
1841 }
1842 }
1843
1844 indices.truncate(limit);
1845
1846 for &idx in &indices {
1848 agent_items[idx].recall_count += 1;
1849 }
1850
1851 let items: Vec<MemoryItem> = indices.iter().map(|&idx| agent_items[idx].clone()).collect();
1853
1854 tracing::debug!("recalled {} items", items.len());
1855 Ok(items)
1856 }
1857
1858 pub fn recall_tagged(
1865 &self,
1866 agent_id: &AgentId,
1867 tags: &[&str],
1868 limit: usize,
1869 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1870 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_tagged");
1871 let items = inner.items.get(agent_id).cloned().unwrap_or_default();
1872 drop(inner);
1873 let mut matched: Vec<MemoryItem> = items
1874 .into_iter()
1875 .filter(|item| {
1876 tags.iter()
1877 .all(|t| item.tags.iter().any(|it| it.as_str() == *t))
1878 })
1879 .collect();
1880 matched.sort_unstable_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap_or(std::cmp::Ordering::Equal));
1881 if limit > 0 {
1882 matched.truncate(limit);
1883 }
1884 Ok(matched)
1885 }
1886
1887 pub fn recall_by_id(
1892 &self,
1893 agent_id: &AgentId,
1894 id: &MemoryId,
1895 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
1896 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_by_id");
1897 Ok(inner
1898 .items
1899 .get(agent_id)
1900 .and_then(|items| items.iter().find(|i| &i.id == id).cloned()))
1901 }
1902
1903 pub fn merge_from(
1910 &self,
1911 other: &EpisodicStore,
1912 agent_id: &AgentId,
1913 ) -> Result<usize, AgentRuntimeError> {
1914 let other_items = {
1915 let inner = recover_lock(other.inner.lock(), "EpisodicStore::merge_from:read");
1916 inner.items.get(agent_id).cloned().unwrap_or_default()
1917 };
1918 let count = other_items.len();
1919 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::merge_from:write");
1920 inner.purge_stale(agent_id);
1921 let cap = inner.per_agent_capacity;
1922 let bucket = inner.items.entry(agent_id.clone()).or_default();
1923 for item in other_items {
1924 if let Some(cap) = cap {
1925 while bucket.len() >= cap {
1926 bucket.remove(0);
1927 }
1928 }
1929 bucket.push(item);
1930 }
1931 Ok(count)
1932 }
1933
1934 pub fn update_importance(
1941 &self,
1942 agent_id: &AgentId,
1943 id: &MemoryId,
1944 new_importance: f32,
1945 ) -> Result<bool, AgentRuntimeError> {
1946 let importance = new_importance.clamp(0.0, 1.0);
1947 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::update_importance");
1948 if let Some(items) = inner.items.get_mut(agent_id) {
1949 if let Some(item) = items.iter_mut().find(|i| &i.id == id) {
1950 item.importance = importance;
1951 return Ok(true);
1952 }
1953 }
1954 Ok(false)
1955 }
1956
1957 pub fn recall_since(
1964 &self,
1965 agent_id: &AgentId,
1966 cutoff: chrono::DateTime<chrono::Utc>,
1967 limit: usize,
1968 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
1969 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_since");
1970 let mut items: Vec<MemoryItem> = inner
1971 .items
1972 .get(agent_id)
1973 .cloned()
1974 .unwrap_or_default()
1975 .into_iter()
1976 .filter(|i| i.timestamp >= cutoff)
1977 .collect();
1978 drop(inner);
1979 items.sort_unstable_by(|a, b| {
1980 b.importance
1981 .partial_cmp(&a.importance)
1982 .unwrap_or(std::cmp::Ordering::Equal)
1983 });
1984 if limit > 0 {
1985 items.truncate(limit);
1986 }
1987 Ok(items)
1988 }
1989
1990 pub fn update_content(
1995 &self,
1996 agent_id: &AgentId,
1997 id: &MemoryId,
1998 new_content: impl Into<String>,
1999 ) -> Result<bool, AgentRuntimeError> {
2000 let new_content = new_content.into();
2001 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::update_content");
2002 if let Some(items) = inner.items.get_mut(agent_id) {
2003 if let Some(item) = items.iter_mut().find(|i| &i.id == id) {
2004 item.content = new_content;
2005 return Ok(true);
2006 }
2007 }
2008 Ok(false)
2009 }
2010
2011 pub fn recall_recent(
2019 &self,
2020 agent_id: &AgentId,
2021 limit: usize,
2022 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2023 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_recent");
2024 let items = inner.items.get(agent_id).cloned().unwrap_or_default();
2025 drop(inner);
2026 let start = if limit > 0 && limit < items.len() {
2027 items.len() - limit
2028 } else {
2029 0
2030 };
2031 Ok(items[start..].iter().rev().cloned().collect())
2032 }
2033
2034 pub fn recall_all(&self, agent_id: &AgentId) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2041 self.recall(agent_id, usize::MAX)
2042 }
2043
2044 pub fn top_n(&self, agent_id: &AgentId, n: usize) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2048 let inner = recover_lock(self.inner.lock(), "EpisodicStore::top_n");
2049 let mut items = inner.items.get(agent_id).cloned().unwrap_or_default();
2050 items.sort_unstable_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap_or(std::cmp::Ordering::Equal));
2051 if n > 0 {
2052 items.truncate(n);
2053 }
2054 Ok(items)
2055 }
2056
2057 pub fn search_by_importance_range(
2060 &self,
2061 agent_id: &AgentId,
2062 min: f32,
2063 max: f32,
2064 limit: usize,
2065 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2066 let inner = recover_lock(self.inner.lock(), "EpisodicStore::search_by_importance_range");
2067 let mut items: Vec<MemoryItem> = inner
2068 .items
2069 .get(agent_id)
2070 .map_or_else(Vec::new, |v| {
2071 v.iter().filter(|i| i.importance >= min && i.importance <= max).cloned().collect()
2072 });
2073 items.sort_unstable_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap_or(std::cmp::Ordering::Equal));
2074 if limit > 0 {
2075 items.truncate(limit);
2076 }
2077 Ok(items)
2078 }
2079
2080 pub fn total_recall_count(&self, agent_id: &AgentId) -> Result<u64, AgentRuntimeError> {
2084 let inner = recover_lock(self.inner.lock(), "EpisodicStore::total_recall_count");
2085 Ok(inner
2086 .items
2087 .get(agent_id)
2088 .map_or(0, |items| items.iter().map(|i| i.recall_count).sum()))
2089 }
2090
2091 pub fn recall_count_for(
2095 &self,
2096 agent_id: &AgentId,
2097 id: &MemoryId,
2098 ) -> Result<Option<u64>, AgentRuntimeError> {
2099 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recall_count_for");
2100 Ok(inner
2101 .items
2102 .get(agent_id)
2103 .and_then(|items| items.iter().find(|i| &i.id == id))
2104 .map(|i| i.recall_count))
2105 }
2106
2107 pub fn importance_stats(&self, agent_id: &AgentId) -> Result<(usize, f32, f32, f32), AgentRuntimeError> {
2111 let inner = recover_lock(self.inner.lock(), "EpisodicStore::importance_stats");
2112 let items = inner.items.get(agent_id).map(|v| v.as_slice()).unwrap_or(&[]);
2113 if items.is_empty() {
2114 return Ok((0, 0.0, 0.0, 0.0));
2115 }
2116 let count = items.len();
2117 let min = items.iter().map(|i| i.importance).fold(f32::MAX, f32::min);
2118 let max = items.iter().map(|i| i.importance).fold(f32::MIN, f32::max);
2119 let mean = items.iter().map(|i| i.importance).sum::<f32>() / count as f32;
2120 Ok((count, min, max, mean))
2121 }
2122
2123 pub fn oldest(
2126 &self,
2127 agent_id: &AgentId,
2128 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
2129 let inner = recover_lock(self.inner.lock(), "EpisodicStore::oldest");
2130 let item = inner.items.get(agent_id).and_then(|v| v.first()).cloned();
2131 Ok(item)
2132 }
2133
2134 pub fn clear_agent(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
2138 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::clear_agent");
2139 let count = inner.items.remove(agent_id).map_or(0, |v| v.len());
2140 Ok(count)
2141 }
2142
2143 pub fn oldest_episode(
2146 &self,
2147 agent_id: &AgentId,
2148 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
2149 let inner = recover_lock(self.inner.lock(), "EpisodicStore::oldest_episode");
2150 Ok(inner
2151 .items
2152 .get(agent_id)
2153 .and_then(|v| v.iter().min_by_key(|i| i.timestamp))
2154 .cloned())
2155 }
2156
2157 pub fn newest_episode(
2160 &self,
2161 agent_id: &AgentId,
2162 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
2163 let inner = recover_lock(self.inner.lock(), "EpisodicStore::newest_episode");
2164 Ok(inner
2165 .items
2166 .get(agent_id)
2167 .and_then(|v| v.iter().max_by_key(|i| i.timestamp))
2168 .cloned())
2169 }
2170
2171 pub fn recent_episodes(
2177 &self,
2178 agent_id: &AgentId,
2179 n: usize,
2180 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2181 let inner = recover_lock(self.inner.lock(), "EpisodicStore::recent_episodes");
2182 let mut items: Vec<MemoryItem> = inner
2183 .items
2184 .get(agent_id)
2185 .map(|v| v.iter().cloned().collect())
2186 .unwrap_or_default();
2187 items.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
2188 items.truncate(n);
2189 Ok(items)
2190 }
2191
2192 pub fn most_recent_episode(
2195 &self,
2196 agent_id: &AgentId,
2197 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
2198 let inner = recover_lock(self.inner.lock(), "EpisodicStore::most_recent_episode");
2199 Ok(inner
2200 .items
2201 .get(agent_id)
2202 .and_then(|v| v.iter().max_by_key(|i| i.timestamp))
2203 .cloned())
2204 }
2205
2206 pub fn most_recalled_episode(
2210 &self,
2211 agent_id: &AgentId,
2212 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
2213 let inner = recover_lock(self.inner.lock(), "EpisodicStore::most_recalled_episode");
2214 Ok(inner
2215 .items
2216 .get(agent_id)
2217 .and_then(|v| {
2218 v.iter().max_by(|a, b| {
2219 a.recall_count.cmp(&b.recall_count).then_with(|| {
2220 a.importance
2221 .partial_cmp(&b.importance)
2222 .unwrap_or(std::cmp::Ordering::Equal)
2223 })
2224 })
2225 })
2226 .cloned())
2227 }
2228
2229 pub fn max_importance_episode(
2233 &self,
2234 agent_id: &AgentId,
2235 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
2236 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_importance_episode");
2237 let item = inner
2238 .items
2239 .get(agent_id)
2240 .and_then(|v| {
2241 v.iter()
2242 .max_by(|a, b| {
2243 a.importance
2244 .partial_cmp(&b.importance)
2245 .unwrap_or(std::cmp::Ordering::Equal)
2246 })
2247 })
2248 .cloned();
2249 Ok(item)
2250 }
2251
2252 pub fn min_importance_episode(
2256 &self,
2257 agent_id: &AgentId,
2258 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
2259 let inner = recover_lock(self.inner.lock(), "EpisodicStore::min_importance_episode");
2260 let item = inner
2261 .items
2262 .get(agent_id)
2263 .and_then(|v| {
2264 v.iter().min_by(|a, b| {
2265 a.importance
2266 .partial_cmp(&b.importance)
2267 .unwrap_or(std::cmp::Ordering::Equal)
2268 })
2269 })
2270 .cloned();
2271 Ok(item)
2272 }
2273
2274 pub fn episodes_sorted_by_importance(
2279 &self,
2280 agent_id: &AgentId,
2281 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2282 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_sorted_by_importance");
2283 let mut items: Vec<MemoryItem> = inner
2284 .items
2285 .get(agent_id)
2286 .map(|v| v.iter().cloned().collect())
2287 .unwrap_or_default();
2288 items.sort_by(|a, b| {
2289 b.importance
2290 .partial_cmp(&a.importance)
2291 .unwrap_or(std::cmp::Ordering::Equal)
2292 });
2293 Ok(items)
2294 }
2295
2296 pub fn newest(
2299 &self,
2300 agent_id: &AgentId,
2301 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
2302 let inner = recover_lock(self.inner.lock(), "EpisodicStore::newest");
2303 let item = inner.items.get(agent_id).and_then(|v| v.last()).cloned();
2304 Ok(item)
2305 }
2306
2307 pub fn len(&self) -> Result<usize, AgentRuntimeError> {
2309 let inner = recover_lock(self.inner.lock(), "EpisodicStore::len");
2310 Ok(inner.items.values().map(|v| v.len()).sum())
2311 }
2312
2313 pub fn is_empty(&self) -> Result<bool, AgentRuntimeError> {
2315 Ok(self.len()? == 0)
2316 }
2317
2318 pub fn agent_count(&self) -> Result<usize, AgentRuntimeError> {
2320 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_count");
2321 Ok(inner.items.len())
2322 }
2323
2324 pub fn agent_memory_count(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
2328 let inner = recover_lock(self.inner.lock(), "EpisodicStore::agent_memory_count");
2329 Ok(inner.items.get(agent_id).map_or(0, |v| v.len()))
2330 }
2331
2332 pub fn has_episodes(&self, agent_id: &AgentId) -> Result<bool, AgentRuntimeError> {
2334 let inner = recover_lock(self.inner.lock(), "EpisodicStore::has_episodes");
2335 Ok(inner
2336 .items
2337 .get(agent_id)
2338 .map_or(false, |v| !v.is_empty()))
2339 }
2340
2341 pub fn latest_episode(
2346 &self,
2347 agent_id: &AgentId,
2348 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
2349 let inner = recover_lock(self.inner.lock(), "EpisodicStore::latest_episode");
2350 Ok(inner
2351 .items
2352 .get(agent_id)
2353 .and_then(|v| v.iter().max_by_key(|i| i.timestamp))
2354 .cloned())
2355 }
2356
2357 pub fn max_recall_count_for(&self, agent_id: &AgentId) -> Result<Option<u64>, AgentRuntimeError> {
2360 let inner = recover_lock(self.inner.lock(), "EpisodicStore::max_recall_count_for");
2361 Ok(inner
2362 .items
2363 .get(agent_id)
2364 .and_then(|v| v.iter().map(|i| i.recall_count).max()))
2365 }
2366
2367 pub fn avg_importance(&self, agent_id: &AgentId) -> Result<f64, AgentRuntimeError> {
2371 let inner = recover_lock(self.inner.lock(), "EpisodicStore::avg_importance");
2372 let episodes = match inner.items.get(agent_id) {
2373 Some(v) if !v.is_empty() => v,
2374 _ => return Ok(0.0),
2375 };
2376 let sum: f64 = episodes.iter().map(|i| f64::from(i.importance)).sum();
2377 Ok(sum / episodes.len() as f64)
2378 }
2379
2380 pub fn importance_range(
2383 &self,
2384 agent_id: &AgentId,
2385 ) -> Result<Option<(f32, f32)>, AgentRuntimeError> {
2386 let inner = recover_lock(self.inner.lock(), "EpisodicStore::importance_range");
2387 Ok(inner.items.get(agent_id).and_then(|v| {
2388 if v.is_empty() {
2389 return None;
2390 }
2391 let min = v
2392 .iter()
2393 .map(|i| i.importance)
2394 .fold(f32::INFINITY, f32::min);
2395 let max = v
2396 .iter()
2397 .map(|i| i.importance)
2398 .fold(f32::NEG_INFINITY, f32::max);
2399 Some((min, max))
2400 }))
2401 }
2402
2403 pub fn sum_recall_counts(&self, agent_id: &AgentId) -> Result<u64, AgentRuntimeError> {
2407 let inner = recover_lock(self.inner.lock(), "EpisodicStore::sum_recall_counts");
2408 Ok(inner
2409 .items
2410 .get(agent_id)
2411 .map(|v| v.iter().map(|i| i.recall_count).sum())
2412 .unwrap_or(0))
2413 }
2414
2415 pub fn list_agents(&self) -> Result<Vec<AgentId>, AgentRuntimeError> {
2419 let inner = recover_lock(self.inner.lock(), "EpisodicStore::list_agents");
2420 Ok(inner.items.keys().cloned().collect())
2421 }
2422
2423 pub fn purge_agent_memories(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
2427 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::purge_agent_memories");
2428 let removed = inner.items.remove(agent_id).map_or(0, |v| v.len());
2429 Ok(removed)
2430 }
2431
2432 pub fn clear_agent_memory(&self, agent_id: &AgentId) -> Result<(), AgentRuntimeError> {
2436 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::clear_agent_memory");
2437 inner.items.remove(agent_id);
2438 Ok(())
2439 }
2440
2441 pub fn clear_all(&self) -> Result<(), AgentRuntimeError> {
2445 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::clear_all");
2446 inner.items.clear();
2447 Ok(())
2448 }
2449
2450 pub fn export_agent_memory(&self, agent_id: &AgentId) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2454 let inner = recover_lock(self.inner.lock(), "EpisodicStore::export_agent_memory");
2455 Ok(inner.items.get(agent_id).cloned().unwrap_or_default())
2456 }
2457
2458 pub fn import_agent_memory(&self, agent_id: &AgentId, items: Vec<MemoryItem>) -> Result<(), AgentRuntimeError> {
2462 let mut inner = recover_lock(self.inner.lock(), "EpisodicStore::import_agent_memory");
2463 inner.items.insert(agent_id.clone(), items);
2464 Ok(())
2465 }
2466
2467 #[doc(hidden)]
2472 pub fn bump_recall_count_by_content(&self, content: &str, amount: u64) {
2473 let mut inner = recover_lock(
2474 self.inner.lock(),
2475 "EpisodicStore::bump_recall_count_by_content",
2476 );
2477 for agent_items in inner.items.values_mut() {
2478 for item in agent_items.iter_mut() {
2479 if item.content == content {
2480 item.recall_count = item.recall_count.saturating_add(amount);
2481 }
2482 }
2483 }
2484 }
2485
2486 pub fn search_by_content(
2493 &self,
2494 agent_id: &AgentId,
2495 query: &str,
2496 limit: usize,
2497 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2498 let inner = recover_lock(self.inner.lock(), "EpisodicStore::search_by_content");
2499 let items = inner.items.get(agent_id).cloned().unwrap_or_default();
2500 drop(inner);
2501 let mut matched: Vec<MemoryItem> = items
2502 .into_iter()
2503 .filter(|item| item.content.contains(query))
2504 .collect();
2505 matched.sort_unstable_by(|a, b| b.importance.partial_cmp(&a.importance).unwrap_or(std::cmp::Ordering::Equal));
2506 if limit > 0 {
2507 matched.truncate(limit);
2508 }
2509 Ok(matched)
2510 }
2511
2512 pub fn episode_count_above_importance(
2517 &self,
2518 agent_id: &AgentId,
2519 threshold: f32,
2520 ) -> Result<usize, AgentRuntimeError> {
2521 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_count_above_importance");
2522 let count = inner
2523 .items
2524 .get(agent_id)
2525 .map_or(0, |items| items.iter().filter(|m| m.importance > threshold).count());
2526 Ok(count)
2527 }
2528
2529 pub fn avg_episode_importance(
2533 &self,
2534 agent_id: &AgentId,
2535 ) -> Result<f64, AgentRuntimeError> {
2536 let inner = recover_lock(self.inner.lock(), "EpisodicStore::avg_episode_importance");
2537 let items = match inner.items.get(agent_id) {
2538 Some(items) if !items.is_empty() => items,
2539 _ => return Ok(0.0),
2540 };
2541 let sum: f64 = items.iter().map(|m| m.importance as f64).sum();
2542 Ok(sum / items.len() as f64)
2543 }
2544
2545 pub fn episode_content_bytes_total(
2550 &self,
2551 agent_id: &AgentId,
2552 ) -> Result<usize, AgentRuntimeError> {
2553 let inner = recover_lock(
2554 self.inner.lock(),
2555 "EpisodicStore::episode_content_bytes_total",
2556 );
2557 Ok(inner
2558 .items
2559 .get(agent_id)
2560 .map_or(0, |items| items.iter().map(|m| m.content.len()).sum()))
2561 }
2562
2563 pub fn total_content_words(
2568 &self,
2569 agent_id: &AgentId,
2570 ) -> Result<usize, AgentRuntimeError> {
2571 let inner = recover_lock(self.inner.lock(), "EpisodicStore::total_content_words");
2572 Ok(inner
2573 .items
2574 .get(agent_id)
2575 .map_or(0, |items| {
2576 items
2577 .iter()
2578 .map(|m| m.content.split_whitespace().count())
2579 .sum()
2580 }))
2581 }
2582
2583 pub fn min_episode_importance(
2588 &self,
2589 agent_id: &AgentId,
2590 ) -> Result<Option<f32>, AgentRuntimeError> {
2591 let inner =
2592 recover_lock(self.inner.lock(), "EpisodicStore::min_episode_importance");
2593 Ok(inner.items.get(agent_id).and_then(|items| {
2594 items
2595 .iter()
2596 .map(|m| m.importance)
2597 .reduce(f32::min)
2598 }))
2599 }
2600
2601 pub fn episodes_above_importance_count(
2606 &self,
2607 agent_id: &AgentId,
2608 threshold: f32,
2609 ) -> Result<usize, AgentRuntimeError> {
2610 let inner =
2611 recover_lock(self.inner.lock(), "EpisodicStore::episodes_above_importance_count");
2612 Ok(inner
2613 .items
2614 .get(agent_id)
2615 .map_or(0, |items| items.iter().filter(|m| m.importance > threshold).count()))
2616 }
2617
2618 pub fn tag_union(
2623 &self,
2624 agent_id: &AgentId,
2625 ) -> Result<std::collections::HashSet<String>, AgentRuntimeError> {
2626 let inner = recover_lock(self.inner.lock(), "EpisodicStore::tag_union");
2627 Ok(inner
2628 .items
2629 .get(agent_id)
2630 .map_or_else(std::collections::HashSet::new, |items| {
2631 items.iter().flat_map(|m| m.tags.iter().cloned()).collect()
2632 }))
2633 }
2634
2635 pub fn episode_most_recent(
2640 &self,
2641 agent_id: &AgentId,
2642 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
2643 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_most_recent");
2644 Ok(inner
2645 .items
2646 .get(agent_id)
2647 .and_then(|items| items.iter().max_by_key(|m| m.timestamp))
2648 .cloned())
2649 }
2650
2651 pub fn episodes_by_importance_range(
2656 &self,
2657 agent_id: &AgentId,
2658 lo: f32,
2659 hi: f32,
2660 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2661 let inner =
2662 recover_lock(self.inner.lock(), "EpisodicStore::episodes_by_importance_range");
2663 let mut items: Vec<MemoryItem> = inner
2664 .items
2665 .get(agent_id)
2666 .map_or_else(Vec::new, |items| {
2667 items
2668 .iter()
2669 .filter(|m| m.importance >= lo && m.importance <= hi)
2670 .cloned()
2671 .collect()
2672 });
2673 items.sort_unstable_by(|a, b| {
2674 a.importance.partial_cmp(&b.importance).unwrap_or(std::cmp::Ordering::Equal)
2675 });
2676 Ok(items)
2677 }
2678
2679 pub fn count_episodes_in_window(
2684 &self,
2685 agent_id: &AgentId,
2686 start: chrono::DateTime<chrono::Utc>,
2687 end: chrono::DateTime<chrono::Utc>,
2688 ) -> Result<usize, AgentRuntimeError> {
2689 let inner =
2690 recover_lock(self.inner.lock(), "EpisodicStore::count_episodes_in_window");
2691 Ok(inner.items.get(agent_id).map_or(0, |items| {
2692 items
2693 .iter()
2694 .filter(|m| m.timestamp >= start && m.timestamp <= end)
2695 .count()
2696 }))
2697 }
2698
2699 pub fn total_tag_count(
2704 &self,
2705 agent_id: &AgentId,
2706 ) -> Result<usize, AgentRuntimeError> {
2707 let inner = recover_lock(self.inner.lock(), "EpisodicStore::total_tag_count");
2708 Ok(inner
2709 .items
2710 .get(agent_id)
2711 .map_or(0, |items| items.iter().map(|m| m.tags.len()).sum()))
2712 }
2713
2714 pub fn avg_tag_count_per_episode(
2718 &self,
2719 agent_id: &AgentId,
2720 ) -> Result<f64, AgentRuntimeError> {
2721 let inner =
2722 recover_lock(self.inner.lock(), "EpisodicStore::avg_tag_count_per_episode");
2723 Ok(inner.items.get(agent_id).map_or(0.0, |items| {
2724 if items.is_empty() {
2725 return 0.0;
2726 }
2727 let total: usize = items.iter().map(|m| m.tags.len()).sum();
2728 total as f64 / items.len() as f64
2729 }))
2730 }
2731
2732 pub fn low_importance_episodes(
2738 &self,
2739 agent_id: &AgentId,
2740 threshold: f32,
2741 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2742 let inner = recover_lock(self.inner.lock(), "EpisodicStore::low_importance_episodes");
2743 let mut items: Vec<MemoryItem> = inner
2744 .items
2745 .get(agent_id)
2746 .map_or_else(Vec::new, |items| {
2747 items.iter().filter(|m| m.importance < threshold).cloned().collect()
2748 });
2749 items.sort_unstable_by(|a, b| {
2750 a.importance.partial_cmp(&b.importance).unwrap_or(std::cmp::Ordering::Equal)
2751 });
2752 Ok(items)
2753 }
2754
2755 pub fn episodes_sorted_by_timestamp(
2760 &self,
2761 agent_id: &AgentId,
2762 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2763 let inner = recover_lock(
2764 self.inner.lock(),
2765 "EpisodicStore::episodes_sorted_by_timestamp",
2766 );
2767 let mut items: Vec<MemoryItem> = inner
2768 .items
2769 .get(agent_id)
2770 .map_or_else(Vec::new, |v| v.clone());
2771 items.sort_unstable_by_key(|m| m.timestamp);
2772 Ok(items)
2773 }
2774
2775 pub fn all_unique_tags(
2780 &self,
2781 agent_id: &AgentId,
2782 ) -> Result<Vec<String>, AgentRuntimeError> {
2783 let inner = recover_lock(self.inner.lock(), "EpisodicStore::all_unique_tags");
2784 let mut tags: Vec<String> = inner
2785 .items
2786 .get(agent_id)
2787 .map_or_else(Vec::new, |items| {
2788 items.iter().flat_map(|m| m.tags.iter().cloned()).collect()
2789 });
2790 tags.sort_unstable();
2791 tags.dedup();
2792 Ok(tags)
2793 }
2794
2795 pub fn episodes_with_tag(
2799 &self,
2800 agent_id: &AgentId,
2801 tag: &str,
2802 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2803 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_with_tag");
2804 Ok(inner
2805 .items
2806 .get(agent_id)
2807 .map_or_else(Vec::new, |items| {
2808 items
2809 .iter()
2810 .filter(|m| m.tags.iter().any(|t| t == tag))
2811 .cloned()
2812 .collect()
2813 }))
2814 }
2815
2816 pub fn episode_count_before(
2821 &self,
2822 agent_id: &AgentId,
2823 before: chrono::DateTime<chrono::Utc>,
2824 ) -> Result<usize, AgentRuntimeError> {
2825 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_count_before");
2826 Ok(inner
2827 .items
2828 .get(agent_id)
2829 .map_or(0, |items| {
2830 items.iter().filter(|m| m.timestamp < before).count()
2831 }))
2832 }
2833
2834 pub fn episode_ids(&self, agent_id: &AgentId) -> Result<Vec<MemoryId>, AgentRuntimeError> {
2839 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_ids");
2840 Ok(inner
2841 .items
2842 .get(agent_id)
2843 .map_or_else(Vec::new, |items| items.iter().map(|m| m.id.clone()).collect()))
2844 }
2845
2846 pub fn episodes_above_importance(
2851 &self,
2852 agent_id: &AgentId,
2853 threshold: f32,
2854 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2855 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_above_importance");
2856 Ok(inner
2857 .items
2858 .get(agent_id)
2859 .map_or_else(Vec::new, |items| {
2860 items.iter().filter(|m| m.importance > threshold).cloned().collect()
2861 }))
2862 }
2863
2864 pub fn episodes_between(
2869 &self,
2870 agent_id: &AgentId,
2871 from: chrono::DateTime<chrono::Utc>,
2872 to: chrono::DateTime<chrono::Utc>,
2873 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2874 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_between");
2875 Ok(inner
2876 .items
2877 .get(agent_id)
2878 .map_or_else(Vec::new, |items| {
2879 items
2880 .iter()
2881 .filter(|m| m.timestamp >= from && m.timestamp < to)
2882 .cloned()
2883 .collect()
2884 }))
2885 }
2886
2887 pub fn episodes_tagged_with_all(
2892 &self,
2893 agent_id: &AgentId,
2894 tags: &[&str],
2895 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2896 if tags.is_empty() {
2897 return Ok(Vec::new());
2898 }
2899 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_tagged_with_all");
2900 Ok(inner
2901 .items
2902 .get(agent_id)
2903 .map_or_else(Vec::new, |items| {
2904 items
2905 .iter()
2906 .filter(|m| tags.iter().all(|t| m.has_tag(t)))
2907 .cloned()
2908 .collect()
2909 }))
2910 }
2911
2912 pub fn content_word_count_total(
2917 &self,
2918 agent_id: &AgentId,
2919 ) -> Result<usize, AgentRuntimeError> {
2920 let inner =
2921 recover_lock(self.inner.lock(), "EpisodicStore::content_word_count_total");
2922 Ok(inner
2923 .items
2924 .get(agent_id)
2925 .map_or(0, |items| {
2926 items.iter().map(|m| m.word_count()).sum()
2927 }))
2928 }
2929
2930 pub fn weighted_importance_sum(
2935 &self,
2936 agent_id: &AgentId,
2937 ) -> Result<f32, AgentRuntimeError> {
2938 let inner = recover_lock(self.inner.lock(), "EpisodicStore::weighted_importance_sum");
2939 Ok(inner
2940 .items
2941 .get(agent_id)
2942 .map_or(0.0, |items| items.iter().map(|m| m.importance).sum()))
2943 }
2944
2945 pub fn episode_content_lengths(
2950 &self,
2951 agent_id: &AgentId,
2952 ) -> Result<Vec<usize>, AgentRuntimeError> {
2953 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_content_lengths");
2954 Ok(inner
2955 .items
2956 .get(agent_id)
2957 .map_or_else(Vec::new, |items| items.iter().map(|m| m.content.len()).collect()))
2958 }
2959
2960 pub fn episode_count_by_agent(&self) -> Result<std::collections::HashMap<AgentId, usize>, AgentRuntimeError> {
2965 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_count_by_agent");
2966 Ok(inner.items.iter().map(|(id, items)| (id.clone(), items.len())).collect())
2967 }
2968
2969 pub fn all_episode_ids(&self) -> Result<Vec<MemoryId>, AgentRuntimeError> {
2974 let inner = recover_lock(self.inner.lock(), "EpisodicStore::all_episode_ids");
2975 let ids: Vec<MemoryId> = inner
2976 .items
2977 .values()
2978 .flat_map(|items| items.iter().map(|m| m.id.clone()))
2979 .collect();
2980 Ok(ids)
2981 }
2982
2983 pub fn episodes_above_content_bytes(
2988 &self,
2989 agent_id: &AgentId,
2990 min_bytes: usize,
2991 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
2992 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_above_content_bytes");
2993 Ok(inner
2994 .items
2995 .get(agent_id)
2996 .map_or_else(Vec::new, |items| {
2997 items.iter().filter(|m| m.content.len() > min_bytes).cloned().collect()
2998 }))
2999 }
3000
3001 pub fn agents_sorted_by_episode_count(&self) -> Result<Vec<AgentId>, AgentRuntimeError> {
3006 let inner =
3007 recover_lock(self.inner.lock(), "EpisodicStore::agents_sorted_by_episode_count");
3008 let mut pairs: Vec<(AgentId, usize)> = inner
3009 .items
3010 .iter()
3011 .map(|(id, items)| (id.clone(), items.len()))
3012 .collect();
3013 pairs.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.as_str().cmp(b.0.as_str())));
3014 Ok(pairs.into_iter().map(|(id, _)| id).collect())
3015 }
3016
3017 pub fn unique_agents_count(&self) -> Result<usize, AgentRuntimeError> {
3021 let inner = recover_lock(self.inner.lock(), "EpisodicStore::unique_agents_count");
3022 Ok(inner.items.len())
3023 }
3024
3025 pub fn episodes_with_tag_count(&self, agent_id: &AgentId) -> Result<usize, AgentRuntimeError> {
3029 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episodes_with_tag_count");
3030 Ok(inner
3031 .items
3032 .get(agent_id)
3033 .map_or(0, |items| items.iter().filter(|m| !m.tags.is_empty()).count()))
3034 }
3035
3036 pub fn episodes_with_min_word_count(
3041 &self,
3042 agent_id: &AgentId,
3043 min_words: usize,
3044 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
3045 let inner =
3046 recover_lock(self.inner.lock(), "EpisodicStore::episodes_with_min_word_count");
3047 Ok(inner
3048 .items
3049 .get(agent_id)
3050 .map_or_else(Vec::new, |items| {
3051 items
3052 .iter()
3053 .filter(|m| m.word_count() >= min_words)
3054 .cloned()
3055 .collect()
3056 }))
3057 }
3058
3059 pub fn most_tagged_episode(
3062 &self,
3063 agent_id: &AgentId,
3064 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
3065 let inner =
3066 recover_lock(self.inner.lock(), "EpisodicStore::most_tagged_episode");
3067 Ok(inner
3068 .items
3069 .get(agent_id)
3070 .and_then(|items| items.iter().max_by_key(|m| m.tag_count()).cloned()))
3071 }
3072
3073 pub fn tag_frequency(
3078 &self,
3079 agent_id: &AgentId,
3080 ) -> Result<std::collections::HashMap<String, usize>, AgentRuntimeError> {
3081 let inner = recover_lock(self.inner.lock(), "EpisodicStore::tag_frequency");
3082 let mut freq: std::collections::HashMap<String, usize> =
3083 std::collections::HashMap::new();
3084 if let Some(items) = inner.items.get(agent_id) {
3085 for item in items {
3086 for tag in &item.tags {
3087 *freq.entry(tag.clone()).or_insert(0) += 1;
3088 }
3089 }
3090 }
3091 Ok(freq)
3092 }
3093
3094 pub fn most_important_episode(
3100 &self,
3101 agent_id: &AgentId,
3102 ) -> Result<Option<MemoryItem>, AgentRuntimeError> {
3103 let inner = recover_lock(self.inner.lock(), "EpisodicStore::most_important_episode");
3104 Ok(inner
3105 .items
3106 .get(agent_id)
3107 .and_then(|items| {
3108 items
3109 .iter()
3110 .max_by(|a, b| {
3111 a.importance
3112 .partial_cmp(&b.importance)
3113 .unwrap_or(std::cmp::Ordering::Equal)
3114 })
3115 .cloned()
3116 }))
3117 }
3118
3119 pub fn episode_count_with_tag(
3123 &self,
3124 agent_id: &AgentId,
3125 tag: &str,
3126 ) -> Result<usize, AgentRuntimeError> {
3127 let inner = recover_lock(self.inner.lock(), "EpisodicStore::episode_count_with_tag");
3128 Ok(inner
3129 .items
3130 .get(agent_id)
3131 .map_or(0, |items| items.iter().filter(|m| m.has_tag(tag)).count()))
3132 }
3133
3134 pub fn episodes_below_importance(
3139 &self,
3140 agent_id: &AgentId,
3141 threshold: f32,
3142 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
3143 let inner =
3144 recover_lock(self.inner.lock(), "EpisodicStore::episodes_below_importance");
3145 Ok(inner
3146 .items
3147 .get(agent_id)
3148 .map_or_else(Vec::new, |items| {
3149 items.iter().filter(|m| m.importance < threshold).cloned().collect()
3150 }))
3151 }
3152
3153 pub fn has_episodes_for_agent(
3158 &self,
3159 agent_id: &AgentId,
3160 ) -> Result<bool, AgentRuntimeError> {
3161 let inner =
3162 recover_lock(self.inner.lock(), "EpisodicStore::has_episodes_for_agent");
3163 Ok(inner
3164 .items
3165 .get(agent_id)
3166 .map_or(false, |items| !items.is_empty()))
3167 }
3168
3169 pub fn episodes_with_content_containing(
3174 &self,
3175 agent_id: &AgentId,
3176 substr: &str,
3177 ) -> Result<Vec<MemoryItem>, AgentRuntimeError> {
3178 let inner = recover_lock(
3179 self.inner.lock(),
3180 "EpisodicStore::episodes_with_content_containing",
3181 );
3182 Ok(inner
3183 .items
3184 .get(agent_id)
3185 .map_or_else(Vec::new, |items| {
3186 items
3187 .iter()
3188 .filter(|m| m.content.contains(substr))
3189 .cloned()
3190 .collect()
3191 }))
3192 }
3193
3194 pub fn max_episode_importance(
3197 &self,
3198 agent_id: &AgentId,
3199 ) -> Result<Option<f32>, AgentRuntimeError> {
3200 let inner =
3201 recover_lock(self.inner.lock(), "EpisodicStore::max_episode_importance");
3202 Ok(inner.items.get(agent_id).and_then(|v| {
3203 v.iter()
3204 .map(|m| m.importance)
3205 .reduce(f32::max)
3206 }))
3207 }
3208
3209}
3210
3211impl Default for EpisodicStore {
3212 fn default() -> Self {
3213 Self::new()
3214 }
3215}
3216
3217#[derive(Debug, Clone)]
3226pub struct SemanticStore {
3227 inner: Arc<Mutex<SemanticInner>>,
3228}
3229
3230#[derive(Debug)]
3231struct SemanticInner {
3232 entries: Vec<SemanticEntry>,
3233 expected_dim: Option<usize>,
3234}
3235
3236#[derive(Debug, Clone)]
3237struct SemanticEntry {
3238 key: String,
3239 value: String,
3240 tags: Vec<String>,
3241 embedding: Option<Vec<f32>>,
3242}
3243
3244impl SemanticStore {
3245 pub fn new() -> Self {
3247 Self {
3248 inner: Arc::new(Mutex::new(SemanticInner {
3249 entries: Vec::new(),
3250 expected_dim: None,
3251 })),
3252 }
3253 }
3254
3255 #[tracing::instrument(skip(self))]
3257 pub fn store(
3258 &self,
3259 key: impl Into<String> + std::fmt::Debug,
3260 value: impl Into<String> + std::fmt::Debug,
3261 tags: Vec<String>,
3262 ) -> Result<(), AgentRuntimeError> {
3263 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::store");
3264 inner.entries.push(SemanticEntry {
3265 key: key.into(),
3266 value: value.into(),
3267 tags,
3268 embedding: None,
3269 });
3270 Ok(())
3271 }
3272
3273 #[tracing::instrument(skip(self))]
3278 pub fn store_with_embedding(
3279 &self,
3280 key: impl Into<String> + std::fmt::Debug,
3281 value: impl Into<String> + std::fmt::Debug,
3282 tags: Vec<String>,
3283 embedding: Vec<f32>,
3284 ) -> Result<(), AgentRuntimeError> {
3285 if embedding.is_empty() {
3286 return Err(AgentRuntimeError::Memory(
3287 "embedding vector must not be empty".into(),
3288 ));
3289 }
3290 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::store_with_embedding");
3291 if let Some(expected) = inner.expected_dim {
3293 if expected != embedding.len() {
3294 return Err(AgentRuntimeError::Memory(format!(
3295 "embedding dimension mismatch: expected {expected}, got {}",
3296 embedding.len()
3297 )));
3298 }
3299 } else {
3300 inner.expected_dim = Some(embedding.len());
3301 }
3302 let mut embedding = embedding;
3305 normalize_in_place(&mut embedding);
3306 inner.entries.push(SemanticEntry {
3307 key: key.into(),
3308 value: value.into(),
3309 tags,
3310 embedding: Some(embedding),
3311 });
3312 Ok(())
3313 }
3314
3315 #[tracing::instrument(skip(self))]
3319 pub fn retrieve(&self, tags: &[&str]) -> Result<Vec<(String, String)>, AgentRuntimeError> {
3320 let inner = recover_lock(self.inner.lock(), "SemanticStore::retrieve");
3321
3322 let results = inner
3323 .entries
3324 .iter()
3325 .filter(|entry| {
3326 tags.iter()
3327 .all(|t| entry.tags.iter().any(|et| et.as_str() == *t))
3328 })
3329 .map(|e| (e.key.clone(), e.value.clone()))
3330 .collect();
3331
3332 Ok(results)
3333 }
3334
3335 #[tracing::instrument(skip(self, query_embedding))]
3345 pub fn retrieve_similar(
3346 &self,
3347 query_embedding: &[f32],
3348 top_k: usize,
3349 ) -> Result<Vec<(String, String, f32)>, AgentRuntimeError> {
3350 let inner = recover_lock(self.inner.lock(), "SemanticStore::retrieve_similar");
3351
3352 if let Some(expected) = inner.expected_dim {
3354 if expected != query_embedding.len() {
3355 return Err(AgentRuntimeError::Memory(format!(
3356 "query embedding dimension mismatch: expected {expected}, got {}",
3357 query_embedding.len()
3358 )));
3359 }
3360 }
3361
3362 let query_norm: f32 = query_embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
3365 if query_norm < f32::EPSILON {
3366 return Ok(vec![]);
3367 }
3368 let query_unit: Vec<f32> = query_embedding.iter().map(|x| x / query_norm).collect();
3369
3370 let mut scored: Vec<(String, String, f32)> = inner
3371 .entries
3372 .iter()
3373 .filter_map(|entry| {
3374 entry.embedding.as_ref().map(|emb| {
3375 let sim = emb
3376 .iter()
3377 .zip(query_unit.iter())
3378 .map(|(a, b)| a * b)
3379 .sum::<f32>()
3380 .clamp(-1.0, 1.0);
3381 (entry.key.clone(), entry.value.clone(), sim)
3382 })
3383 })
3384 .collect();
3385
3386 let cmp = |a: &(String, String, f32), b: &(String, String, f32)| {
3389 b.2.partial_cmp(&a.2).unwrap_or(std::cmp::Ordering::Equal)
3390 };
3391 if top_k > 0 && top_k < scored.len() {
3392 scored.select_nth_unstable_by(top_k - 1, cmp);
3393 scored[..top_k].sort_unstable_by(cmp);
3394 } else {
3395 scored.sort_unstable_by(cmp);
3396 }
3397 scored.truncate(top_k);
3398 Ok(scored)
3399 }
3400
3401 pub fn update(&self, key: &str, new_value: impl Into<String>) -> Result<bool, AgentRuntimeError> {
3405 let new_value = new_value.into();
3406 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::update");
3407 if let Some(entry) = inner.entries.iter_mut().find(|e| e.key == key) {
3408 entry.value = new_value;
3409 Ok(true)
3410 } else {
3411 Ok(false)
3412 }
3413 }
3414
3415 pub fn retrieve_by_key(&self, key: &str) -> Result<Option<(String, Vec<String>)>, AgentRuntimeError> {
3419 let inner = recover_lock(self.inner.lock(), "SemanticStore::retrieve_by_key");
3420 Ok(inner.entries.iter().find(|e| e.key == key).map(|e| (e.value.clone(), e.tags.clone())))
3421 }
3422
3423 pub fn contains(&self, key: &str) -> Result<bool, AgentRuntimeError> {
3425 let inner = recover_lock(self.inner.lock(), "SemanticStore::contains");
3426 Ok(inner.entries.iter().any(|e| e.key == key))
3427 }
3428
3429 pub fn clear(&self) -> Result<(), AgentRuntimeError> {
3431 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::clear");
3432 inner.entries.clear();
3433 inner.expected_dim = None;
3434 Ok(())
3435 }
3436
3437 pub fn count_by_tag(&self, tag: &str) -> Result<usize, AgentRuntimeError> {
3439 let inner = recover_lock(self.inner.lock(), "SemanticStore::count_by_tag");
3440 Ok(inner
3441 .entries
3442 .iter()
3443 .filter(|e| e.tags.iter().any(|t| t.as_str() == tag))
3444 .count())
3445 }
3446
3447 pub fn list_tags(&self) -> Result<Vec<String>, AgentRuntimeError> {
3449 let inner = recover_lock(self.inner.lock(), "SemanticStore::list_tags");
3450 let mut tags: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
3451 for entry in &inner.entries {
3452 for tag in &entry.tags {
3453 tags.insert(tag.clone());
3454 }
3455 }
3456 Ok(tags.into_iter().collect())
3457 }
3458
3459 pub fn remove_entries_with_tag(&self, tag: &str) -> Result<usize, AgentRuntimeError> {
3463 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::remove_entries_with_tag");
3464 let before = inner.entries.len();
3465 inner.entries.retain(|e| !e.tags.iter().any(|t| t == tag));
3466 Ok(before - inner.entries.len())
3467 }
3468
3469 pub fn most_common_tag(&self) -> Result<Option<String>, AgentRuntimeError> {
3473 let inner = recover_lock(self.inner.lock(), "SemanticStore::most_common_tag");
3474 let mut counts: std::collections::HashMap<&str, usize> = std::collections::HashMap::new();
3475 for entry in &inner.entries {
3476 for tag in &entry.tags {
3477 *counts.entry(tag.as_str()).or_insert(0) += 1;
3478 }
3479 }
3480 Ok(counts.into_iter().max_by_key(|(_, c)| *c).map(|(t, _)| t.to_string()))
3481 }
3482
3483 pub fn keys_for_tag(&self, tag: &str) -> Result<Vec<String>, AgentRuntimeError> {
3485 let inner = recover_lock(self.inner.lock(), "SemanticStore::keys_for_tag");
3486 let keys = inner
3487 .entries
3488 .iter()
3489 .filter(|e| e.tags.iter().any(|t| t == tag))
3490 .map(|e| e.key.clone())
3491 .collect();
3492 Ok(keys)
3493 }
3494
3495 pub fn unique_tags(&self) -> Result<Vec<String>, AgentRuntimeError> {
3497 let inner = recover_lock(self.inner.lock(), "SemanticStore::unique_tags");
3498 let mut tags: Vec<String> = inner
3499 .entries
3500 .iter()
3501 .flat_map(|e| e.tags.iter().cloned())
3502 .collect::<std::collections::HashSet<_>>()
3503 .into_iter()
3504 .collect();
3505 tags.sort_unstable();
3506 Ok(tags)
3507 }
3508
3509 pub fn tag_count(&self) -> Result<usize, AgentRuntimeError> {
3513 let inner = recover_lock(self.inner.lock(), "SemanticStore::tag_count");
3514 let distinct: std::collections::HashSet<&str> = inner
3515 .entries
3516 .iter()
3517 .flat_map(|e| e.tags.iter().map(|t| t.as_str()))
3518 .collect();
3519 Ok(distinct.len())
3520 }
3521
3522 pub fn entry_count_with_embedding(&self) -> Result<usize, AgentRuntimeError> {
3524 let inner = recover_lock(self.inner.lock(), "SemanticStore::entry_count_with_embedding");
3525 Ok(inner.entries.iter().filter(|e| e.embedding.is_some()).count())
3526 }
3527
3528 pub fn get_value(&self, key: &str) -> Result<Option<String>, AgentRuntimeError> {
3536 let inner = recover_lock(self.inner.lock(), "SemanticStore::get_value");
3537 Ok(inner
3538 .entries
3539 .iter()
3540 .find(|e| e.key == key)
3541 .map(|e| e.value.clone()))
3542 }
3543
3544 pub fn get_tags(&self, key: &str) -> Result<Option<Vec<String>>, AgentRuntimeError> {
3548 let inner = recover_lock(self.inner.lock(), "SemanticStore::get_tags");
3549 Ok(inner.entries.iter().find(|e| e.key == key).map(|e| e.tags.clone()))
3550 }
3551
3552 pub fn keys_with_tag(&self, tag: &str) -> Result<Vec<String>, AgentRuntimeError> {
3554 let inner = recover_lock(self.inner.lock(), "SemanticStore::keys_with_tag");
3555 Ok(inner
3556 .entries
3557 .iter()
3558 .filter(|e| e.tags.iter().any(|t| t.as_str() == tag))
3559 .map(|e| e.key.clone())
3560 .collect())
3561 }
3562
3563 pub fn tags_for(&self, key: &str) -> Result<Option<Vec<String>>, AgentRuntimeError> {
3566 let inner = recover_lock(self.inner.lock(), "SemanticStore::tags_for");
3567 Ok(inner
3568 .entries
3569 .iter()
3570 .find(|e| e.key == key)
3571 .map(|e| e.tags.clone()))
3572 }
3573
3574 pub fn has_key(&self, key: &str) -> Result<bool, AgentRuntimeError> {
3576 let inner = recover_lock(self.inner.lock(), "SemanticStore::has_key");
3577 Ok(inner.entries.iter().any(|e| e.key == key))
3578 }
3579
3580 pub fn value_for(&self, key: &str) -> Result<Option<String>, AgentRuntimeError> {
3583 let inner = recover_lock(self.inner.lock(), "SemanticStore::value_for");
3584 Ok(inner
3585 .entries
3586 .iter()
3587 .find(|e| e.key == key)
3588 .map(|e| e.value.clone()))
3589 }
3590
3591 pub fn entries_without_tags(&self) -> Result<usize, AgentRuntimeError> {
3593 let inner = recover_lock(self.inner.lock(), "SemanticStore::entries_without_tags");
3594 Ok(inner.entries.iter().filter(|e| e.tags.is_empty()).count())
3595 }
3596
3597 pub fn most_tagged_key(&self) -> Result<Option<String>, AgentRuntimeError> {
3601 let inner = recover_lock(self.inner.lock(), "SemanticStore::most_tagged_key");
3602 Ok(inner
3603 .entries
3604 .iter()
3605 .max_by_key(|e| e.tags.len())
3606 .map(|e| e.key.clone()))
3607 }
3608
3609 pub fn rename_tag(&self, old_tag: &str, new_tag: &str) -> Result<usize, AgentRuntimeError> {
3614 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::rename_tag");
3615 let mut count = 0;
3616 for entry in &mut inner.entries {
3617 for tag in &mut entry.tags {
3618 if tag == old_tag {
3619 *tag = new_tag.to_string();
3620 count += 1;
3621 }
3622 }
3623 }
3624 Ok(count)
3625 }
3626
3627 pub fn count_matching_value(&self, substring: &str) -> Result<usize, AgentRuntimeError> {
3629 let inner = recover_lock(self.inner.lock(), "SemanticStore::count_matching_value");
3630 Ok(inner.entries.iter().filter(|e| e.value.contains(substring)).count())
3631 }
3632
3633 pub fn entries_with_no_tags(&self) -> Result<Vec<String>, AgentRuntimeError> {
3635 let inner = recover_lock(self.inner.lock(), "SemanticStore::entries_with_no_tags");
3636 Ok(inner
3637 .entries
3638 .iter()
3639 .filter(|e| e.tags.is_empty())
3640 .map(|e| e.key.clone())
3641 .collect())
3642 }
3643
3644 pub fn avg_tag_count_per_entry(&self) -> Result<f64, AgentRuntimeError> {
3648 let inner = recover_lock(self.inner.lock(), "SemanticStore::avg_tag_count_per_entry");
3649 let n = inner.entries.len();
3650 if n == 0 {
3651 return Ok(0.0);
3652 }
3653 let total: usize = inner.entries.iter().map(|e| e.tags.len()).sum();
3654 Ok(total as f64 / n as f64)
3655 }
3656
3657 pub fn most_recent_key(&self) -> Result<Option<String>, AgentRuntimeError> {
3659 let inner = recover_lock(self.inner.lock(), "SemanticStore::most_recent_key");
3660 Ok(inner.entries.last().map(|e| e.key.clone()))
3661 }
3662
3663 pub fn oldest_key(&self) -> Result<Option<String>, AgentRuntimeError> {
3665 let inner = recover_lock(self.inner.lock(), "SemanticStore::oldest_key");
3666 Ok(inner.entries.first().map(|e| e.key.clone()))
3667 }
3668
3669 pub fn remove_by_key(&self, key: &str) -> Result<usize, AgentRuntimeError> {
3673 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::remove_by_key");
3674 let before = inner.entries.len();
3675 inner.entries.retain(|e| e.key != key);
3676 Ok(before - inner.entries.len())
3677 }
3678
3679 pub fn entry_count_with_tag(&self, tag: &str) -> Result<usize, AgentRuntimeError> {
3681 let inner = recover_lock(self.inner.lock(), "SemanticStore::entry_count_with_tag");
3682 Ok(inner
3683 .entries
3684 .iter()
3685 .filter(|e| e.tags.iter().any(|t| t == tag))
3686 .count())
3687 }
3688
3689 pub fn list_keys(&self) -> Result<Vec<String>, AgentRuntimeError> {
3693 let inner = recover_lock(self.inner.lock(), "SemanticStore::list_keys");
3694 Ok(inner.entries.iter().map(|e| e.key.clone()).collect())
3695 }
3696
3697 pub fn keys(&self) -> Result<Vec<String>, AgentRuntimeError> {
3703 self.list_keys()
3704 }
3705
3706 pub fn values(&self) -> Result<Vec<String>, AgentRuntimeError> {
3708 let inner = recover_lock(self.inner.lock(), "SemanticStore::values");
3709 Ok(inner.entries.iter().map(|e| e.value.clone()).collect())
3710 }
3711
3712 pub fn keys_matching(&self, pattern: &str) -> Result<Vec<String>, AgentRuntimeError> {
3716 let inner = recover_lock(self.inner.lock(), "SemanticStore::keys_matching");
3717 let lower = pattern.to_ascii_lowercase();
3718 Ok(inner
3719 .entries
3720 .iter()
3721 .filter(|e| e.key.to_ascii_lowercase().contains(&lower))
3722 .map(|e| e.key.clone())
3723 .collect())
3724 }
3725
3726 pub fn update_value(
3730 &self,
3731 key: &str,
3732 new_value: impl Into<String>,
3733 ) -> Result<bool, AgentRuntimeError> {
3734 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::update_value");
3735 if let Some(entry) = inner.entries.iter_mut().find(|e| e.key == key) {
3736 entry.value = new_value.into();
3737 Ok(true)
3738 } else {
3739 Ok(false)
3740 }
3741 }
3742
3743 pub fn to_map(&self) -> Result<std::collections::HashMap<String, String>, AgentRuntimeError> {
3748 let inner = recover_lock(self.inner.lock(), "SemanticStore::to_map");
3749 Ok(inner
3750 .entries
3751 .iter()
3752 .map(|e| (e.key.clone(), e.value.clone()))
3753 .collect())
3754 }
3755
3756 pub fn update_tags(
3760 &self,
3761 key: &str,
3762 new_tags: Vec<String>,
3763 ) -> Result<bool, AgentRuntimeError> {
3764 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::update_tags");
3765 if let Some(entry) = inner.entries.iter_mut().find(|e| e.key == key) {
3766 entry.tags = new_tags;
3767 Ok(true)
3768 } else {
3769 Ok(false)
3770 }
3771 }
3772
3773 pub fn total_value_bytes(&self) -> Result<usize, AgentRuntimeError> {
3777 let inner = recover_lock(self.inner.lock(), "SemanticStore::total_value_bytes");
3778 Ok(inner.entries.iter().map(|e| e.value.len()).sum())
3779 }
3780
3781 pub fn avg_value_bytes(&self) -> Result<f64, AgentRuntimeError> {
3785 let inner = recover_lock(self.inner.lock(), "SemanticStore::avg_value_bytes");
3786 if inner.entries.is_empty() {
3787 return Ok(0.0);
3788 }
3789 let total: usize = inner.entries.iter().map(|e| e.value.len()).sum();
3790 Ok(total as f64 / inner.entries.len() as f64)
3791 }
3792
3793 pub fn max_value_bytes(&self) -> Result<usize, AgentRuntimeError> {
3797 let inner = recover_lock(self.inner.lock(), "SemanticStore::max_value_bytes");
3798 Ok(inner.entries.iter().map(|e| e.value.len()).max().unwrap_or(0))
3799 }
3800
3801 pub fn min_value_bytes(&self) -> Result<usize, AgentRuntimeError> {
3805 let inner = recover_lock(self.inner.lock(), "SemanticStore::min_value_bytes");
3806 Ok(inner.entries.iter().map(|e| e.value.len()).min().unwrap_or(0))
3807 }
3808
3809 pub fn all_keys(&self) -> Result<Vec<String>, AgentRuntimeError> {
3811 let inner = recover_lock(self.inner.lock(), "SemanticStore::all_keys");
3812 let mut keys: Vec<String> = inner.entries.iter().map(|e| e.key.clone()).collect();
3813 keys.sort_unstable();
3814 Ok(keys)
3815 }
3816
3817 pub fn keys_with_prefix(&self, prefix: &str) -> Result<Vec<String>, AgentRuntimeError> {
3822 let inner = recover_lock(self.inner.lock(), "SemanticStore::keys_with_prefix");
3823 let mut keys: Vec<String> = inner
3824 .entries
3825 .iter()
3826 .filter(|e| e.key.starts_with(prefix))
3827 .map(|e| e.key.clone())
3828 .collect();
3829 keys.sort_unstable();
3830 Ok(keys)
3831 }
3832
3833 pub fn len(&self) -> Result<usize, AgentRuntimeError> {
3835 let inner = recover_lock(self.inner.lock(), "SemanticStore::len");
3836 Ok(inner.entries.len())
3837 }
3838
3839 pub fn is_empty(&self) -> Result<bool, AgentRuntimeError> {
3841 Ok(self.len()? == 0)
3842 }
3843
3844 pub fn count(&self) -> Result<usize, AgentRuntimeError> {
3850 self.len()
3851 }
3852
3853 pub fn remove(&self, key: &str) -> Result<bool, AgentRuntimeError> {
3858 let mut inner = recover_lock(self.inner.lock(), "SemanticStore::remove");
3859 let before = inner.entries.len();
3860 inner.entries.retain(|e| e.key != key);
3861 Ok(inner.entries.len() < before)
3862 }
3863
3864}
3865
3866impl Default for SemanticStore {
3867 fn default() -> Self {
3868 Self::new()
3869 }
3870}
3871
3872#[derive(Debug, Clone)]
3883pub struct WorkingMemory {
3884 capacity: usize,
3885 inner: Arc<Mutex<WorkingInner>>,
3886}
3887
3888#[derive(Debug)]
3889struct WorkingInner {
3890 map: HashMap<String, String>,
3891 order: VecDeque<String>,
3892}
3893
3894impl WorkingMemory {
3895 pub fn new(capacity: usize) -> Result<Self, AgentRuntimeError> {
3901 if capacity == 0 {
3902 return Err(AgentRuntimeError::Memory(
3903 "WorkingMemory capacity must be > 0".into(),
3904 ));
3905 }
3906 Ok(Self {
3907 capacity,
3908 inner: Arc::new(Mutex::new(WorkingInner {
3909 map: HashMap::new(),
3910 order: VecDeque::new(),
3911 })),
3912 })
3913 }
3914
3915 #[tracing::instrument(skip(self))]
3917 pub fn set(
3918 &self,
3919 key: impl Into<String> + std::fmt::Debug,
3920 value: impl Into<String> + std::fmt::Debug,
3921 ) -> Result<(), AgentRuntimeError> {
3922 let key = key.into();
3923 let value = value.into();
3924 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::set");
3925
3926 if inner.map.contains_key(&key) {
3928 inner.order.retain(|k| k != &key);
3929 } else if inner.map.len() >= self.capacity {
3930 if let Some(oldest) = inner.order.pop_front() {
3932 inner.map.remove(&oldest);
3933 }
3934 }
3935
3936 inner.order.push_back(key.clone());
3937 inner.map.insert(key, value);
3938 Ok(())
3939 }
3940
3941 #[tracing::instrument(skip(self))]
3947 pub fn get(&self, key: &str) -> Result<Option<String>, AgentRuntimeError> {
3948 let inner = recover_lock(self.inner.lock(), "WorkingMemory::get");
3949 Ok(inner.map.get(key).cloned())
3950 }
3951
3952 pub fn set_many(
3960 &self,
3961 pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
3962 ) -> Result<(), AgentRuntimeError> {
3963 let capacity = self.capacity;
3964 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::set_many");
3965 for (key, value) in pairs {
3966 let key: String = key.into();
3967 let value: String = value.into();
3968 if inner.map.contains_key(&key) {
3969 inner.order.retain(|k| k != &key);
3970 } else if inner.map.len() >= capacity {
3971 if let Some(oldest) = inner.order.pop_front() {
3972 inner.map.remove(&oldest);
3973 }
3974 }
3975 inner.order.push_back(key.clone());
3976 inner.map.insert(key, value);
3977 }
3978 Ok(())
3979 }
3980
3981 pub fn set_if_absent(
3991 &self,
3992 key: impl Into<String> + std::fmt::Debug,
3993 value: impl Into<String> + std::fmt::Debug,
3994 ) -> Result<bool, AgentRuntimeError> {
3995 let key = key.into();
3996 {
3997 let inner = recover_lock(self.inner.lock(), "WorkingMemory::set_if_absent");
3998 if inner.map.contains_key(&key) {
3999 return Ok(false);
4000 }
4001 }
4002 self.set(key, value)?;
4003 Ok(true)
4004 }
4005
4006 pub fn update_if_exists(
4008 &self,
4009 key: &str,
4010 value: impl Into<String>,
4011 ) -> Result<bool, AgentRuntimeError> {
4012 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::update_if_exists");
4013 if let Some(v) = inner.map.get_mut(key) {
4014 *v = value.into();
4015 Ok(true)
4016 } else {
4017 Ok(false)
4018 }
4019 }
4020
4021 pub fn update_many(
4029 &self,
4030 pairs: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
4031 ) -> Result<usize, AgentRuntimeError> {
4032 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::update_many");
4033 let mut updated = 0;
4034 for (key, value) in pairs {
4035 let key: String = key.into();
4036 if let Some(v) = inner.map.get_mut(&key) {
4037 *v = value.into();
4038 updated += 1;
4039 }
4040 }
4041 Ok(updated)
4042 }
4043
4044 pub fn keys_starting_with(&self, prefix: &str) -> Result<Vec<String>, AgentRuntimeError> {
4049 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys_starting_with");
4050 Ok(inner
4051 .map
4052 .keys()
4053 .filter(|k| k.starts_with(prefix))
4054 .cloned()
4055 .collect())
4056 }
4057
4058 pub fn values_matching(&self, pattern: &str) -> Result<Vec<(String, String)>, AgentRuntimeError> {
4064 let inner = recover_lock(self.inner.lock(), "WorkingMemory::values_matching");
4065 Ok(inner
4066 .map
4067 .iter()
4068 .filter(|(_, v)| v.contains(pattern))
4069 .map(|(k, v)| (k.clone(), v.clone()))
4070 .collect())
4071 }
4072
4073 pub fn value_length(&self, key: &str) -> Result<Option<usize>, AgentRuntimeError> {
4078 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_length");
4079 Ok(inner.map.get(key).map(|v| v.len()))
4080 }
4081
4082 pub fn contains_all<'a>(&self, keys: impl IntoIterator<Item = &'a str>) -> Result<bool, AgentRuntimeError> {
4088 let inner = recover_lock(self.inner.lock(), "WorkingMemory::contains_all");
4089 Ok(keys.into_iter().all(|k| inner.map.contains_key(k)))
4090 }
4091
4092 pub fn has_any_key<'a>(&self, keys: impl IntoIterator<Item = &'a str>) -> Result<bool, AgentRuntimeError> {
4096 let inner = recover_lock(self.inner.lock(), "WorkingMemory::has_any_key");
4097 Ok(keys.into_iter().any(|k| inner.map.contains_key(k)))
4098 }
4099
4100 pub fn rename(
4104 &self,
4105 old_key: &str,
4106 new_key: impl Into<String>,
4107 ) -> Result<bool, AgentRuntimeError> {
4108 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::rename");
4109 let value = match inner.map.remove(old_key) {
4110 None => return Ok(false),
4111 Some(v) => v,
4112 };
4113 let new_key = new_key.into();
4114 if let Some(pos) = inner.order.iter().position(|k| k == old_key) {
4116 inner.order[pos] = new_key.clone();
4117 }
4118 inner.map.insert(new_key, value);
4119 Ok(true)
4120 }
4121
4122 pub fn get_many(&self, keys: &[&str]) -> Result<Vec<Option<String>>, AgentRuntimeError> {
4127 let inner = recover_lock(self.inner.lock(), "WorkingMemory::get_many");
4128 Ok(keys.iter().map(|k| inner.map.get(*k).cloned()).collect())
4129 }
4130
4131 pub fn contains(&self, key: &str) -> Result<bool, AgentRuntimeError> {
4133 let inner = recover_lock(self.inner.lock(), "WorkingMemory::contains");
4134 Ok(inner.map.contains_key(key))
4135 }
4136
4137 pub fn get_or_default(
4139 &self,
4140 key: &str,
4141 default: impl Into<String>,
4142 ) -> Result<String, AgentRuntimeError> {
4143 Ok(self.get(key)?.unwrap_or_else(|| default.into()))
4144 }
4145
4146 pub fn remove(&self, key: &str) -> Result<bool, AgentRuntimeError> {
4148 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::remove");
4149 if inner.map.remove(key).is_some() {
4150 inner.order.retain(|k| k != key);
4151 Ok(true)
4152 } else {
4153 Ok(false)
4154 }
4155 }
4156
4157 pub fn keys(&self) -> Result<Vec<String>, AgentRuntimeError> {
4159 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys");
4160 Ok(inner.order.iter().cloned().collect())
4161 }
4162
4163 pub fn values(&self) -> Result<Vec<String>, AgentRuntimeError> {
4167 let inner = recover_lock(self.inner.lock(), "WorkingMemory::values");
4168 Ok(inner
4169 .order
4170 .iter()
4171 .filter_map(|k| inner.map.get(k).cloned())
4172 .collect())
4173 }
4174
4175 pub fn clear(&self) -> Result<(), AgentRuntimeError> {
4177 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::clear");
4178 inner.map.clear();
4179 inner.order.clear();
4180 Ok(())
4181 }
4182
4183 pub fn iter_sorted(&self) -> Result<Vec<(String, String)>, AgentRuntimeError> {
4193 let inner = recover_lock(self.inner.lock(), "WorkingMemory::iter_sorted");
4194 let mut pairs: Vec<(String, String)> = inner
4195 .map
4196 .iter()
4197 .map(|(k, v)| (k.clone(), v.clone()))
4198 .collect();
4199 pairs.sort_unstable_by(|a, b| a.0.cmp(&b.0));
4200 Ok(pairs)
4201 }
4202
4203 pub fn drain(&self) -> Result<Vec<(String, String)>, AgentRuntimeError> {
4206 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::drain");
4207 let pairs: Vec<(String, String)> = inner
4208 .order
4209 .iter()
4210 .filter_map(|k| inner.map.get(k).map(|v| (k.clone(), v.clone())))
4211 .collect();
4212 inner.map.clear();
4213 inner.order.clear();
4214 Ok(pairs)
4215 }
4216
4217 pub fn snapshot(&self) -> Result<std::collections::HashMap<String, String>, AgentRuntimeError> {
4223 let inner = recover_lock(self.inner.lock(), "WorkingMemory::snapshot");
4224 Ok(inner.map.clone())
4225 }
4226
4227 pub fn len(&self) -> Result<usize, AgentRuntimeError> {
4229 let inner = recover_lock(self.inner.lock(), "WorkingMemory::len");
4230 Ok(inner.map.len())
4231 }
4232
4233 pub fn is_empty(&self) -> Result<bool, AgentRuntimeError> {
4235 Ok(self.len()? == 0)
4236 }
4237
4238 pub fn iter(&self) -> Result<Vec<(String, String)>, AgentRuntimeError> {
4245 self.entries()
4246 }
4247
4248 pub fn entries(&self) -> Result<Vec<(String, String)>, AgentRuntimeError> {
4250 let inner = recover_lock(self.inner.lock(), "WorkingMemory::entries");
4251 let entries = inner
4252 .order
4253 .iter()
4254 .filter_map(|k| inner.map.get(k).map(|v| (k.clone(), v.clone())))
4255 .collect();
4256 Ok(entries)
4257 }
4258
4259 pub fn pop_oldest(&self) -> Result<Option<(String, String)>, AgentRuntimeError> {
4263 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::pop_oldest");
4264 if let Some(key) = inner.order.pop_front() {
4265 let value = inner.map.remove(&key).unwrap_or_default();
4266 Ok(Some((key, value)))
4267 } else {
4268 Ok(None)
4269 }
4270 }
4271
4272 pub fn peek_oldest(&self) -> Result<Option<(String, String)>, AgentRuntimeError> {
4279 let inner = recover_lock(self.inner.lock(), "WorkingMemory::peek_oldest");
4280 Ok(inner.order.front().and_then(|key| {
4281 inner.map.get(key).map(|val| (key.clone(), val.clone()))
4282 }))
4283 }
4284
4285 pub fn capacity(&self) -> usize {
4293 self.capacity
4294 }
4295
4296 pub fn fill_ratio(&self) -> Result<f64, AgentRuntimeError> {
4301 Ok(self.len()? as f64 / self.capacity as f64)
4302 }
4303
4304 pub fn is_at_capacity(&self) -> Result<bool, AgentRuntimeError> {
4310 Ok(self.len()? >= self.capacity)
4311 }
4312
4313 pub fn remove_keys_starting_with(&self, prefix: &str) -> Result<usize, AgentRuntimeError> {
4318 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::remove_keys_starting_with");
4319 let before = inner.map.len();
4320 inner.map.retain(|k, _| !k.starts_with(prefix));
4321 Ok(before - inner.map.len())
4322 }
4323
4324 pub fn total_value_bytes(&self) -> Result<usize, AgentRuntimeError> {
4328 let inner = recover_lock(self.inner.lock(), "WorkingMemory::total_value_bytes");
4329 Ok(inner.map.values().map(|v| v.len()).sum())
4330 }
4331
4332 pub fn max_key_length(&self) -> Result<usize, AgentRuntimeError> {
4336 let inner = recover_lock(self.inner.lock(), "WorkingMemory::max_key_length");
4337 Ok(inner.map.keys().map(|k| k.len()).max().unwrap_or(0))
4338 }
4339
4340 pub fn max_value_length(&self) -> Result<usize, AgentRuntimeError> {
4344 let inner = recover_lock(self.inner.lock(), "WorkingMemory::max_value_length");
4345 Ok(inner.map.values().map(|v| v.len()).max().unwrap_or(0))
4346 }
4347
4348 pub fn min_value_length(&self) -> Result<usize, AgentRuntimeError> {
4352 let inner = recover_lock(self.inner.lock(), "WorkingMemory::min_value_length");
4353 Ok(inner.map.values().map(|v| v.len()).min().unwrap_or(0))
4354 }
4355
4356 pub fn key_count_matching(&self, substring: &str) -> Result<usize, AgentRuntimeError> {
4361 let inner = recover_lock(self.inner.lock(), "WorkingMemory::key_count_matching");
4362 Ok(inner.map.keys().filter(|k| k.contains(substring)).count())
4363 }
4364
4365 pub fn avg_value_length(&self) -> Result<f64, AgentRuntimeError> {
4369 let inner = recover_lock(self.inner.lock(), "WorkingMemory::avg_value_length");
4370 let n = inner.map.len();
4371 if n == 0 {
4372 return Ok(0.0);
4373 }
4374 let total: usize = inner.map.values().map(|v| v.len()).sum();
4375 Ok(total as f64 / n as f64)
4376 }
4377
4378 pub fn count_above_value_length(&self, min_bytes: usize) -> Result<usize, AgentRuntimeError> {
4380 let inner = recover_lock(self.inner.lock(), "WorkingMemory::count_above_value_length");
4381 Ok(inner.map.values().filter(|v| v.len() > min_bytes).count())
4382 }
4383
4384 pub fn longest_key(&self) -> Result<Option<String>, AgentRuntimeError> {
4389 let inner = recover_lock(self.inner.lock(), "WorkingMemory::longest_key");
4390 Ok(inner
4391 .map
4392 .keys()
4393 .max_by_key(|k| k.len())
4394 .map(|k| k.clone()))
4395 }
4396
4397 pub fn longest_value(&self) -> Result<Option<String>, AgentRuntimeError> {
4402 let inner = recover_lock(self.inner.lock(), "WorkingMemory::longest_value");
4403 Ok(inner
4404 .map
4405 .values()
4406 .max_by_key(|v| v.len())
4407 .map(|v| v.clone()))
4408 }
4409
4410 pub fn key_value_pairs_sorted(&self) -> Result<Vec<(String, String)>, AgentRuntimeError> {
4415 let inner = recover_lock(self.inner.lock(), "WorkingMemory::key_value_pairs_sorted");
4416 let mut pairs: Vec<(String, String)> = inner
4417 .map
4418 .iter()
4419 .map(|(k, v)| (k.clone(), v.clone()))
4420 .collect();
4421 pairs.sort_unstable_by(|a, b| a.0.cmp(&b.0));
4422 Ok(pairs)
4423 }
4424
4425 pub fn pairs_starting_with(&self, prefix: &str) -> Result<Vec<(String, String)>, AgentRuntimeError> {
4427 let inner = recover_lock(self.inner.lock(), "WorkingMemory::pairs_starting_with");
4428 let pairs = inner
4429 .map
4430 .iter()
4431 .filter(|(k, _)| k.starts_with(prefix))
4432 .map(|(k, v)| (k.clone(), v.clone()))
4433 .collect();
4434 Ok(pairs)
4435 }
4436
4437 pub fn total_key_bytes(&self) -> Result<usize, AgentRuntimeError> {
4439 let inner = recover_lock(self.inner.lock(), "WorkingMemory::total_key_bytes");
4440 Ok(inner.map.keys().map(|k| k.len()).sum())
4441 }
4442
4443 pub fn entries_sorted_by_key_length(
4448 &self,
4449 ) -> Result<Vec<(String, String)>, AgentRuntimeError> {
4450 let inner =
4451 recover_lock(self.inner.lock(), "WorkingMemory::entries_sorted_by_key_length");
4452 let mut pairs: Vec<(String, String)> = inner
4453 .map
4454 .iter()
4455 .map(|(k, v)| (k.clone(), v.clone()))
4456 .collect();
4457 pairs.sort_unstable_by(|(a, _), (b, _)| a.len().cmp(&b.len()).then(a.cmp(b)));
4458 Ok(pairs)
4459 }
4460
4461 pub fn value_bytes_max(&self) -> Result<usize, AgentRuntimeError> {
4465 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_bytes_max");
4466 Ok(inner.map.values().map(|v| v.len()).max().unwrap_or(0))
4467 }
4468
4469 pub fn keys_with_suffix(
4474 &self,
4475 suffix: &str,
4476 ) -> Result<Vec<String>, AgentRuntimeError> {
4477 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys_with_suffix");
4478 let mut keys: Vec<String> = inner
4479 .map
4480 .keys()
4481 .filter(|k| k.ends_with(suffix))
4482 .cloned()
4483 .collect();
4484 keys.sort_unstable();
4485 Ok(keys)
4486 }
4487
4488 pub fn avg_key_length(&self) -> Result<f64, AgentRuntimeError> {
4492 let inner = recover_lock(self.inner.lock(), "WorkingMemory::avg_key_length");
4493 if inner.map.is_empty() {
4494 return Ok(0.0);
4495 }
4496 let total: usize = inner.map.keys().map(|k| k.len()).sum();
4497 Ok(total as f64 / inner.map.len() as f64)
4498 }
4499
4500 pub fn max_key_bytes(&self) -> Result<usize, AgentRuntimeError> {
4504 let inner = recover_lock(self.inner.lock(), "WorkingMemory::max_key_bytes");
4505 Ok(inner.map.keys().map(|k| k.len()).max().unwrap_or(0))
4506 }
4507
4508 pub fn value_count_above_bytes(&self, min_bytes: usize) -> Result<usize, AgentRuntimeError> {
4512 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_count_above_bytes");
4513 Ok(inner.map.values().filter(|v| v.len() > min_bytes).count())
4514 }
4515
4516 pub fn has_key_starting_with(&self, prefix: &str) -> Result<bool, AgentRuntimeError> {
4520 let inner = recover_lock(self.inner.lock(), "WorkingMemory::has_key_starting_with");
4521 Ok(inner.map.keys().any(|k| k.starts_with(prefix)))
4522 }
4523
4524 pub fn key_count_starting_with(&self, prefix: &str) -> Result<usize, AgentRuntimeError> {
4528 let inner = recover_lock(self.inner.lock(), "WorkingMemory::key_count_starting_with");
4529 Ok(inner.map.keys().filter(|k| k.starts_with(prefix)).count())
4530 }
4531
4532 pub fn value_with_max_bytes(&self) -> Result<Option<String>, AgentRuntimeError> {
4537 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_with_max_bytes");
4538 Ok(inner
4539 .map
4540 .values()
4541 .max_by(|a, b| a.len().cmp(&b.len()).then(a.cmp(b)))
4542 .cloned())
4543 }
4544
4545 pub fn values_longer_than(
4550 &self,
4551 min_bytes: usize,
4552 ) -> Result<Vec<String>, AgentRuntimeError> {
4553 let inner = recover_lock(self.inner.lock(), "WorkingMemory::values_longer_than");
4554 let mut keys: Vec<String> = inner
4555 .map
4556 .iter()
4557 .filter(|(_, v)| v.len() > min_bytes)
4558 .map(|(k, _)| k.clone())
4559 .collect();
4560 keys.sort_unstable();
4561 Ok(keys)
4562 }
4563
4564 pub fn has_key_with_prefix(&self, prefix: &str) -> Result<bool, AgentRuntimeError> {
4566 let inner = recover_lock(self.inner.lock(), "WorkingMemory::has_key_with_prefix");
4567 Ok(inner.map.keys().any(|k| k.starts_with(prefix)))
4568 }
4569
4570 pub fn value_count_matching_prefix(
4574 &self,
4575 prefix: &str,
4576 ) -> Result<usize, AgentRuntimeError> {
4577 let inner =
4578 recover_lock(self.inner.lock(), "WorkingMemory::value_count_matching_prefix");
4579 Ok(inner.map.values().filter(|v| v.starts_with(prefix)).count())
4580 }
4581
4582 pub fn value_bytes_total(&self) -> Result<usize, AgentRuntimeError> {
4586 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_bytes_total");
4587 Ok(inner.map.values().map(|v| v.len()).sum())
4588 }
4589
4590 pub fn all_keys_sorted(&self) -> Result<Vec<String>, AgentRuntimeError> {
4594 let inner = recover_lock(self.inner.lock(), "WorkingMemory::all_keys_sorted");
4595 let mut keys: Vec<String> = inner.map.keys().cloned().collect();
4596 keys.sort_unstable();
4597 Ok(keys)
4598 }
4599
4600 pub fn all_values_sorted(&self) -> Result<Vec<String>, AgentRuntimeError> {
4605 let inner = recover_lock(self.inner.lock(), "WorkingMemory::all_values_sorted");
4606 let mut values: Vec<String> = inner.map.values().cloned().collect();
4607 values.sort_unstable();
4608 Ok(values)
4609 }
4610
4611 pub fn keys_with_value_prefix(&self, prefix: &str) -> Result<Vec<String>, AgentRuntimeError> {
4615 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys_with_value_prefix");
4616 Ok(inner
4617 .map
4618 .iter()
4619 .filter(|(_, v)| v.starts_with(prefix))
4620 .map(|(k, _)| k.clone())
4621 .collect())
4622 }
4623
4624 pub fn key_count(&self) -> Result<usize, AgentRuntimeError> {
4626 let inner = recover_lock(self.inner.lock(), "WorkingMemory::key_count");
4627 Ok(inner.map.len())
4628 }
4629
4630 pub fn value_for_key_or_default<'a>(
4632 &self,
4633 key: &str,
4634 default: &'a str,
4635 ) -> Result<String, AgentRuntimeError> {
4636 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_for_key_or_default");
4637 Ok(inner
4638 .map
4639 .get(key)
4640 .map(|v| v.clone())
4641 .unwrap_or_else(|| default.to_string()))
4642 }
4643
4644 pub fn entries_with_key_prefix(
4649 &self,
4650 prefix: &str,
4651 ) -> Result<Vec<(String, String)>, AgentRuntimeError> {
4652 let inner = recover_lock(self.inner.lock(), "WorkingMemory::entries_with_key_prefix");
4653 let mut entries: Vec<(String, String)> = inner
4654 .map
4655 .iter()
4656 .filter(|(k, _)| k.starts_with(prefix))
4657 .map(|(k, v)| (k.clone(), v.clone()))
4658 .collect();
4659 entries.sort_by(|a, b| a.0.cmp(&b.0));
4660 Ok(entries)
4661 }
4662
4663 pub fn has_value_equal_to(&self, val: &str) -> Result<bool, AgentRuntimeError> {
4667 let inner = recover_lock(self.inner.lock(), "WorkingMemory::has_value_equal_to");
4668 Ok(inner.map.values().any(|v| v == val))
4669 }
4670
4671 pub fn value_contains(&self, key: &str, substr: &str) -> Result<bool, AgentRuntimeError> {
4675 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_contains");
4676 Ok(inner.map.get(key).map_or(false, |v| v.contains(substr)))
4677 }
4678
4679 pub fn keys_without_prefix(&self, prefix: &str) -> Result<Vec<String>, AgentRuntimeError> {
4683 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys_without_prefix");
4684 let mut keys: Vec<String> = inner
4685 .map
4686 .keys()
4687 .filter(|k| !k.starts_with(prefix))
4688 .cloned()
4689 .collect();
4690 keys.sort_unstable();
4691 Ok(keys)
4692 }
4693
4694 pub fn count_values_equal_to(&self, val: &str) -> Result<usize, AgentRuntimeError> {
4696 let inner = recover_lock(self.inner.lock(), "WorkingMemory::count_values_equal_to");
4697 Ok(inner.map.values().filter(|v| v.as_str() == val).count())
4698 }
4699
4700 pub fn count_keys_below_bytes(&self, max_bytes: usize) -> Result<usize, AgentRuntimeError> {
4703 let inner = recover_lock(self.inner.lock(), "WorkingMemory::count_keys_below_bytes");
4704 Ok(inner.map.keys().filter(|k| k.len() < max_bytes).count())
4705 }
4706
4707 pub fn value_char_count(&self, key: &str) -> Result<usize, AgentRuntimeError> {
4710 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_char_count");
4711 Ok(inner.map.get(key).map_or(0, |v| v.chars().count()))
4712 }
4713
4714 pub fn keys_longer_than(&self, min_bytes: usize) -> Result<Vec<String>, AgentRuntimeError> {
4718 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys_longer_than");
4719 Ok(inner.map.keys().filter(|k| k.len() > min_bytes).cloned().collect())
4720 }
4721
4722 pub fn keys_shorter_than(&self, max_bytes: usize) -> Result<Vec<String>, AgentRuntimeError> {
4726 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys_shorter_than");
4727 Ok(inner.map.keys().filter(|k| k.len() < max_bytes).cloned().collect())
4728 }
4729
4730 pub fn values_with_prefix(&self, prefix: &str) -> Result<Vec<String>, AgentRuntimeError> {
4732 let inner = recover_lock(self.inner.lock(), "WorkingMemory::values_with_prefix");
4733 Ok(inner
4734 .map
4735 .values()
4736 .filter(|v| v.starts_with(prefix))
4737 .cloned()
4738 .collect())
4739 }
4740
4741 pub fn values_with_suffix(&self, suffix: &str) -> Result<Vec<String>, AgentRuntimeError> {
4743 let inner = recover_lock(self.inner.lock(), "WorkingMemory::values_with_suffix");
4744 Ok(inner
4745 .map
4746 .values()
4747 .filter(|v| v.ends_with(suffix))
4748 .cloned()
4749 .collect())
4750 }
4751
4752 pub fn value_starts_with(&self, key: &str, prefix: &str) -> Result<bool, AgentRuntimeError> {
4756 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_starts_with");
4757 Ok(inner
4758 .map
4759 .get(key)
4760 .map_or(false, |v| v.starts_with(prefix)))
4761 }
4762
4763 pub fn value_length_histogram(
4770 &self,
4771 bucket_size: usize,
4772 ) -> Result<Vec<(usize, usize)>, AgentRuntimeError> {
4773 if bucket_size == 0 {
4774 return Ok(Vec::new());
4775 }
4776 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_length_histogram");
4777 let mut buckets: std::collections::HashMap<usize, usize> =
4778 std::collections::HashMap::new();
4779 for v in inner.map.values() {
4780 let bucket = (v.len() / bucket_size) * bucket_size;
4781 *buckets.entry(bucket).or_insert(0) += 1;
4782 }
4783 let mut result: Vec<(usize, usize)> = buckets.into_iter().collect();
4784 result.sort_unstable_by_key(|(k, _)| *k);
4785 Ok(result)
4786 }
4787
4788 pub fn semantic_key_count(&self, substr: &str) -> Result<usize, AgentRuntimeError> {
4794 let inner = recover_lock(self.inner.lock(), "WorkingMemory::semantic_key_count");
4795 Ok(inner.map.keys().filter(|k| k.contains(substr)).count())
4796 }
4797
4798 pub fn longest_value_bytes(&self) -> Result<usize, AgentRuntimeError> {
4803 let inner = recover_lock(self.inner.lock(), "WorkingMemory::longest_value_bytes");
4804 Ok(inner.map.values().map(|v| v.len()).max().unwrap_or(0))
4805 }
4806
4807 pub fn min_key_length(&self) -> Result<usize, AgentRuntimeError> {
4809 let inner = recover_lock(self.inner.lock(), "WorkingMemory::min_key_length");
4810 Ok(inner.map.keys().map(|k| k.len()).min().unwrap_or(0))
4811 }
4812
4813 pub fn count_matching_prefix(&self, prefix: &str) -> Result<usize, AgentRuntimeError> {
4815 let inner = recover_lock(self.inner.lock(), "WorkingMemory::count_matching_prefix");
4816 Ok(inner.map.keys().filter(|k| k.starts_with(prefix)).count())
4817 }
4818
4819 pub fn entry_count(&self) -> Result<usize, AgentRuntimeError> {
4822 let inner = recover_lock(self.inner.lock(), "WorkingMemory::entry_count");
4823 Ok(inner.map.len())
4824 }
4825
4826 pub fn value_lengths(&self) -> Result<Vec<(String, usize)>, AgentRuntimeError> {
4828 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_lengths");
4829 Ok(inner.map.iter().map(|(k, v)| (k.clone(), v.len())).collect())
4830 }
4831
4832 pub fn keys_with_value_longer_than(&self, threshold: usize) -> Result<Vec<String>, AgentRuntimeError> {
4835 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys_with_value_longer_than");
4836 Ok(inner
4837 .map
4838 .iter()
4839 .filter(|(_, v)| v.len() > threshold)
4840 .map(|(k, _)| k.clone())
4841 .collect())
4842 }
4843
4844 pub fn longest_value_key(&self) -> Result<Option<String>, AgentRuntimeError> {
4846 let inner = recover_lock(self.inner.lock(), "WorkingMemory::longest_value_key");
4847 Ok(inner
4848 .map
4849 .iter()
4850 .max_by_key(|(_, v)| v.len())
4851 .map(|(k, _)| k.clone()))
4852 }
4853
4854 pub fn retain<F>(&self, mut predicate: F) -> Result<usize, AgentRuntimeError>
4859 where
4860 F: FnMut(&str, &str) -> bool,
4861 {
4862 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::retain");
4863 let before = inner.map.len();
4864 inner.map.retain(|k, v| predicate(k.as_str(), v.as_str()));
4865 let surviving: std::collections::HashSet<String> =
4866 inner.map.keys().cloned().collect();
4867 inner.order.retain(|k| surviving.contains(k));
4868 Ok(before - inner.map.len())
4869 }
4870
4871 pub fn merge_from(&self, other: &WorkingMemory) -> Result<usize, AgentRuntimeError> {
4879 let pairs: Vec<(String, String)> = {
4880 let inner = recover_lock(other.inner.lock(), "WorkingMemory::merge_from(read)");
4881 inner.order.iter().filter_map(|k| {
4882 inner.map.get(k).map(|v| (k.clone(), v.clone()))
4883 }).collect()
4884 };
4885 let count = pairs.len();
4886 self.set_many(pairs)?;
4887 Ok(count)
4888 }
4889
4890 pub fn entry_count_satisfying<F>(&self, mut predicate: F) -> Result<usize, AgentRuntimeError>
4892 where
4893 F: FnMut(&str, &str) -> bool,
4894 {
4895 let inner = recover_lock(self.inner.lock(), "WorkingMemory::entry_count_satisfying");
4896 Ok(inner.map.iter().filter(|(k, v)| predicate(k.as_str(), v.as_str())).count())
4897 }
4898
4899 pub fn get_all_keys(&self) -> Result<Vec<String>, AgentRuntimeError> {
4901 let inner = recover_lock(self.inner.lock(), "WorkingMemory::get_all_keys");
4902 Ok(inner.order.iter().cloned().collect())
4903 }
4904
4905 pub fn replace_all(
4911 &self,
4912 map: std::collections::HashMap<String, String>,
4913 ) -> Result<(), AgentRuntimeError> {
4914 let capacity = self.capacity;
4915 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::replace_all");
4916 inner.map.clear();
4917 inner.order.clear();
4918 for (k, v) in map {
4919 if inner.map.len() >= capacity {
4920 if let Some(oldest) = inner.order.pop_front() {
4921 inner.map.remove(&oldest);
4922 }
4923 }
4924 inner.order.push_back(k.clone());
4925 inner.map.insert(k, v);
4926 }
4927 Ok(())
4928 }
4929
4930 pub fn values_containing(&self, substring: &str) -> Result<Vec<String>, AgentRuntimeError> {
4935 let inner = recover_lock(self.inner.lock(), "WorkingMemory::values_containing");
4936 let result = inner
4937 .order
4938 .iter()
4939 .filter_map(|k| {
4940 inner.map.get(k).filter(|v| v.contains(substring)).cloned()
4941 })
4942 .collect();
4943 Ok(result)
4944 }
4945
4946 pub fn keys_sorted(&self) -> Result<Vec<String>, AgentRuntimeError> {
4953 let inner = recover_lock(self.inner.lock(), "WorkingMemory::keys_sorted");
4954 let mut keys: Vec<String> = inner.map.keys().cloned().collect();
4955 keys.sort_unstable();
4956 Ok(keys)
4957 }
4958
4959 pub fn value_for_longest_key(&self) -> Result<Option<String>, AgentRuntimeError> {
4965 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_for_longest_key");
4966 let best_key = inner
4967 .map
4968 .keys()
4969 .max_by(|a, b| a.len().cmp(&b.len()).then_with(|| b.cmp(a)));
4970 Ok(best_key.and_then(|k| inner.map.get(k).cloned()))
4971 }
4972
4973 pub fn contains_value(&self, value: &str) -> Result<bool, AgentRuntimeError> {
4980 let inner = recover_lock(self.inner.lock(), "WorkingMemory::contains_value");
4981 Ok(inner.map.values().any(|v| v == value))
4982 }
4983
4984 pub fn shortest_key(&self) -> Result<Option<String>, AgentRuntimeError> {
4989 let inner = recover_lock(self.inner.lock(), "WorkingMemory::shortest_key");
4990 Ok(inner
4991 .map
4992 .keys()
4993 .min_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.cmp(b)))
4994 .cloned())
4995 }
4996
4997 pub fn entry_byte_pairs(&self) -> Result<Vec<(usize, usize)>, AgentRuntimeError> {
5002 let inner = recover_lock(self.inner.lock(), "WorkingMemory::entry_byte_pairs");
5003 Ok(inner.map.iter().map(|(k, v)| (k.len(), v.len())).collect())
5004 }
5005
5006 pub fn total_bytes(&self) -> Result<usize, AgentRuntimeError> {
5011 let inner = recover_lock(self.inner.lock(), "WorkingMemory::total_bytes");
5012 Ok(inner.map.iter().map(|(k, v)| k.len() + v.len()).sum())
5013 }
5014
5015 pub fn key_with_longest_value(&self) -> Result<Option<String>, AgentRuntimeError> {
5021 let inner = recover_lock(self.inner.lock(), "WorkingMemory::key_with_longest_value");
5022 Ok(inner
5023 .map
5024 .iter()
5025 .max_by(|(ka, va), (kb, vb)| va.len().cmp(&vb.len()).then_with(|| kb.cmp(ka)))
5026 .map(|(k, _)| k.clone()))
5027 }
5028
5029 pub fn count_below_value_length(&self, max_bytes: usize) -> Result<usize, AgentRuntimeError> {
5033 let inner = recover_lock(self.inner.lock(), "WorkingMemory::count_below_value_length");
5034 Ok(inner.map.values().filter(|v| v.len() < max_bytes).count())
5035 }
5036
5037 pub fn value_for_shortest_key(&self) -> Result<Option<String>, AgentRuntimeError> {
5043 let inner = recover_lock(self.inner.lock(), "WorkingMemory::value_for_shortest_key");
5044 let best_key = inner
5045 .map
5046 .keys()
5047 .min_by(|a, b| a.len().cmp(&b.len()).then_with(|| a.cmp(b)));
5048 Ok(best_key.and_then(|k| inner.map.get(k).cloned()))
5049 }
5050
5051 pub fn key_value_pairs_above_length(
5057 &self,
5058 min_value_bytes: usize,
5059 ) -> Result<Vec<(String, String)>, AgentRuntimeError> {
5060 let inner = recover_lock(self.inner.lock(), "WorkingMemory::key_value_pairs_above_length");
5061 let pairs: Vec<(String, String)> = inner
5062 .map
5063 .iter()
5064 .filter(|(_, v)| v.len() > min_value_bytes)
5065 .map(|(k, v)| (k.clone(), v.clone()))
5066 .collect();
5067 Ok(pairs)
5068 }
5069
5070 pub fn get_or_insert(
5077 &self,
5078 key: impl Into<String>,
5079 default: impl Into<String>,
5080 ) -> Result<String, AgentRuntimeError> {
5081 let key = key.into();
5082 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::get_or_insert");
5083 if let Some(v) = inner.map.get(&key) {
5084 return Ok(v.clone());
5085 }
5086 let value = default.into();
5087 let cap = self.capacity;
5089 if inner.map.len() >= cap && cap > 0 {
5090 if let Some(oldest) = inner.order.pop_front() {
5091 inner.map.remove(&oldest);
5092 }
5093 }
5094 inner.map.insert(key.clone(), value.clone());
5095 inner.order.push_back(key);
5096 Ok(value)
5097 }
5098
5099 pub fn increment(&self, key: impl Into<String>, step: i64) -> Result<i64, AgentRuntimeError> {
5108 let key = key.into();
5109 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::increment");
5110 let current: i64 = if let Some(v) = inner.map.get(&key) {
5111 v.parse::<i64>().map_err(|_| {
5112 AgentRuntimeError::Memory(format!(
5113 "WorkingMemory::increment: value for key '{key}' is not a valid integer"
5114 ))
5115 })?
5116 } else {
5117 0
5118 };
5119 let new_val = current.saturating_add(step);
5120 let new_str = new_val.to_string();
5121 if !inner.map.contains_key(&key) {
5122 let cap = self.capacity;
5124 if inner.map.len() >= cap && cap > 0 {
5125 if let Some(oldest) = inner.order.pop_front() {
5126 inner.map.remove(&oldest);
5127 }
5128 }
5129 inner.order.push_back(key.clone());
5130 }
5131 inner.map.insert(key, new_str);
5132 Ok(new_val)
5133 }
5134
5135 pub fn swap(&self, key_a: &str, key_b: &str) -> Result<(), AgentRuntimeError> {
5140 let mut inner = recover_lock(self.inner.lock(), "WorkingMemory::swap");
5141 let a = inner.map.get(key_a).cloned().ok_or_else(|| {
5142 AgentRuntimeError::Memory(format!("WorkingMemory::swap: key '{key_a}' not found"))
5143 })?;
5144 let b = inner.map.get(key_b).cloned().ok_or_else(|| {
5145 AgentRuntimeError::Memory(format!("WorkingMemory::swap: key '{key_b}' not found"))
5146 })?;
5147 inner.map.insert(key_a.to_owned(), b);
5148 inner.map.insert(key_b.to_owned(), a);
5149 Ok(())
5150 }
5151
5152 pub fn non_empty_values(&self) -> Result<Vec<String>, AgentRuntimeError> {
5157 let inner = recover_lock(self.inner.lock(), "WorkingMemory::non_empty_values");
5158 let values: Vec<String> = inner
5159 .order
5160 .iter()
5161 .filter_map(|k| inner.map.get(k))
5162 .filter(|v| !v.is_empty())
5163 .cloned()
5164 .collect();
5165 Ok(values)
5166 }
5167
5168 pub fn values_sorted(&self) -> Result<Vec<String>, AgentRuntimeError> {
5172 let inner = recover_lock(self.inner.lock(), "WorkingMemory::values_sorted");
5173 let mut values: Vec<String> = inner.map.values().cloned().collect();
5174 values.sort_unstable();
5175 Ok(values)
5176 }
5177
5178 pub fn count_keys_above_bytes(&self, min_bytes: usize) -> Result<usize, AgentRuntimeError> {
5183 let inner = recover_lock(self.inner.lock(), "WorkingMemory::count_keys_above_bytes");
5184 Ok(inner.map.keys().filter(|k| k.len() > min_bytes).count())
5185 }
5186
5187 pub fn has_duplicate_values(&self) -> Result<bool, AgentRuntimeError> {
5191 let inner = recover_lock(self.inner.lock(), "WorkingMemory::has_duplicate_values");
5192 let mut seen = std::collections::HashSet::new();
5193 for v in inner.map.values() {
5194 if !seen.insert(v.as_str()) {
5195 return Ok(true);
5196 }
5197 }
5198 Ok(false)
5199 }
5200
5201 pub fn shortest_value(&self) -> Result<Option<String>, AgentRuntimeError> {
5206 let inner = recover_lock(self.inner.lock(), "WorkingMemory::shortest_value");
5207 Ok(inner
5208 .map
5209 .values()
5210 .min_by(|a, b| a.len().cmp(&b.len()).then(a.cmp(b)))
5211 .cloned())
5212 }
5213
5214 pub fn keys_with_value_equal_to(&self, value: &str) -> Result<Vec<String>, AgentRuntimeError> {
5219 let inner =
5220 recover_lock(self.inner.lock(), "WorkingMemory::keys_with_value_equal_to");
5221 let mut keys: Vec<String> = inner
5222 .map
5223 .iter()
5224 .filter(|(_, v)| v.as_str() == value)
5225 .map(|(k, _)| k.clone())
5226 .collect();
5227 keys.sort_unstable();
5228 Ok(keys)
5229 }
5230
5231 pub fn all_values_non_empty(&self) -> Result<bool, AgentRuntimeError> {
5238 let inner =
5239 recover_lock(self.inner.lock(), "WorkingMemory::all_values_non_empty");
5240 Ok(inner.map.values().all(|v| !v.is_empty()))
5241 }
5242
5243 pub fn average_value_length(&self) -> Result<f64, AgentRuntimeError> {
5247 let inner =
5248 recover_lock(self.inner.lock(), "WorkingMemory::average_value_length");
5249 let count = inner.map.len();
5250 if count == 0 {
5251 return Ok(0.0);
5252 }
5253 let total: usize = inner.map.values().map(|v| v.len()).sum();
5254 Ok(total as f64 / count as f64)
5255 }
5256
5257}
5258
5259#[cfg(test)]
5262mod tests {
5263 use super::*;
5264
5265 #[test]
5268 fn test_agent_id_new_stores_string() {
5269 let id = AgentId::new("agent-1");
5270 assert_eq!(id.0, "agent-1");
5271 }
5272
5273 #[test]
5274 fn test_agent_id_random_is_unique() {
5275 let a = AgentId::random();
5276 let b = AgentId::random();
5277 assert_ne!(a, b);
5278 }
5279
5280 #[test]
5281 fn test_memory_id_new_stores_string() {
5282 let id = MemoryId::new("mem-1");
5283 assert_eq!(id.0, "mem-1");
5284 }
5285
5286 #[test]
5287 fn test_memory_id_random_is_unique() {
5288 let a = MemoryId::random();
5289 let b = MemoryId::random();
5290 assert_ne!(a, b);
5291 }
5292
5293 #[test]
5296 fn test_memory_item_new_clamps_importance_above_one() {
5297 let item = MemoryItem::new(AgentId::new("a"), "test", 1.5, vec![]);
5298 assert_eq!(item.importance, 1.0);
5299 }
5300
5301 #[test]
5302 fn test_memory_item_new_clamps_importance_below_zero() {
5303 let item = MemoryItem::new(AgentId::new("a"), "test", -0.5, vec![]);
5304 assert_eq!(item.importance, 0.0);
5305 }
5306
5307 #[test]
5308 fn test_memory_item_new_preserves_valid_importance() {
5309 let item = MemoryItem::new(AgentId::new("a"), "test", 0.7, vec![]);
5310 assert!((item.importance - 0.7).abs() < 1e-6);
5311 }
5312
5313 #[test]
5316 fn test_decay_policy_rejects_zero_half_life() {
5317 assert!(DecayPolicy::exponential(0.0).is_err());
5318 }
5319
5320 #[test]
5321 fn test_decay_policy_rejects_negative_half_life() {
5322 assert!(DecayPolicy::exponential(-1.0).is_err());
5323 }
5324
5325 #[test]
5326 fn test_decay_policy_no_decay_at_age_zero() {
5327 let p = DecayPolicy::exponential(24.0).unwrap();
5328 let decayed = p.apply(1.0, 0.0);
5329 assert!((decayed - 1.0).abs() < 1e-5);
5330 }
5331
5332 #[test]
5333 fn test_decay_policy_half_importance_at_half_life() {
5334 let p = DecayPolicy::exponential(24.0).unwrap();
5335 let decayed = p.apply(1.0, 24.0);
5336 assert!((decayed - 0.5).abs() < 1e-5);
5337 }
5338
5339 #[test]
5340 fn test_decay_policy_quarter_importance_at_two_half_lives() {
5341 let p = DecayPolicy::exponential(24.0).unwrap();
5342 let decayed = p.apply(1.0, 48.0);
5343 assert!((decayed - 0.25).abs() < 1e-5);
5344 }
5345
5346 #[test]
5347 fn test_decay_policy_result_is_clamped_to_zero_one() {
5348 let p = DecayPolicy::exponential(1.0).unwrap();
5349 let decayed = p.apply(0.0, 1000.0);
5350 assert!(decayed >= 0.0 && decayed <= 1.0);
5351 }
5352
5353 #[test]
5356 fn test_episodic_store_add_episode_returns_id() {
5357 let store = EpisodicStore::new();
5358 let id = store.add_episode(AgentId::new("a"), "event", 0.8).unwrap();
5359 assert!(!id.0.is_empty());
5360 }
5361
5362 #[test]
5363 fn test_episodic_store_recall_returns_stored_item() {
5364 let store = EpisodicStore::new();
5365 let agent = AgentId::new("agent-1");
5366 store
5367 .add_episode(agent.clone(), "hello world", 0.9)
5368 .unwrap();
5369 let items = store.recall(&agent, 10).unwrap();
5370 assert_eq!(items.len(), 1);
5371 assert_eq!(items[0].content, "hello world");
5372 }
5373
5374 #[test]
5375 fn test_episodic_store_recall_filters_by_agent() {
5376 let store = EpisodicStore::new();
5377 let a = AgentId::new("agent-a");
5378 let b = AgentId::new("agent-b");
5379 store.add_episode(a.clone(), "for a", 0.5).unwrap();
5380 store.add_episode(b.clone(), "for b", 0.5).unwrap();
5381 let items = store.recall(&a, 10).unwrap();
5382 assert_eq!(items.len(), 1);
5383 assert_eq!(items[0].content, "for a");
5384 }
5385
5386 #[test]
5387 fn test_episodic_store_recall_sorted_by_descending_importance() {
5388 let store = EpisodicStore::new();
5389 let agent = AgentId::new("agent-1");
5390 store.add_episode(agent.clone(), "low", 0.1).unwrap();
5391 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5392 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
5393 let items = store.recall(&agent, 10).unwrap();
5394 assert_eq!(items[0].content, "high");
5395 assert_eq!(items[1].content, "mid");
5396 assert_eq!(items[2].content, "low");
5397 }
5398
5399 #[test]
5400 fn test_episodic_store_recall_respects_limit() {
5401 let store = EpisodicStore::new();
5402 let agent = AgentId::new("agent-1");
5403 for i in 0..5 {
5404 store
5405 .add_episode(agent.clone(), format!("item {i}"), 0.5)
5406 .unwrap();
5407 }
5408 let items = store.recall(&agent, 3).unwrap();
5409 assert_eq!(items.len(), 3);
5410 }
5411
5412 #[test]
5413 fn test_episodic_store_len_tracks_insertions() {
5414 let store = EpisodicStore::new();
5415 let agent = AgentId::new("a");
5416 store.add_episode(agent.clone(), "a", 0.5).unwrap();
5417 store.add_episode(agent.clone(), "b", 0.5).unwrap();
5418 assert_eq!(store.len().unwrap(), 2);
5419 }
5420
5421 #[test]
5422 fn test_episodic_store_is_empty_initially() {
5423 let store = EpisodicStore::new();
5424 assert!(store.is_empty().unwrap());
5425 }
5426
5427 #[test]
5428 fn test_episodic_store_with_decay_reduces_importance() {
5429 let policy = DecayPolicy::exponential(0.001).unwrap(); let store = EpisodicStore::with_decay(policy);
5431 let agent = AgentId::new("a");
5432
5433 let old_ts = Utc::now() - chrono::Duration::hours(1);
5435 store
5436 .add_episode_at(agent.clone(), "old event", 1.0, old_ts)
5437 .unwrap();
5438
5439 let items = store.recall(&agent, 10).unwrap();
5440 assert_eq!(items.len(), 1);
5442 assert!(
5443 items[0].importance < 0.01,
5444 "expected near-zero importance, got {}",
5445 items[0].importance
5446 );
5447 }
5448
5449 #[test]
5452 fn test_max_age_rejects_zero() {
5453 assert!(EpisodicStore::with_max_age(0.0).is_err());
5454 }
5455
5456 #[test]
5457 fn test_max_age_rejects_negative() {
5458 assert!(EpisodicStore::with_max_age(-1.0).is_err());
5459 }
5460
5461 #[test]
5462 fn test_max_age_evicts_old_items_on_recall() {
5463 let store = EpisodicStore::with_max_age(0.001).unwrap();
5465 let agent = AgentId::new("a");
5466
5467 let old_ts = Utc::now() - chrono::Duration::hours(1);
5468 store
5469 .add_episode_at(agent.clone(), "old", 0.9, old_ts)
5470 .unwrap();
5471 store.add_episode(agent.clone(), "new", 0.5).unwrap();
5472
5473 let items = store.recall(&agent, 10).unwrap();
5474 assert_eq!(items.len(), 1, "old item should be evicted by max_age");
5475 assert_eq!(items[0].content, "new");
5476 }
5477
5478 #[test]
5479 fn test_max_age_evicts_old_items_on_add() {
5480 let store = EpisodicStore::with_max_age(0.001).unwrap();
5481 let agent = AgentId::new("a");
5482
5483 let old_ts = Utc::now() - chrono::Duration::hours(1);
5484 store
5485 .add_episode_at(agent.clone(), "old", 0.9, old_ts)
5486 .unwrap();
5487 store.add_episode(agent.clone(), "new", 0.5).unwrap();
5489
5490 assert_eq!(store.len().unwrap(), 1);
5491 }
5492
5493 #[test]
5496 fn test_recall_increments_recall_count() {
5497 let store = EpisodicStore::new();
5498 let agent = AgentId::new("agent-rc");
5499 store.add_episode(agent.clone(), "memory", 0.5).unwrap();
5500
5501 let items = store.recall(&agent, 10).unwrap();
5503 assert_eq!(items[0].recall_count, 1);
5504
5505 let items = store.recall(&agent, 10).unwrap();
5507 assert_eq!(items[0].recall_count, 2);
5508 }
5509
5510 #[test]
5511 fn test_hybrid_recall_policy_prefers_recently_used() {
5512 let store = EpisodicStore::with_recall_policy(RecallPolicy::Hybrid {
5513 recency_weight: 0.1,
5514 frequency_weight: 2.0,
5515 });
5516 let agent = AgentId::new("agent-hybrid");
5517
5518 let old_ts = Utc::now() - chrono::Duration::hours(48);
5519 store
5520 .add_episode_at(agent.clone(), "old_frequent", 0.5, old_ts)
5521 .unwrap();
5522 store.add_episode(agent.clone(), "new_never", 0.5).unwrap();
5523
5524 store.bump_recall_count_by_content("old_frequent", 100);
5526
5527 let items = store.recall(&agent, 10).unwrap();
5528 assert_eq!(items.len(), 2);
5529 assert_eq!(
5530 items[0].content, "old_frequent",
5531 "hybrid policy should rank the frequently-recalled item first"
5532 );
5533 }
5534
5535 #[test]
5536 fn test_per_agent_capacity_evicts_lowest_importance() {
5537 let store = EpisodicStore::with_per_agent_capacity(2);
5538 let agent = AgentId::new("agent-cap");
5539
5540 store.add_episode(agent.clone(), "low", 0.1).unwrap();
5542 store.add_episode(agent.clone(), "high", 0.9).unwrap();
5543 store.add_episode(agent.clone(), "new", 0.5).unwrap();
5546
5547 assert_eq!(
5548 store.len().unwrap(),
5549 2,
5550 "store should hold exactly 2 items after eviction"
5551 );
5552
5553 let items = store.recall(&agent, 10).unwrap();
5554 let contents: Vec<&str> = items.iter().map(|i| i.content.as_str()).collect();
5555 assert!(
5556 !contents.contains(&"low"),
5557 "the pre-existing lowest-importance item should have been evicted; remaining: {:?}",
5558 contents
5559 );
5560 assert!(
5561 contents.contains(&"new"),
5562 "the newly added item must never be evicted; remaining: {:?}",
5563 contents
5564 );
5565 }
5566
5567 #[test]
5570 fn test_many_agents_do_not_see_each_others_memories() {
5571 let store = EpisodicStore::new();
5572 let n_agents = 20usize;
5573 for i in 0..n_agents {
5574 let agent = AgentId::new(format!("agent-{i}"));
5575 for j in 0..5 {
5576 store
5577 .add_episode(agent.clone(), format!("item-{i}-{j}"), 0.5)
5578 .unwrap();
5579 }
5580 }
5581 for i in 0..n_agents {
5583 let agent = AgentId::new(format!("agent-{i}"));
5584 let items = store.recall(&agent, 100).unwrap();
5585 assert_eq!(
5586 items.len(),
5587 5,
5588 "agent {i} should see exactly 5 items, got {}",
5589 items.len()
5590 );
5591 for item in &items {
5592 assert!(
5593 item.content.starts_with(&format!("item-{i}-")),
5594 "agent {i} saw foreign item: {}",
5595 item.content
5596 );
5597 }
5598 }
5599 }
5600
5601 #[test]
5604 fn test_agent_memory_count_returns_zero_for_unknown_agent() {
5605 let store = EpisodicStore::new();
5606 let count = store.agent_memory_count(&AgentId::new("ghost")).unwrap();
5607 assert_eq!(count, 0);
5608 }
5609
5610 #[test]
5611 fn test_agent_memory_count_tracks_insertions() {
5612 let store = EpisodicStore::new();
5613 let agent = AgentId::new("a");
5614 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
5615 store.add_episode(agent.clone(), "e2", 0.5).unwrap();
5616 assert_eq!(store.agent_memory_count(&agent).unwrap(), 2);
5617 }
5618
5619 #[test]
5620 fn test_list_agents_returns_all_known_agents() {
5621 let store = EpisodicStore::new();
5622 let a = AgentId::new("agent-a");
5623 let b = AgentId::new("agent-b");
5624 store.add_episode(a.clone(), "x", 0.5).unwrap();
5625 store.add_episode(b.clone(), "y", 0.5).unwrap();
5626 let agents = store.list_agents().unwrap();
5627 assert_eq!(agents.len(), 2);
5628 assert!(agents.contains(&a));
5629 assert!(agents.contains(&b));
5630 }
5631
5632 #[test]
5633 fn test_list_agents_empty_when_no_episodes() {
5634 let store = EpisodicStore::new();
5635 let agents = store.list_agents().unwrap();
5636 assert!(agents.is_empty());
5637 }
5638
5639 #[test]
5640 fn test_purge_agent_memories_removes_all_for_agent() {
5641 let store = EpisodicStore::new();
5642 let a = AgentId::new("a");
5643 let b = AgentId::new("b");
5644 store.add_episode(a.clone(), "ep1", 0.5).unwrap();
5645 store.add_episode(a.clone(), "ep2", 0.5).unwrap();
5646 store.add_episode(b.clone(), "ep-b", 0.5).unwrap();
5647
5648 let removed = store.purge_agent_memories(&a).unwrap();
5649 assert_eq!(removed, 2);
5650 assert_eq!(store.agent_memory_count(&a).unwrap(), 0);
5651 assert_eq!(store.agent_memory_count(&b).unwrap(), 1);
5652 assert_eq!(store.len().unwrap(), 1);
5653 }
5654
5655 #[test]
5656 fn test_purge_agent_memories_returns_zero_for_unknown_agent() {
5657 let store = EpisodicStore::new();
5658 let removed = store.purge_agent_memories(&AgentId::new("ghost")).unwrap();
5659 assert_eq!(removed, 0);
5660 }
5661
5662 #[test]
5665 fn test_recall_returns_empty_when_all_items_are_stale() {
5666 let store = EpisodicStore::with_max_age(0.001).unwrap();
5668 let agent = AgentId::new("stale-agent");
5669
5670 let old_ts = Utc::now() - chrono::Duration::hours(1);
5671 store
5672 .add_episode_at(agent.clone(), "stale-1", 0.9, old_ts)
5673 .unwrap();
5674 store
5675 .add_episode_at(agent.clone(), "stale-2", 0.7, old_ts)
5676 .unwrap();
5677
5678 let items = store.recall(&agent, 100).unwrap();
5679 assert!(
5680 items.is_empty(),
5681 "all stale items should be evicted on recall, got {}",
5682 items.len()
5683 );
5684 }
5685
5686 #[test]
5689 fn test_concurrent_add_and_recall_are_consistent() {
5690 use std::sync::Arc;
5691 use std::thread;
5692
5693 let store = Arc::new(EpisodicStore::new());
5694 let agent = AgentId::new("concurrent-agent");
5695 let n_threads = 8;
5696 let items_per_thread = 25;
5697
5698 let mut handles = Vec::new();
5700 for t in 0..n_threads {
5701 let s = Arc::clone(&store);
5702 let a = agent.clone();
5703 handles.push(thread::spawn(move || {
5704 for i in 0..items_per_thread {
5705 s.add_episode(a.clone(), format!("t{t}-i{i}"), 0.5).unwrap();
5706 }
5707 }));
5708 }
5709 for h in handles {
5710 h.join().unwrap();
5711 }
5712
5713 let mut read_handles = Vec::new();
5715 for _ in 0..n_threads {
5716 let s = Arc::clone(&store);
5717 let a = agent.clone();
5718 read_handles.push(thread::spawn(move || {
5719 let items = s.recall(&a, 1000).unwrap();
5720 assert!(items.len() <= n_threads * items_per_thread);
5721 }));
5722 }
5723 for h in read_handles {
5724 h.join().unwrap();
5725 }
5726 }
5727
5728 #[test]
5731 fn test_concurrent_capacity_eviction_never_exceeds_cap() {
5732 use std::sync::Arc;
5733 use std::thread;
5734
5735 let cap = 5usize;
5736 let store = Arc::new(EpisodicStore::with_per_agent_capacity(cap));
5737 let agent = AgentId::new("cap-agent");
5738 let n_threads = 8;
5739 let items_per_thread = 10;
5740
5741 let mut handles = Vec::new();
5742 for t in 0..n_threads {
5743 let s = Arc::clone(&store);
5744 let a = agent.clone();
5745 handles.push(thread::spawn(move || {
5746 for i in 0..items_per_thread {
5747 let importance = (t * items_per_thread + i) as f32 / 100.0;
5748 s.add_episode(a.clone(), format!("t{t}-i{i}"), importance)
5749 .unwrap();
5750 }
5751 }));
5752 }
5753 for h in handles {
5754 h.join().unwrap();
5755 }
5756
5757 let count = store.agent_memory_count(&agent).unwrap();
5762 assert!(
5763 count <= cap + n_threads,
5764 "expected at most {} items, got {}",
5765 cap + n_threads,
5766 count
5767 );
5768 }
5769
5770 #[test]
5773 fn test_semantic_store_store_and_retrieve_all() {
5774 let store = SemanticStore::new();
5775 store.store("key1", "value1", vec!["tag-a".into()]).unwrap();
5776 store.store("key2", "value2", vec!["tag-b".into()]).unwrap();
5777 let results = store.retrieve(&[]).unwrap();
5778 assert_eq!(results.len(), 2);
5779 }
5780
5781 #[test]
5782 fn test_semantic_store_retrieve_filters_by_tag() {
5783 let store = SemanticStore::new();
5784 store
5785 .store("k1", "v1", vec!["rust".into(), "async".into()])
5786 .unwrap();
5787 store.store("k2", "v2", vec!["rust".into()]).unwrap();
5788 let results = store.retrieve(&["async"]).unwrap();
5789 assert_eq!(results.len(), 1);
5790 assert_eq!(results[0].0, "k1");
5791 }
5792
5793 #[test]
5794 fn test_semantic_store_retrieve_requires_all_tags() {
5795 let store = SemanticStore::new();
5796 store
5797 .store("k1", "v1", vec!["a".into(), "b".into()])
5798 .unwrap();
5799 store.store("k2", "v2", vec!["a".into()]).unwrap();
5800 let results = store.retrieve(&["a", "b"]).unwrap();
5801 assert_eq!(results.len(), 1);
5802 }
5803
5804 #[test]
5805 fn test_semantic_store_is_empty_initially() {
5806 let store = SemanticStore::new();
5807 assert!(store.is_empty().unwrap());
5808 }
5809
5810 #[test]
5811 fn test_semantic_store_len_tracks_insertions() {
5812 let store = SemanticStore::new();
5813 store.store("k", "v", vec![]).unwrap();
5814 assert_eq!(store.len().unwrap(), 1);
5815 }
5816
5817 #[test]
5818 fn test_semantic_store_empty_embedding_is_rejected() {
5819 let store = SemanticStore::new();
5820 let result = store.store_with_embedding("k", "v", vec![], vec![]);
5821 assert!(result.is_err(), "empty embedding should be rejected");
5822 }
5823
5824 #[test]
5825 fn test_semantic_store_dimension_mismatch_is_rejected() {
5826 let store = SemanticStore::new();
5827 store
5828 .store_with_embedding("k1", "v1", vec![], vec![1.0, 0.0])
5829 .unwrap();
5830 let result = store.store_with_embedding("k2", "v2", vec![], vec![1.0, 0.0, 0.0]);
5832 assert!(
5833 result.is_err(),
5834 "embedding dimension mismatch should be rejected"
5835 );
5836 }
5837
5838 #[test]
5839 fn test_semantic_store_retrieve_similar_returns_closest() {
5840 let store = SemanticStore::new();
5841 store
5842 .store_with_embedding("close", "close value", vec![], vec![1.0, 0.0, 0.0])
5843 .unwrap();
5844 store
5845 .store_with_embedding("far", "far value", vec![], vec![0.0, 1.0, 0.0])
5846 .unwrap();
5847
5848 let query = vec![1.0, 0.0, 0.0];
5849 let results = store.retrieve_similar(&query, 2).unwrap();
5850 assert_eq!(results.len(), 2);
5851 assert_eq!(results[0].0, "close");
5852 assert!(
5853 (results[0].2 - 1.0).abs() < 1e-5,
5854 "expected similarity ~1.0, got {}",
5855 results[0].2
5856 );
5857 assert!(
5858 (results[1].2).abs() < 1e-5,
5859 "expected similarity ~0.0, got {}",
5860 results[1].2
5861 );
5862 }
5863
5864 #[test]
5865 fn test_semantic_store_retrieve_similar_ignores_unembedded_entries() {
5866 let store = SemanticStore::new();
5867 store.store("no-emb", "no embedding value", vec![]).unwrap();
5868 store
5869 .store_with_embedding("with-emb", "with embedding value", vec![], vec![1.0, 0.0])
5870 .unwrap();
5871
5872 let query = vec![1.0, 0.0];
5873 let results = store.retrieve_similar(&query, 10).unwrap();
5874 assert_eq!(results.len(), 1, "only the embedded entry should appear");
5875 assert_eq!(results[0].0, "with-emb");
5876 }
5877
5878 #[test]
5879 fn test_cosine_similarity_orthogonal_vectors_return_zero() {
5880 let store = SemanticStore::new();
5881 store
5882 .store_with_embedding("a", "va", vec![], vec![1.0, 0.0])
5883 .unwrap();
5884 store
5885 .store_with_embedding("b", "vb", vec![], vec![0.0, 1.0])
5886 .unwrap();
5887
5888 let query = vec![1.0, 0.0];
5889 let results = store.retrieve_similar(&query, 2).unwrap();
5890 assert_eq!(results.len(), 2);
5891 let b_result = results.iter().find(|(k, _, _)| k == "b").unwrap();
5892 assert!(
5893 b_result.2.abs() < 1e-5,
5894 "expected cosine similarity 0.0 for orthogonal vectors, got {}",
5895 b_result.2
5896 );
5897 }
5898
5899 #[test]
5902 fn test_working_memory_new_rejects_zero_capacity() {
5903 assert!(WorkingMemory::new(0).is_err());
5904 }
5905
5906 #[test]
5907 fn test_working_memory_set_and_get() {
5908 let wm = WorkingMemory::new(10).unwrap();
5909 wm.set("foo", "bar").unwrap();
5910 let val = wm.get("foo").unwrap();
5911 assert_eq!(val, Some("bar".into()));
5912 }
5913
5914 #[test]
5915 fn test_working_memory_get_missing_key_returns_none() {
5916 let wm = WorkingMemory::new(10).unwrap();
5917 assert_eq!(wm.get("missing").unwrap(), None);
5918 }
5919
5920 #[test]
5921 fn test_working_memory_bounded_evicts_oldest() {
5922 let wm = WorkingMemory::new(3).unwrap();
5923 wm.set("k1", "v1").unwrap();
5924 wm.set("k2", "v2").unwrap();
5925 wm.set("k3", "v3").unwrap();
5926 wm.set("k4", "v4").unwrap(); assert_eq!(wm.get("k1").unwrap(), None);
5928 assert_eq!(wm.get("k4").unwrap(), Some("v4".into()));
5929 }
5930
5931 #[test]
5932 fn test_working_memory_update_existing_key_no_eviction() {
5933 let wm = WorkingMemory::new(2).unwrap();
5934 wm.set("k1", "v1").unwrap();
5935 wm.set("k2", "v2").unwrap();
5936 wm.set("k1", "v1-updated").unwrap(); assert_eq!(wm.len().unwrap(), 2);
5938 assert_eq!(wm.get("k1").unwrap(), Some("v1-updated".into()));
5939 assert_eq!(wm.get("k2").unwrap(), Some("v2".into()));
5940 }
5941
5942 #[test]
5943 fn test_working_memory_clear_removes_all() {
5944 let wm = WorkingMemory::new(10).unwrap();
5945 wm.set("a", "1").unwrap();
5946 wm.set("b", "2").unwrap();
5947 wm.clear().unwrap();
5948 assert!(wm.is_empty().unwrap());
5949 }
5950
5951 #[test]
5952 fn test_working_memory_is_empty_initially() {
5953 let wm = WorkingMemory::new(5).unwrap();
5954 assert!(wm.is_empty().unwrap());
5955 }
5956
5957 #[test]
5958 fn test_working_memory_len_tracks_entries() {
5959 let wm = WorkingMemory::new(10).unwrap();
5960 wm.set("a", "1").unwrap();
5961 wm.set("b", "2").unwrap();
5962 assert_eq!(wm.len().unwrap(), 2);
5963 }
5964
5965 #[test]
5966 fn test_working_memory_capacity_never_exceeded() {
5967 let cap = 5usize;
5968 let wm = WorkingMemory::new(cap).unwrap();
5969 for i in 0..20 {
5970 wm.set(format!("key-{i}"), format!("val-{i}")).unwrap();
5971 assert!(wm.len().unwrap() <= cap);
5972 }
5973 }
5974
5975 #[test]
5978 fn test_semantic_dimension_mismatch_on_retrieve_returns_error() {
5979 let store = SemanticStore::new();
5980 store
5981 .store_with_embedding("k1", "v1", vec![], vec![1.0, 0.0, 0.0])
5982 .unwrap();
5983 let result = store.retrieve_similar(&[1.0, 0.0], 10);
5985 assert!(result.is_err(), "dimension mismatch on retrieve should error");
5986 }
5987
5988 #[test]
5993 fn test_clear_agent_memory_removes_all_episodes() {
5994 let store = EpisodicStore::new();
5995 let agent = AgentId::new("a");
5996 store.add_episode(agent.clone(), "ep1", 0.5).unwrap();
5997 store.add_episode(agent.clone(), "ep2", 0.9).unwrap();
5998 store.clear_agent_memory(&agent).unwrap();
5999 let items = store.recall(&agent, 10).unwrap();
6000 assert!(items.is_empty(), "all memories should be cleared");
6001 }
6002
6003 #[test]
6006 fn test_agent_id_as_str() {
6007 let id = AgentId::new("hello");
6008 assert_eq!(id.as_str(), "hello");
6009 }
6010
6011 #[test]
6014 fn test_export_import_agent_memory_round_trip() {
6015 let store = EpisodicStore::new();
6016 let agent = AgentId::new("export-agent");
6017 store.add_episode(agent.clone(), "fact1", 0.8).unwrap();
6018 store.add_episode(agent.clone(), "fact2", 0.6).unwrap();
6019
6020 let exported = store.export_agent_memory(&agent).unwrap();
6021 assert_eq!(exported.len(), 2);
6022
6023 let new_store = EpisodicStore::new();
6024 new_store.import_agent_memory(&agent, exported).unwrap();
6025 let recalled = new_store.recall(&agent, 10).unwrap();
6026 assert_eq!(recalled.len(), 2);
6027 }
6028
6029 #[test]
6032 fn test_working_memory_iter_matches_entries() {
6033 let wm = WorkingMemory::new(10).unwrap();
6034 wm.set("a", "1").unwrap();
6035 wm.set("b", "2").unwrap();
6036 let via_iter = wm.iter().unwrap();
6037 let via_entries = wm.entries().unwrap();
6038 assert_eq!(via_iter, via_entries);
6039 }
6040
6041 #[test]
6044 fn test_agent_id_as_ref_str() {
6045 let id = AgentId::new("ref-test");
6046 let s: &str = id.as_ref();
6047 assert_eq!(s, "ref-test");
6048 }
6049
6050 #[test]
6051 fn test_eviction_policy_oldest_evicts_first_inserted() {
6052 let store = EpisodicStore::with_eviction_policy(EvictionPolicy::Oldest);
6053 let store = {
6059 let inner = EpisodicInner {
6061 items: std::collections::HashMap::new(),
6062 decay: None,
6063 recall_policy: RecallPolicy::Importance,
6064 per_agent_capacity: Some(2),
6065 max_age_hours: None,
6066 eviction_policy: EvictionPolicy::Oldest,
6067 };
6068 EpisodicStore {
6069 inner: std::sync::Arc::new(std::sync::Mutex::new(inner)),
6070 }
6071 };
6072
6073 let agent = AgentId::new("agent");
6074 let t1 = chrono::Utc::now() - chrono::Duration::seconds(100);
6076 let t2 = chrono::Utc::now() - chrono::Duration::seconds(50);
6077 store.add_episode_at(agent.clone(), "oldest", 0.9, t1).unwrap();
6078 store.add_episode_at(agent.clone(), "newer", 0.8, t2).unwrap();
6079 store.add_episode(agent.clone(), "newest", 0.5).unwrap();
6081
6082 let items = store.recall(&agent, 10).unwrap();
6083 assert_eq!(items.len(), 2);
6084 let contents: Vec<&str> = items.iter().map(|i| i.content.as_str()).collect();
6085 assert!(!contents.contains(&"oldest"), "oldest item should have been evicted; got: {contents:?}");
6086 }
6087
6088 #[test]
6091 fn test_search_by_content_finds_matching_episodes() {
6092 let store = EpisodicStore::new();
6093 let agent = AgentId::new("a");
6094 store.add_episode(agent.clone(), "the quick brown fox", 0.9).unwrap();
6095 store.add_episode(agent.clone(), "jumps over the lazy dog", 0.5).unwrap();
6096 store.add_episode(agent.clone(), "hello world", 0.7).unwrap();
6097
6098 let results = store.search_by_content(&agent, "the", 10).unwrap();
6099 assert_eq!(results.len(), 2);
6100 assert_eq!(results[0].content, "the quick brown fox");
6102 }
6103
6104 #[test]
6105 fn test_search_by_content_returns_empty_on_no_match() {
6106 let store = EpisodicStore::new();
6107 let agent = AgentId::new("a");
6108 store.add_episode(agent.clone(), "hello", 0.5).unwrap();
6109 let results = store.search_by_content(&agent, "xyz", 10).unwrap();
6110 assert!(results.is_empty());
6111 }
6112
6113 #[test]
6114 fn test_recall_tagged_filters_by_all_tags() {
6115 let store = EpisodicStore::new();
6116 let agent = AgentId::new("a");
6117 let mut inner = store.inner.lock().unwrap();
6118 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 };
6119 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 };
6120 inner.items.entry(agent.clone()).or_default().push(item1);
6121 inner.items.entry(agent.clone()).or_default().push(item2);
6122 drop(inner);
6123
6124 let results = store.recall_tagged(&agent, &["lang", "sys"], 10).unwrap();
6125 assert_eq!(results.len(), 1);
6126 assert_eq!(results[0].content, "rust");
6127
6128 let all = store.recall_tagged(&agent, &["lang"], 10).unwrap();
6129 assert_eq!(all.len(), 2);
6130 }
6131
6132 #[test]
6133 fn test_recall_recent_returns_newest_first() {
6134 let store = EpisodicStore::new();
6135 let agent = AgentId::new("a");
6136 store.add_episode(agent.clone(), "first", 0.3).unwrap();
6137 store.add_episode(agent.clone(), "second", 0.5).unwrap();
6138 store.add_episode(agent.clone(), "third", 0.9).unwrap();
6139
6140 let recent = store.recall_recent(&agent, 2).unwrap();
6141 assert_eq!(recent.len(), 2);
6142 assert_eq!(recent[0].content, "third");
6143 assert_eq!(recent[1].content, "second");
6144 }
6145
6146 #[test]
6147 fn test_recall_by_id_finds_specific_episode() {
6148 let store = EpisodicStore::new();
6149 let agent = AgentId::new("a");
6150 let id = store.add_episode(agent.clone(), "specific", 0.7).unwrap();
6151 store.add_episode(agent.clone(), "other", 0.5).unwrap();
6152
6153 let found = store.recall_by_id(&agent, &id).unwrap();
6154 assert!(found.is_some());
6155 assert_eq!(found.unwrap().content, "specific");
6156 }
6157
6158 #[test]
6159 fn test_recall_by_id_returns_none_for_unknown_id() {
6160 let store = EpisodicStore::new();
6161 let agent = AgentId::new("a");
6162 let result = store.recall_by_id(&agent, &MemoryId::random()).unwrap();
6163 assert!(result.is_none());
6164 }
6165
6166 #[test]
6167 fn test_update_importance_changes_score() {
6168 let store = EpisodicStore::new();
6169 let agent = AgentId::new("a");
6170 let id = store.add_episode(agent.clone(), "item", 0.5).unwrap();
6171 let updated = store.update_importance(&agent, &id, 0.9).unwrap();
6172 assert!(updated);
6173 let item = store.recall_by_id(&agent, &id).unwrap().unwrap();
6174 assert!((item.importance - 0.9).abs() < 1e-5);
6175 }
6176
6177 #[test]
6178 fn test_merge_from_imports_episodes() {
6179 let src = EpisodicStore::new();
6180 let agent = AgentId::new("a");
6181 src.add_episode(agent.clone(), "ep1", 0.8).unwrap();
6182 src.add_episode(agent.clone(), "ep2", 0.6).unwrap();
6183
6184 let dst = EpisodicStore::new();
6185 let count = dst.merge_from(&src, &agent).unwrap();
6186 assert_eq!(count, 2);
6187 assert_eq!(dst.agent_memory_count(&agent).unwrap(), 2);
6188 }
6189
6190 #[test]
6191 fn test_memory_item_age_hours_is_non_negative() {
6192 let item = MemoryItem::new(AgentId::new("a"), "test", 0.5, vec![]);
6193 let age = item.age_hours();
6194 assert!(age >= 0.0);
6195 }
6196
6197 #[test]
6198 fn test_working_memory_remove_and_contains() {
6199 let wm = WorkingMemory::new(10).unwrap();
6200 wm.set("k", "v").unwrap();
6201 assert!(wm.contains("k").unwrap());
6202 let removed = wm.remove("k").unwrap();
6203 assert!(removed);
6204 assert!(!wm.contains("k").unwrap());
6205 assert!(!wm.remove("k").unwrap()); }
6207
6208 #[test]
6209 fn test_working_memory_keys_in_insertion_order() {
6210 let wm = WorkingMemory::new(10).unwrap();
6211 wm.set("b", "2").unwrap();
6212 wm.set("a", "1").unwrap();
6213 wm.set("c", "3").unwrap();
6214 assert_eq!(wm.keys().unwrap(), vec!["b", "a", "c"]);
6215 }
6216
6217 #[test]
6218 fn test_working_memory_set_many_batch_insert() {
6219 let wm = WorkingMemory::new(10).unwrap();
6220 wm.set_many([("x", "1"), ("y", "2"), ("z", "3")]).unwrap();
6221 assert_eq!(wm.len().unwrap(), 3);
6222 assert_eq!(wm.get("y").unwrap(), Some("2".into()));
6223 }
6224
6225 #[test]
6226 fn test_working_memory_get_or_default() {
6227 let wm = WorkingMemory::new(5).unwrap();
6228 wm.set("a", "val").unwrap();
6229 assert_eq!(wm.get_or_default("a", "fallback").unwrap(), "val");
6230 assert_eq!(wm.get_or_default("missing", "fallback").unwrap(), "fallback");
6231 }
6232
6233 #[test]
6234 fn test_semantic_store_remove_deletes_entry() {
6235 let store = SemanticStore::new();
6236 store.store("k", "v", vec![]).unwrap();
6237 assert_eq!(store.len().unwrap(), 1);
6238 let removed = store.remove("k").unwrap();
6239 assert!(removed);
6240 assert_eq!(store.len().unwrap(), 0);
6241 }
6242
6243 #[test]
6244 fn test_semantic_store_clear_empties_store() {
6245 let store = SemanticStore::new();
6246 store.store("a", "1", vec!["t".into()]).unwrap();
6247 store.store("b", "2", vec!["t".into()]).unwrap();
6248 store.clear().unwrap();
6249 assert!(store.is_empty().unwrap());
6250 }
6251
6252 #[test]
6253 fn test_semantic_store_update_changes_value() {
6254 let store = SemanticStore::new();
6255 store.store("k", "old", vec![]).unwrap();
6256 let updated = store.update("k", "new").unwrap();
6257 assert!(updated);
6258 let (val, _) = store.retrieve_by_key("k").unwrap().unwrap();
6259 assert_eq!(val, "new");
6260 }
6261
6262 #[test]
6263 fn test_semantic_store_retrieve_by_key() {
6264 let store = SemanticStore::new();
6265 store.store("key", "value", vec!["tag1".into()]).unwrap();
6266 let result = store.retrieve_by_key("key").unwrap().unwrap();
6267 assert_eq!(result.0, "value");
6268 assert_eq!(result.1, vec!["tag1".to_string()]);
6269 assert!(store.retrieve_by_key("missing").unwrap().is_none());
6270 }
6271
6272 #[test]
6273 fn test_semantic_store_list_tags() {
6274 let store = SemanticStore::new();
6275 store.store("a", "1", vec!["rust".into(), "sys".into()]).unwrap();
6276 store.store("b", "2", vec!["rust".into(), "ml".into()]).unwrap();
6277 let tags = store.list_tags().unwrap();
6278 assert_eq!(tags, vec!["ml", "rust", "sys"]); }
6280
6281 #[test]
6282 fn test_semantic_store_count_by_tag() {
6283 let store = SemanticStore::new();
6284 store.store("a", "1", vec!["rust".into(), "sys".into()]).unwrap();
6285 store.store("b", "2", vec!["rust".into(), "ml".into()]).unwrap();
6286 store.store("c", "3", vec!["ml".into()]).unwrap();
6287 assert_eq!(store.count_by_tag("rust").unwrap(), 2);
6288 assert_eq!(store.count_by_tag("ml").unwrap(), 2);
6289 assert_eq!(store.count_by_tag("sys").unwrap(), 1);
6290 assert_eq!(store.count_by_tag("absent").unwrap(), 0);
6291 }
6292
6293 #[test]
6294 fn test_working_memory_get_many_returns_present_values() {
6295 let wm = WorkingMemory::new(10).unwrap();
6296 wm.set("a", "alpha".to_string()).unwrap();
6297 wm.set("b", "beta".to_string()).unwrap();
6298 let results = wm.get_many(&["a", "b", "c"]).unwrap();
6300 assert_eq!(results[0].as_deref(), Some("alpha"));
6301 assert_eq!(results[1].as_deref(), Some("beta"));
6302 assert_eq!(results[2], None);
6303 }
6304
6305 #[test]
6306 fn test_working_memory_update_if_exists_changes_value() {
6307 let wm = WorkingMemory::new(5).unwrap();
6308 wm.set("x", "old".to_string()).unwrap();
6309 assert!(wm.update_if_exists("x", "new".to_string()).unwrap());
6310 assert_eq!(wm.get("x").unwrap().as_deref(), Some("new"));
6311 }
6312
6313 #[test]
6314 fn test_working_memory_update_if_exists_returns_false_for_missing() {
6315 let wm = WorkingMemory::new(5).unwrap();
6316 assert!(!wm.update_if_exists("nope", "val".to_string()).unwrap());
6317 }
6318
6319 #[test]
6320 fn test_episodic_total_recall_count_sums_all_accesses() {
6321 let store = EpisodicStore::new();
6322 let agent = AgentId::new("agent1");
6323 store.add_episode(agent.clone(), "first", 0.8).unwrap();
6324 store.add_episode(agent.clone(), "second", 0.5).unwrap();
6325 store.recall(&agent, 10).unwrap();
6327 store.recall(&agent, 10).unwrap();
6328 let total = store.total_recall_count(&agent).unwrap();
6329 assert_eq!(total, 4);
6331 }
6332
6333 #[test]
6334 fn test_episodic_total_recall_count_zero_before_recall() {
6335 let store = EpisodicStore::new();
6336 let agent = AgentId::new("fresh");
6337 store.add_episode(agent.clone(), "ep", 0.5).unwrap();
6338 assert_eq!(store.total_recall_count(&agent).unwrap(), 0);
6339 }
6340
6341 #[test]
6342 fn test_episodic_recall_all_returns_all_items() {
6343 let store = EpisodicStore::new();
6344 let agent = AgentId::new("agent-all");
6345 store.add_episode(agent.clone(), "one", 0.9).unwrap();
6346 store.add_episode(agent.clone(), "two", 0.5).unwrap();
6347 store.add_episode(agent.clone(), "three", 0.1).unwrap();
6348 let all = store.recall_all(&agent).unwrap();
6349 assert_eq!(all.len(), 3);
6350 }
6351
6352 #[test]
6353 fn test_episodic_recall_all_empty_agent_returns_empty() {
6354 let store = EpisodicStore::new();
6355 let agent = AgentId::new("nobody");
6356 let all = store.recall_all(&agent).unwrap();
6357 assert!(all.is_empty());
6358 }
6359
6360 #[test]
6361 fn test_episodic_clear_all_removes_all_agents() {
6362 let store = EpisodicStore::new();
6363 let a1 = AgentId::new("a1");
6364 let a2 = AgentId::new("a2");
6365 store.add_episode(a1.clone(), "ep", 0.5).unwrap();
6366 store.add_episode(a2.clone(), "ep", 0.5).unwrap();
6367 store.clear_all().unwrap();
6368 assert_eq!(store.len().unwrap(), 0);
6369 assert!(store.list_agents().unwrap().is_empty());
6370 }
6371
6372 #[test]
6373 fn test_working_memory_drain_returns_all_and_empties() {
6374 let wm = WorkingMemory::new(10).unwrap();
6375 wm.set("x", "1".to_string()).unwrap();
6376 wm.set("y", "2".to_string()).unwrap();
6377 let drained = wm.drain().unwrap();
6378 assert_eq!(drained.len(), 2);
6379 assert!(wm.is_empty().unwrap());
6380 }
6381
6382 #[test]
6383 fn test_working_memory_drain_preserves_insertion_order() {
6384 let wm = WorkingMemory::new(10).unwrap();
6385 wm.set("a", "1".to_string()).unwrap();
6386 wm.set("b", "2".to_string()).unwrap();
6387 wm.set("c", "3".to_string()).unwrap();
6388 let drained = wm.drain().unwrap();
6389 let keys: Vec<&str> = drained.iter().map(|(k, _)| k.as_str()).collect();
6390 assert_eq!(keys, vec!["a", "b", "c"]);
6391 }
6392
6393 #[test]
6394 fn test_semantic_store_tag_count() {
6395 let store = SemanticStore::new();
6396 store.store("a", "1", vec!["rust".into(), "sys".into()]).unwrap();
6397 store.store("b", "2", vec!["rust".into(), "ml".into()]).unwrap();
6398 assert_eq!(store.tag_count().unwrap(), 3); }
6400
6401 #[test]
6402 fn test_semantic_store_tag_count_empty() {
6403 let store = SemanticStore::new();
6404 assert_eq!(store.tag_count().unwrap(), 0);
6405 }
6406
6407 #[test]
6408 fn test_episodic_top_n_returns_highest_importance() {
6409 let store = EpisodicStore::new();
6410 let agent = AgentId::new("ag");
6411 store.add_episode(agent.clone(), "high", 0.9).unwrap();
6412 store.add_episode(agent.clone(), "med", 0.5).unwrap();
6413 store.add_episode(agent.clone(), "low", 0.1).unwrap();
6414 let top = store.top_n(&agent, 2).unwrap();
6415 assert_eq!(top.len(), 2);
6416 assert_eq!(top[0].content, "high");
6417 assert_eq!(top[1].content, "med");
6418 }
6419
6420 #[test]
6421 fn test_episodic_top_n_zero_returns_all() {
6422 let store = EpisodicStore::new();
6423 let agent = AgentId::new("ag");
6424 store.add_episode(agent.clone(), "a", 0.9).unwrap();
6425 store.add_episode(agent.clone(), "b", 0.5).unwrap();
6426 assert_eq!(store.top_n(&agent, 0).unwrap().len(), 2);
6427 }
6428
6429 #[test]
6430 fn test_episodic_search_by_importance_range() {
6431 let store = EpisodicStore::new();
6432 let agent = AgentId::new("ag");
6433 store.add_episode(agent.clone(), "high", 0.9).unwrap();
6434 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
6435 store.add_episode(agent.clone(), "low", 0.1).unwrap();
6436 let results = store.search_by_importance_range(&agent, 0.4, 1.0, 0).unwrap();
6437 assert_eq!(results.len(), 2);
6438 assert_eq!(results[0].content, "high");
6439 }
6440
6441 #[test]
6442 fn test_semantic_store_contains_true_for_stored_key() {
6443 let store = SemanticStore::new();
6444 store.store("k", "v", vec![]).unwrap();
6445 assert!(store.contains("k").unwrap());
6446 }
6447
6448 #[test]
6449 fn test_semantic_store_contains_false_for_missing_key() {
6450 let store = SemanticStore::new();
6451 assert!(!store.contains("missing").unwrap());
6452 }
6453
6454 #[test]
6455 fn test_working_memory_rename_changes_key() {
6456 let wm = WorkingMemory::new(10).unwrap();
6457 wm.set("old", "value".to_string()).unwrap();
6458 assert!(wm.rename("old", "new").unwrap());
6459 assert_eq!(wm.get("new").unwrap().as_deref(), Some("value"));
6460 assert!(wm.get("old").unwrap().is_none());
6461 }
6462
6463 #[test]
6464 fn test_working_memory_rename_preserves_order() {
6465 let wm = WorkingMemory::new(10).unwrap();
6466 wm.set("a", "1".to_string()).unwrap();
6467 wm.set("b", "2".to_string()).unwrap();
6468 wm.rename("a", "x").unwrap();
6469 assert_eq!(wm.keys().unwrap(), vec!["x", "b"]);
6470 }
6471
6472 #[test]
6473 fn test_working_memory_rename_missing_returns_false() {
6474 let wm = WorkingMemory::new(10).unwrap();
6475 assert!(!wm.rename("nope", "other").unwrap());
6476 }
6477
6478 #[test]
6479 fn test_episodic_importance_stats_basic() {
6480 let store = EpisodicStore::new();
6481 let agent = AgentId::new("ag");
6482 store.add_episode(agent.clone(), "a", 0.2).unwrap();
6483 store.add_episode(agent.clone(), "b", 0.6).unwrap();
6484 store.add_episode(agent.clone(), "c", 1.0).unwrap();
6485 let (count, min, max, mean) = store.importance_stats(&agent).unwrap();
6486 assert_eq!(count, 3);
6487 assert!((min - 0.2).abs() < 1e-5);
6488 assert!((max - 1.0).abs() < 1e-5);
6489 assert!((mean - 0.6).abs() < 1e-4);
6490 }
6491
6492 #[test]
6493 fn test_episodic_importance_stats_empty_agent() {
6494 let store = EpisodicStore::new();
6495 let agent = AgentId::new("nobody");
6496 let (count, min, max, mean) = store.importance_stats(&agent).unwrap();
6497 assert_eq!(count, 0);
6498 assert_eq!(min, 0.0);
6499 assert_eq!(max, 0.0);
6500 assert_eq!(mean, 0.0);
6501 }
6502
6503 #[test]
6504 fn test_semantic_store_keys_returns_stored_keys() {
6505 let store = SemanticStore::new();
6506 store.store("alpha", "v1", vec![]).unwrap();
6507 store.store("beta", "v2", vec![]).unwrap();
6508 let keys = store.keys().unwrap();
6509 assert_eq!(keys.len(), 2);
6510 assert!(keys.contains(&"alpha".to_string()));
6511 assert!(keys.contains(&"beta".to_string()));
6512 }
6513
6514 #[test]
6515 fn test_working_memory_snapshot_clones_contents() {
6516 let wm = WorkingMemory::new(10).unwrap();
6517 wm.set("x", "1".to_string()).unwrap();
6518 wm.set("y", "2".to_string()).unwrap();
6519 let snap = wm.snapshot().unwrap();
6520 assert_eq!(snap.get("x").map(|s| s.as_str()), Some("1"));
6521 assert_eq!(snap.get("y").map(|s| s.as_str()), Some("2"));
6522 assert_eq!(wm.len().unwrap(), 2);
6524 }
6525
6526 #[test]
6529 fn test_memory_item_display_includes_content() {
6530 let agent = AgentId::new("a");
6531 let item = MemoryItem::new(agent, "hello world", 0.75, vec![]);
6532 let s = format!("{item}");
6533 assert!(s.contains("hello world"), "Display should include content");
6534 assert!(s.contains("0.75"), "Display should include importance");
6535 }
6536
6537 #[test]
6538 fn test_decay_policy_half_life_hours_accessor() {
6539 let policy = DecayPolicy::exponential(12.5).unwrap();
6540 assert!((policy.half_life_hours() - 12.5).abs() < f64::EPSILON);
6541 }
6542
6543 #[test]
6544 fn test_semantic_store_list_keys_returns_all_keys() {
6545 let store = SemanticStore::new();
6546 store.store("k1", "v1", vec![]).unwrap();
6547 store.store("k2", "v2", vec![]).unwrap();
6548 let keys = store.list_keys().unwrap();
6549 assert_eq!(keys.len(), 2);
6550 assert!(keys.contains(&"k1".to_string()));
6551 assert!(keys.contains(&"k2".to_string()));
6552 }
6553
6554 #[test]
6555 fn test_semantic_store_update_tags_returns_true_on_found() {
6556 let store = SemanticStore::new();
6557 store.store("k", "v", vec!["old".into()]).unwrap();
6558 let updated = store.update_tags("k", vec!["new".into()]).unwrap();
6559 assert!(updated);
6560 let (_, tags) = store.retrieve_by_key("k").unwrap().unwrap();
6561 assert_eq!(tags, vec!["new".to_string()]);
6562 }
6563
6564 #[test]
6565 fn test_semantic_store_update_tags_returns_false_on_missing() {
6566 let store = SemanticStore::new();
6567 assert!(!store.update_tags("missing", vec![]).unwrap());
6568 }
6569
6570 #[test]
6571 fn test_episodic_store_recall_since_filters_old() {
6572 let store = EpisodicStore::new();
6573 let agent = AgentId::new("a");
6574 let old_ts = Utc::now() - chrono::Duration::hours(2);
6575 store.add_episode_at(agent.clone(), "old", 0.9, old_ts).unwrap();
6576 store.add_episode(agent.clone(), "new", 0.5).unwrap();
6577
6578 let cutoff = Utc::now() - chrono::Duration::minutes(30);
6579 let items = store.recall_since(&agent, cutoff, 0).unwrap();
6580 assert_eq!(items.len(), 1);
6581 assert_eq!(items[0].content, "new");
6582 }
6583
6584 #[test]
6585 fn test_episodic_store_recall_since_limit_respected() {
6586 let store = EpisodicStore::new();
6587 let agent = AgentId::new("a");
6588 for i in 0..5 {
6589 store.add_episode(agent.clone(), format!("item {i}"), 0.5).unwrap();
6590 }
6591 let cutoff = Utc::now() - chrono::Duration::hours(1);
6592 let items = store.recall_since(&agent, cutoff, 2).unwrap();
6593 assert_eq!(items.len(), 2);
6594 }
6595
6596 #[test]
6597 fn test_episodic_store_update_content_returns_true_on_found() {
6598 let store = EpisodicStore::new();
6599 let agent = AgentId::new("a");
6600 let id = store.add_episode(agent.clone(), "original", 0.5).unwrap();
6601 let updated = store.update_content(&agent, &id, "updated").unwrap();
6602 assert!(updated);
6603 let item = store.recall_by_id(&agent, &id).unwrap().unwrap();
6604 assert_eq!(item.content, "updated");
6605 }
6606
6607 #[test]
6608 fn test_episodic_store_update_content_returns_false_on_missing() {
6609 let store = EpisodicStore::new();
6610 let agent = AgentId::new("a");
6611 let fake_id = MemoryId::new("does-not-exist");
6612 assert!(!store.update_content(&agent, &fake_id, "x").unwrap());
6613 }
6614
6615 #[test]
6616 fn test_working_memory_capacity_matches_constructor() {
6617 let wm = WorkingMemory::new(7).unwrap();
6618 assert_eq!(wm.capacity(), 7);
6619 }
6620
6621 #[test]
6624 fn test_add_episode_with_tags_stores_and_recalls() {
6625 let store = EpisodicStore::new();
6626 let agent = AgentId::new("a");
6627 let id = store
6628 .add_episode_with_tags(agent.clone(), "tagged", 0.8, vec!["t1".to_string(), "t2".to_string()])
6629 .unwrap();
6630 let item = store.recall_by_id(&agent, &id).unwrap().unwrap();
6631 assert_eq!(item.content, "tagged");
6632 assert_eq!(item.tags, vec!["t1", "t2"]);
6633 }
6634
6635 #[test]
6636 fn test_remove_by_id_deletes_episode() {
6637 let store = EpisodicStore::new();
6638 let agent = AgentId::new("a");
6639 let id = store.add_episode(agent.clone(), "to-delete", 0.5).unwrap();
6640 assert!(store.remove_by_id(&agent, &id).unwrap());
6641 assert!(store.recall_by_id(&agent, &id).unwrap().is_none());
6642 }
6643
6644 #[test]
6645 fn test_remove_by_id_returns_false_for_missing() {
6646 let store = EpisodicStore::new();
6647 let agent = AgentId::new("a");
6648 let fake = MemoryId::new("does-not-exist");
6649 assert!(!store.remove_by_id(&agent, &fake).unwrap());
6650 }
6651
6652 #[test]
6653 fn test_update_tags_by_id_replaces_tags() {
6654 let store = EpisodicStore::new();
6655 let agent = AgentId::new("a");
6656 let id = store
6657 .add_episode_with_tags(agent.clone(), "x", 0.5, vec!["old".to_string()])
6658 .unwrap();
6659 let updated = store
6660 .update_tags_by_id(&agent, &id, vec!["new1".to_string(), "new2".to_string()])
6661 .unwrap();
6662 assert!(updated);
6663 let item = store.recall_by_id(&agent, &id).unwrap().unwrap();
6664 assert_eq!(item.tags, vec!["new1", "new2"]);
6665 }
6666
6667 #[test]
6668 fn test_update_tags_by_id_returns_false_for_missing() {
6669 let store = EpisodicStore::new();
6670 let agent = AgentId::new("a");
6671 let fake = MemoryId::new("nope");
6672 assert!(!store.update_tags_by_id(&agent, &fake, vec![]).unwrap());
6673 }
6674
6675 #[test]
6676 fn test_max_importance_for_returns_highest() {
6677 let store = EpisodicStore::new();
6678 let agent = AgentId::new("a");
6679 store.add_episode(agent.clone(), "low", 0.2).unwrap();
6680 store.add_episode(agent.clone(), "high", 0.9).unwrap();
6681 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
6682 let max = store.max_importance_for(&agent).unwrap().unwrap();
6683 assert!((max - 0.9).abs() < 1e-6);
6684 }
6685
6686 #[test]
6687 fn test_max_importance_for_returns_none_when_empty() {
6688 let store = EpisodicStore::new();
6689 let agent = AgentId::new("empty");
6690 assert!(store.max_importance_for(&agent).unwrap().is_none());
6691 }
6692
6693 #[test]
6696 fn test_working_memory_snapshot_returns_all_entries() {
6697 let wm = WorkingMemory::new(10).unwrap();
6698 wm.set("k1", "v1").unwrap();
6699 wm.set("k2", "v2").unwrap();
6700 let snap = wm.snapshot().unwrap();
6701 assert_eq!(snap.len(), 2);
6702 assert_eq!(snap.get("k1").unwrap(), "v1");
6703 assert_eq!(snap.get("k2").unwrap(), "v2");
6704 }
6705
6706 #[test]
6707 fn test_working_memory_pop_oldest_removes_first_inserted() {
6708 let wm = WorkingMemory::new(10).unwrap();
6709 wm.set("first", "1").unwrap();
6710 wm.set("second", "2").unwrap();
6711 let popped = wm.pop_oldest().unwrap().unwrap();
6712 assert_eq!(popped.0, "first");
6713 assert_eq!(popped.1, "1");
6714 assert_eq!(wm.len().unwrap(), 1);
6715 }
6716
6717 #[test]
6718 fn test_working_memory_pop_oldest_returns_none_when_empty() {
6719 let wm = WorkingMemory::new(5).unwrap();
6720 assert!(wm.pop_oldest().unwrap().is_none());
6721 }
6722
6723 #[test]
6726 fn test_semantic_store_keys_with_tag_returns_matching() {
6727 let store = SemanticStore::new();
6728 store
6729 .store("doc1", "content one", vec!["alpha".to_string(), "beta".to_string()])
6730 .unwrap();
6731 store
6732 .store("doc2", "content two", vec!["alpha".to_string()])
6733 .unwrap();
6734 store
6735 .store("doc3", "content three", vec!["gamma".to_string()])
6736 .unwrap();
6737 let mut keys = store.keys_with_tag("alpha").unwrap();
6738 keys.sort();
6739 assert_eq!(keys, vec!["doc1", "doc2"]);
6740 }
6741
6742 #[test]
6743 fn test_semantic_store_keys_with_tag_empty_when_no_match() {
6744 let store = SemanticStore::new();
6745 store
6746 .store("doc1", "content", vec!["x".to_string()])
6747 .unwrap();
6748 let keys = store.keys_with_tag("z").unwrap();
6749 assert!(keys.is_empty());
6750 }
6751
6752 #[test]
6755 fn test_episodic_oldest_returns_first_inserted() {
6756 let store = EpisodicStore::new();
6757 let agent = AgentId::new("agent-oldest");
6758 store.add_episode(agent.clone(), "first episode", 0.5).unwrap();
6759 store.add_episode(agent.clone(), "second episode", 0.9).unwrap();
6760 let oldest = store.oldest(&agent).unwrap().unwrap();
6761 assert_eq!(oldest.content, "first episode");
6762 }
6763
6764 #[test]
6765 fn test_episodic_oldest_none_when_no_episodes() {
6766 let store = EpisodicStore::new();
6767 let agent = AgentId::new("no-episodes");
6768 assert!(store.oldest(&agent).unwrap().is_none());
6769 }
6770
6771 #[test]
6772 fn test_working_memory_get_or_default_returns_value_when_present() {
6773 let wm = WorkingMemory::new(10).unwrap();
6774 wm.set("key", "value").unwrap();
6775 assert_eq!(wm.get_or_default("key", "fallback").unwrap(), "value");
6776 }
6777
6778 #[test]
6779 fn test_working_memory_get_or_default_returns_default_when_absent() {
6780 let wm = WorkingMemory::new(10).unwrap();
6781 assert_eq!(wm.get_or_default("missing", "fallback").unwrap(), "fallback");
6782 }
6783
6784 #[test]
6787 fn test_episodic_count_for_returns_correct_count() {
6788 let store = EpisodicStore::new();
6789 let agent = AgentId::new("a");
6790 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
6791 store.add_episode(agent.clone(), "e2", 0.5).unwrap();
6792 assert_eq!(store.count_for(&agent).unwrap(), 2);
6793 }
6794
6795 #[test]
6796 fn test_episodic_count_for_returns_zero_for_unknown_agent() {
6797 let store = EpisodicStore::new();
6798 let agent = AgentId::new("unknown");
6799 assert_eq!(store.count_for(&agent).unwrap(), 0);
6800 }
6801
6802 #[test]
6803 fn test_recall_by_tag_returns_matching_sorted_by_importance() {
6804 let store = EpisodicStore::new();
6805 let agent = AgentId::new("a");
6806 store
6807 .add_episode_with_tags(agent.clone(), "low", 0.1, vec!["news".to_string()])
6808 .unwrap();
6809 store
6810 .add_episode_with_tags(agent.clone(), "high", 0.9, vec!["news".to_string()])
6811 .unwrap();
6812 store
6813 .add_episode_with_tags(agent.clone(), "other", 0.8, vec!["other".to_string()])
6814 .unwrap();
6815 let items = store.recall_by_tag(&agent, "news", 0).unwrap();
6816 assert_eq!(items.len(), 2);
6817 assert_eq!(items[0].content, "high");
6818 }
6819
6820 #[test]
6821 fn test_recall_by_tag_respects_limit() {
6822 let store = EpisodicStore::new();
6823 let agent = AgentId::new("a");
6824 for i in 0..5 {
6825 store
6826 .add_episode_with_tags(agent.clone(), format!("ep{i}"), 0.5, vec!["t".to_string()])
6827 .unwrap();
6828 }
6829 let items = store.recall_by_tag(&agent, "t", 2).unwrap();
6830 assert_eq!(items.len(), 2);
6831 }
6832
6833 #[test]
6834 fn test_merge_from_copies_episodes() {
6835 let src = EpisodicStore::new();
6836 let dst = EpisodicStore::new();
6837 let agent = AgentId::new("a");
6838 src.add_episode(agent.clone(), "ep1", 0.5).unwrap();
6839 src.add_episode(agent.clone(), "ep2", 0.9).unwrap();
6840 let count = dst.merge_from(&src, &agent).unwrap();
6841 assert_eq!(count, 2);
6842 assert_eq!(dst.count_for(&agent).unwrap(), 2);
6843 }
6844
6845 #[test]
6848 fn test_working_memory_get_all_keys_preserves_insertion_order() {
6849 let wm = WorkingMemory::new(10).unwrap();
6850 wm.set("first", "1").unwrap();
6851 wm.set("second", "2").unwrap();
6852 wm.set("third", "3").unwrap();
6853 assert_eq!(wm.get_all_keys().unwrap(), vec!["first", "second", "third"]);
6854 }
6855
6856 #[test]
6857 fn test_working_memory_replace_all_replaces_contents() {
6858 let wm = WorkingMemory::new(10).unwrap();
6859 wm.set("old", "x").unwrap();
6860 let mut new_map = std::collections::HashMap::new();
6861 new_map.insert("a".to_string(), "1".to_string());
6862 new_map.insert("b".to_string(), "2".to_string());
6863 wm.replace_all(new_map).unwrap();
6864 assert_eq!(wm.len().unwrap(), 2);
6865 assert!(wm.get("old").unwrap().is_none());
6866 assert_eq!(wm.get("a").unwrap().unwrap(), "1");
6867 }
6868
6869 #[test]
6872 fn test_semantic_store_remove_returns_true_and_removes() {
6873 let store = SemanticStore::new();
6874 store.store("k1", "v1", vec![]).unwrap();
6875 store.store("k2", "v2", vec![]).unwrap();
6876 assert!(store.remove("k1").unwrap());
6877 assert_eq!(store.count().unwrap(), 1);
6878 let keys = store.list_keys().unwrap();
6879 assert_eq!(keys, vec!["k2"]);
6880 }
6881
6882 #[test]
6883 fn test_semantic_store_remove_returns_false_when_missing() {
6884 let store = SemanticStore::new();
6885 assert!(!store.remove("ghost").unwrap());
6886 }
6887
6888 #[test]
6889 fn test_semantic_store_count_matches_len() {
6890 let store = SemanticStore::new();
6891 store.store("a", "1", vec![]).unwrap();
6892 store.store("b", "2", vec![]).unwrap();
6893 assert_eq!(store.count().unwrap(), store.len().unwrap());
6894 }
6895
6896 #[test]
6899 fn test_episodic_newest_returns_last_inserted() {
6900 let store = EpisodicStore::new();
6901 let agent = AgentId::new("agent-newest");
6902 store.add_episode(agent.clone(), "first", 0.3).unwrap();
6903 store.add_episode(agent.clone(), "last", 0.8).unwrap();
6904 let newest = store.newest(&agent).unwrap().unwrap();
6905 assert_eq!(newest.content, "last");
6906 }
6907
6908 #[test]
6909 fn test_episodic_newest_none_when_empty() {
6910 let store = EpisodicStore::new();
6911 let agent = AgentId::new("no-ep");
6912 assert!(store.newest(&agent).unwrap().is_none());
6913 }
6914
6915 #[test]
6916 fn test_working_memory_values_returns_values_in_insertion_order() {
6917 let wm = WorkingMemory::new(10).unwrap();
6918 wm.set("a", "apple").unwrap();
6919 wm.set("b", "banana").unwrap();
6920 let vals = wm.values().unwrap();
6921 assert_eq!(vals, vec!["apple", "banana"]);
6922 }
6923
6924 #[test]
6925 fn test_working_memory_values_empty_when_no_entries() {
6926 let wm = WorkingMemory::new(10).unwrap();
6927 assert!(wm.values().unwrap().is_empty());
6928 }
6929
6930 #[test]
6933 fn test_episodic_clear_agent_removes_all_episodes() {
6934 let store = EpisodicStore::new();
6935 let agent = AgentId::new("agent-clear");
6936 store.add_episode(agent.clone(), "ep1", 0.5).unwrap();
6937 store.add_episode(agent.clone(), "ep2", 0.7).unwrap();
6938 let removed = store.clear_agent(&agent).unwrap();
6939 assert_eq!(removed, 2);
6940 assert_eq!(store.agent_memory_count(&agent).unwrap(), 0);
6941 }
6942
6943 #[test]
6944 fn test_episodic_clear_agent_returns_zero_when_none() {
6945 let store = EpisodicStore::new();
6946 let agent = AgentId::new("no-agent");
6947 assert_eq!(store.clear_agent(&agent).unwrap(), 0);
6948 }
6949
6950 #[test]
6953 fn test_episodic_max_importance_episode_returns_highest() {
6954 let store = EpisodicStore::new();
6955 let agent = AgentId::new("agent-max");
6956 store.add_episode(agent.clone(), "low", 0.2).unwrap();
6957 store.add_episode(agent.clone(), "high", 0.9).unwrap();
6958 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
6959 let top = store.max_importance_episode(&agent).unwrap().unwrap();
6960 assert_eq!(top.content, "high");
6961 }
6962
6963 #[test]
6964 fn test_episodic_max_importance_episode_none_when_empty() {
6965 let store = EpisodicStore::new();
6966 let agent = AgentId::new("empty-max");
6967 assert!(store.max_importance_episode(&agent).unwrap().is_none());
6968 }
6969
6970 #[test]
6971 fn test_semantic_store_update_replaces_value() {
6972 let store = SemanticStore::new();
6973 store.store("key1", "original", vec![]).unwrap();
6974 let updated = store.update("key1", "new value").unwrap();
6975 assert!(updated);
6976 let retrieved = store.retrieve_by_key("key1").unwrap().unwrap();
6977 assert_eq!(retrieved.0, "new value");
6978 }
6979
6980 #[test]
6981 fn test_semantic_store_update_returns_false_for_missing_key() {
6982 let store = SemanticStore::new();
6983 assert!(!store.update("missing", "value").unwrap());
6984 }
6985
6986 #[test]
6989 fn test_episodic_clear_for_removes_all_episodes_for_agent() {
6990 let store = EpisodicStore::new();
6991 let agent = AgentId::new("a");
6992 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
6993 store.add_episode(agent.clone(), "e2", 0.9).unwrap();
6994 let removed = store.clear_for(&agent).unwrap();
6995 assert_eq!(removed, 2);
6996 assert_eq!(store.count_for(&agent).unwrap(), 0);
6997 }
6998
6999 #[test]
7000 fn test_episodic_clear_for_returns_zero_for_unknown_agent() {
7001 let store = EpisodicStore::new();
7002 let agent = AgentId::new("ghost");
7003 assert_eq!(store.clear_for(&agent).unwrap(), 0);
7004 }
7005
7006 #[test]
7007 fn test_episodic_importance_sum_correct() {
7008 let store = EpisodicStore::new();
7009 let agent = AgentId::new("a");
7010 store.add_episode(agent.clone(), "e1", 0.3).unwrap();
7011 store.add_episode(agent.clone(), "e2", 0.5).unwrap();
7012 let sum = store.importance_sum(&agent).unwrap();
7013 assert!((sum - 0.8).abs() < 1e-5);
7014 }
7015
7016 #[test]
7017 fn test_episodic_importance_sum_zero_when_empty() {
7018 let store = EpisodicStore::new();
7019 let agent = AgentId::new("empty");
7020 assert_eq!(store.importance_sum(&agent).unwrap(), 0.0);
7021 }
7022
7023 #[test]
7026 fn test_working_memory_merge_from_copies_entries() {
7027 let src = WorkingMemory::new(10).unwrap();
7028 src.set("x", "1").unwrap();
7029 src.set("y", "2").unwrap();
7030 let dst = WorkingMemory::new(10).unwrap();
7031 let count = dst.merge_from(&src).unwrap();
7032 assert_eq!(count, 2);
7033 assert_eq!(dst.get("x").unwrap().unwrap(), "1");
7034 assert_eq!(dst.get("y").unwrap().unwrap(), "2");
7035 }
7036
7037 #[test]
7038 fn test_working_memory_entry_count_satisfying_counts_matching() {
7039 let wm = WorkingMemory::new(10).unwrap();
7040 wm.set("a", "hello").unwrap();
7041 wm.set("b", "world").unwrap();
7042 wm.set("c", "hello world").unwrap();
7043 let count = wm
7044 .entry_count_satisfying(|_, v| v.contains("hello"))
7045 .unwrap();
7046 assert_eq!(count, 2);
7047 }
7048
7049 #[test]
7052 fn test_semantic_update_value_replaces_stored_value() {
7053 let store = SemanticStore::new();
7054 store.store("k", "old", vec![]).unwrap();
7055 assert!(store.update_value("k", "new").unwrap());
7056 let pairs = store.retrieve(&[]).unwrap();
7057 assert!(pairs.iter().any(|(_, v)| v == "new"));
7058 }
7059
7060 #[test]
7061 fn test_semantic_update_value_returns_false_when_missing() {
7062 let store = SemanticStore::new();
7063 assert!(!store.update_value("nope", "x").unwrap());
7064 }
7065
7066 #[test]
7069 fn test_episodic_agent_ids_returns_all_agents() {
7070 let store = EpisodicStore::new();
7071 let a1 = AgentId::new("agent-1");
7072 let a2 = AgentId::new("agent-2");
7073 store.add_episode(a1.clone(), "e", 0.5).unwrap();
7074 store.add_episode(a2.clone(), "e", 0.5).unwrap();
7075 let mut ids = store.agent_ids().unwrap();
7076 ids.sort_by_key(|id| id.0.clone());
7077 assert_eq!(ids, vec![a1, a2]);
7078 }
7079
7080 #[test]
7081 fn test_episodic_agent_ids_empty_for_new_store() {
7082 let store = EpisodicStore::new();
7083 assert!(store.agent_ids().unwrap().is_empty());
7084 }
7085
7086 #[test]
7087 fn test_find_by_content_returns_matching_episodes() {
7088 let store = EpisodicStore::new();
7089 let agent = AgentId::new("a");
7090 store.add_episode(agent.clone(), "hello world", 0.5).unwrap();
7091 store.add_episode(agent.clone(), "goodbye world", 0.8).unwrap();
7092 store.add_episode(agent.clone(), "something else", 0.9).unwrap();
7093 let results = store.find_by_content(&agent, "world").unwrap();
7094 assert_eq!(results.len(), 2);
7095 assert_eq!(results[0].content, "goodbye world");
7097 }
7098
7099 #[test]
7100 fn test_find_by_content_returns_empty_when_no_match() {
7101 let store = EpisodicStore::new();
7102 let agent = AgentId::new("a");
7103 store.add_episode(agent.clone(), "hello", 0.5).unwrap();
7104 let results = store.find_by_content(&agent, "xyz").unwrap();
7105 assert!(results.is_empty());
7106 }
7107
7108 #[test]
7111 fn test_add_episode_at_stores_with_given_timestamp() {
7112 let store = EpisodicStore::new();
7113 let agent = AgentId::new("agent-ts");
7114 let ts = chrono::Utc::now() - chrono::Duration::hours(2);
7115 let id = store.add_episode_at(agent.clone(), "past event", 0.7, ts).unwrap();
7116 let items = store.recall_all(&agent).unwrap();
7117 assert_eq!(items.len(), 1);
7118 assert_eq!(items[0].id, id);
7119 assert_eq!(items[0].content, "past event");
7120 assert!((items[0].timestamp - ts).num_seconds().abs() < 1);
7121 }
7122
7123 #[test]
7124 fn test_add_episodes_batch_returns_all_ids() {
7125 let store = EpisodicStore::new();
7126 let agent = AgentId::new("batch-agent");
7127 let episodes = vec![
7128 ("first", 0.5f32),
7129 ("second", 0.8f32),
7130 ("third", 0.3f32),
7131 ];
7132 let ids = store.add_episodes_batch(agent.clone(), episodes).unwrap();
7133 assert_eq!(ids.len(), 3);
7134 let all = store.recall_all(&agent).unwrap();
7135 assert_eq!(all.len(), 3);
7136 }
7137
7138 #[test]
7139 fn test_add_episodes_batch_empty_iter_returns_empty_ids() {
7140 let store = EpisodicStore::new();
7141 let agent = AgentId::new("empty-batch");
7142 let ids = store
7143 .add_episodes_batch(agent.clone(), Vec::<(String, f32)>::new())
7144 .unwrap();
7145 assert!(ids.is_empty());
7146 assert_eq!(store.count_for(&agent).unwrap(), 0);
7147 }
7148
7149 #[test]
7150 fn test_semantic_keys_matching_returns_substring_matches() {
7151 let store = SemanticStore::new();
7152 store.store("rust_intro", "value1", vec![]).unwrap();
7153 store.store("rust_advanced", "value2", vec![]).unwrap();
7154 store.store("python_basics", "value3", vec![]).unwrap();
7155 let matches = store.keys_matching("rust").unwrap();
7156 assert_eq!(matches.len(), 2);
7157 assert!(matches.contains(&"rust_intro".to_string()));
7158 assert!(matches.contains(&"rust_advanced".to_string()));
7159 }
7160
7161 #[test]
7162 fn test_semantic_keys_matching_is_case_insensitive() {
7163 let store = SemanticStore::new();
7164 store.store("UPPER_KEY", "v", vec![]).unwrap();
7165 let matches = store.keys_matching("upper").unwrap();
7166 assert_eq!(matches.len(), 1);
7167 }
7168
7169 #[test]
7170 fn test_semantic_keys_matching_empty_when_no_match() {
7171 let store = SemanticStore::new();
7172 store.store("abc", "val", vec![]).unwrap();
7173 let matches = store.keys_matching("xyz").unwrap();
7174 assert!(matches.is_empty());
7175 }
7176
7177 #[test]
7180 fn test_importance_avg_correct() {
7181 let store = EpisodicStore::new();
7182 let agent = AgentId::new("a");
7183 store.add_episode(agent.clone(), "e1", 0.2).unwrap();
7184 store.add_episode(agent.clone(), "e2", 0.8).unwrap();
7185 let avg = store.importance_avg(&agent).unwrap();
7186 assert!((avg - 0.5).abs() < 1e-5);
7187 }
7188
7189 #[test]
7190 fn test_importance_avg_zero_for_empty() {
7191 let store = EpisodicStore::new();
7192 let agent = AgentId::new("empty");
7193 assert_eq!(store.importance_avg(&agent).unwrap(), 0.0);
7194 }
7195
7196 #[test]
7197 fn test_deduplicate_content_removes_lower_importance_duplicate() {
7198 let store = EpisodicStore::new();
7199 let agent = AgentId::new("a");
7200 store.add_episode(agent.clone(), "same", 0.3).unwrap();
7201 store.add_episode(agent.clone(), "same", 0.9).unwrap();
7202 store.add_episode(agent.clone(), "different", 0.5).unwrap();
7203 let removed = store.deduplicate_content(&agent).unwrap();
7204 assert_eq!(removed, 1);
7205 assert_eq!(store.count_for(&agent).unwrap(), 2);
7206 }
7207
7208 #[test]
7209 fn test_deduplicate_content_keeps_highest_importance() {
7210 let store = EpisodicStore::new();
7211 let agent = AgentId::new("a");
7212 store.add_episode(agent.clone(), "dup", 0.1).unwrap();
7213 store.add_episode(agent.clone(), "dup", 0.7).unwrap();
7214 store.deduplicate_content(&agent).unwrap();
7215 let items = store.recall(&agent, 10).unwrap();
7216 assert_eq!(items.len(), 1);
7217 assert!((items[0].importance - 0.7).abs() < 1e-5);
7218 }
7219
7220 #[test]
7223 fn test_working_memory_iter_sorted_alphabetical_order() {
7224 let wm = WorkingMemory::new(10).unwrap();
7225 wm.set("c", "3").unwrap();
7226 wm.set("a", "1").unwrap();
7227 wm.set("b", "2").unwrap();
7228 let sorted = wm.iter_sorted().unwrap();
7229 let keys: Vec<&str> = sorted.iter().map(|(k, _)| k.as_str()).collect();
7230 assert_eq!(keys, vec!["a", "b", "c"]);
7231 }
7232
7233 #[test]
7234 fn test_semantic_get_value_returns_value_for_existing_key() {
7235 let store = SemanticStore::new();
7236 store.store("mykey", "myvalue", vec![]).unwrap();
7237 assert_eq!(store.get_value("mykey").unwrap(), Some("myvalue".to_string()));
7238 }
7239
7240 #[test]
7241 fn test_semantic_get_value_returns_none_for_missing_key() {
7242 let store = SemanticStore::new();
7243 assert!(store.get_value("ghost").unwrap().is_none());
7244 }
7245
7246 #[test]
7249 fn test_recall_top_n_returns_highest_importance_items() {
7250 let store = EpisodicStore::new();
7251 let agent = AgentId::new("a");
7252 store.add_episode(agent.clone(), "low", 0.1).unwrap();
7253 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
7254 store.add_episode(agent.clone(), "high", 0.9).unwrap();
7255 let top = store.recall_top_n(&agent, 2).unwrap();
7256 assert_eq!(top.len(), 2);
7257 assert!((top[0].importance - 0.9).abs() < 1e-5);
7258 }
7259
7260 #[test]
7261 fn test_recall_top_n_clamps_to_available_items() {
7262 let store = EpisodicStore::new();
7263 let agent = AgentId::new("a");
7264 store.add_episode(agent.clone(), "only", 0.5).unwrap();
7265 let top = store.recall_top_n(&agent, 100).unwrap();
7266 assert_eq!(top.len(), 1);
7267 }
7268
7269 #[test]
7270 fn test_filter_by_importance_returns_items_in_range() {
7271 let store = EpisodicStore::new();
7272 let agent = AgentId::new("b");
7273 store.add_episode(agent.clone(), "low", 0.1).unwrap();
7274 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
7275 store.add_episode(agent.clone(), "high", 0.9).unwrap();
7276 let mid_range = store.filter_by_importance(&agent, 0.3, 0.7).unwrap();
7277 assert_eq!(mid_range.len(), 1);
7278 assert!((mid_range[0].importance - 0.5).abs() < 1e-5);
7279 }
7280
7281 #[test]
7282 fn test_update_many_sets_multiple_keys() {
7283 let wm = WorkingMemory::new(10).unwrap();
7284 wm.set("x", "old_x").unwrap();
7285 wm.set("y", "old_y").unwrap();
7286 let updated = wm
7287 .update_many(vec![("x".to_string(), "new_x".to_string()), ("y".to_string(), "new_y".to_string())])
7288 .unwrap();
7289 assert_eq!(updated, 2);
7290 assert_eq!(wm.get("x").unwrap(), Some("new_x".to_string()));
7291 assert_eq!(wm.get("y").unwrap(), Some("new_y".to_string()));
7292 }
7293
7294 #[test]
7295 fn test_update_many_returns_zero_for_empty_iter() {
7296 let wm = WorkingMemory::new(5).unwrap();
7297 let updated = wm.update_many(Vec::<(String, String)>::new()).unwrap();
7298 assert_eq!(updated, 0);
7299 }
7300
7301 #[test]
7302 fn test_entry_count_with_embedding_counts_only_embedded_entries() {
7303 let store = SemanticStore::new();
7304 store.store_with_embedding("has_emb", "v1", vec![], vec![0.1_f32, 0.2_f32]).unwrap();
7305 store.store("no_emb", "v2", vec![]).unwrap();
7306 assert_eq!(store.entry_count_with_embedding().unwrap(), 1);
7307 }
7308
7309 #[test]
7312 fn test_retain_top_n_removes_low_importance_episodes() {
7313 let store = EpisodicStore::new();
7314 let agent = AgentId::new("a");
7315 store.add_episode(agent.clone(), "low", 0.1).unwrap();
7316 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
7317 store.add_episode(agent.clone(), "high", 0.9).unwrap();
7318 let removed = store.retain_top_n(&agent, 2).unwrap();
7319 assert_eq!(removed, 1);
7320 let remaining = store.recall(&agent, 10).unwrap();
7321 assert_eq!(remaining.len(), 2);
7322 assert!(remaining.iter().all(|i| i.importance >= 0.5));
7323 }
7324
7325 #[test]
7326 fn test_retain_top_n_noop_when_fewer_than_n() {
7327 let store = EpisodicStore::new();
7328 let agent = AgentId::new("b");
7329 store.add_episode(agent.clone(), "only", 0.7).unwrap();
7330 let removed = store.retain_top_n(&agent, 5).unwrap();
7331 assert_eq!(removed, 0);
7332 assert_eq!(store.recall(&agent, 10).unwrap().len(), 1);
7333 }
7334
7335 #[test]
7336 fn test_keys_starting_with_returns_matching_keys() {
7337 let wm = WorkingMemory::new(10).unwrap();
7338 wm.set("user:name", "alice").unwrap();
7339 wm.set("user:email", "alice@example.com").unwrap();
7340 wm.set("session:id", "abc").unwrap();
7341 let mut keys = wm.keys_starting_with("user:").unwrap();
7342 keys.sort();
7343 assert_eq!(keys, vec!["user:email", "user:name"]);
7344 }
7345
7346 #[test]
7347 fn test_keys_starting_with_returns_empty_when_no_match() {
7348 let wm = WorkingMemory::new(5).unwrap();
7349 wm.set("foo", "bar").unwrap();
7350 assert!(wm.keys_starting_with("xyz:").unwrap().is_empty());
7351 }
7352
7353 #[test]
7356 fn test_episodic_most_recent_returns_last_inserted() {
7357 let store = EpisodicStore::new();
7358 let agent = AgentId::new("a");
7359 store.add_episode(agent.clone(), "first", 0.5).unwrap();
7360 store.add_episode(agent.clone(), "second", 0.3).unwrap();
7361 let recent = store.most_recent(&agent).unwrap().unwrap();
7362 assert_eq!(recent.content, "second");
7363 }
7364
7365 #[test]
7366 fn test_episodic_most_recent_none_when_empty() {
7367 let store = EpisodicStore::new();
7368 let agent = AgentId::new("nobody");
7369 assert!(store.most_recent(&agent).unwrap().is_none());
7370 }
7371
7372 #[test]
7373 fn test_working_memory_contains_all_true_when_all_present() {
7374 let wm = WorkingMemory::new(10).unwrap();
7375 wm.set("x", "1").unwrap();
7376 wm.set("y", "2").unwrap();
7377 assert!(wm.contains_all(["x", "y"]).unwrap());
7378 }
7379
7380 #[test]
7381 fn test_working_memory_contains_all_false_when_one_missing() {
7382 let wm = WorkingMemory::new(10).unwrap();
7383 wm.set("x", "1").unwrap();
7384 assert!(!wm.contains_all(["x", "missing"]).unwrap());
7385 }
7386
7387 #[test]
7388 fn test_working_memory_contains_all_vacuously_true_for_empty() {
7389 let wm = WorkingMemory::new(5).unwrap();
7390 assert!(wm.contains_all(std::iter::empty::<&str>()).unwrap());
7391 }
7392
7393 #[test]
7394 fn test_working_memory_has_any_key_true_when_at_least_one_present() {
7395 let wm = WorkingMemory::new(5).unwrap();
7396 wm.set("present", "v").unwrap();
7397 assert!(wm.has_any_key(["missing", "present"]).unwrap());
7398 }
7399
7400 #[test]
7401 fn test_working_memory_has_any_key_false_when_none_present() {
7402 let wm = WorkingMemory::new(5).unwrap();
7403 assert!(!wm.has_any_key(["a", "b"]).unwrap());
7404 }
7405
7406 #[test]
7407 fn test_working_memory_has_any_key_false_for_empty_iter() {
7408 let wm = WorkingMemory::new(5).unwrap();
7409 assert!(!wm.has_any_key(std::iter::empty::<&str>()).unwrap());
7410 }
7411
7412 #[test]
7413 fn test_semantic_to_map_returns_key_value_pairs() {
7414 let store = SemanticStore::new();
7415 store.store("key1", "val1", vec![]).unwrap();
7416 store.store("key2", "val2", vec![]).unwrap();
7417 let map = store.to_map().unwrap();
7418 assert_eq!(map.get("key1").map(String::as_str), Some("val1"));
7419 assert_eq!(map.get("key2").map(String::as_str), Some("val2"));
7420 assert_eq!(map.len(), 2);
7421 }
7422
7423 #[test]
7424 fn test_semantic_to_map_empty_when_no_entries() {
7425 let store = SemanticStore::new();
7426 assert!(store.to_map().unwrap().is_empty());
7427 }
7428
7429 #[test]
7430 fn test_working_memory_fill_ratio_zero_when_empty() {
7431 let wm = WorkingMemory::new(10).unwrap();
7432 assert_eq!(wm.fill_ratio().unwrap(), 0.0);
7433 }
7434
7435 #[test]
7436 fn test_working_memory_fill_ratio_correct_proportion() {
7437 let wm = WorkingMemory::new(4).unwrap();
7438 wm.set("a", "1").unwrap();
7439 wm.set("b", "2").unwrap();
7440 assert!((wm.fill_ratio().unwrap() - 0.5).abs() < 1e-9);
7442 }
7443
7444 #[test]
7447 fn test_most_recent_returns_last_inserted_episode() {
7448 let store = EpisodicStore::new();
7449 let agent = AgentId::new("a");
7450 store.add_episode(agent.clone(), "first", 0.5).unwrap();
7451 store.add_episode(agent.clone(), "second", 0.8).unwrap();
7452 let recent = store.most_recent(&agent).unwrap().unwrap();
7453 assert_eq!(recent.content, "second");
7454 }
7455
7456 #[test]
7457 fn test_most_recent_returns_none_for_new_agent() {
7458 let store = EpisodicStore::new();
7459 let agent = AgentId::new("empty");
7460 assert!(store.most_recent(&agent).unwrap().is_none());
7461 }
7462
7463 #[test]
7464 fn test_contains_all_true_when_all_keys_present() {
7465 let wm = WorkingMemory::new(10).unwrap();
7466 wm.set("a", "1").unwrap();
7467 wm.set("b", "2").unwrap();
7468 assert!(wm.contains_all(["a", "b"]).unwrap());
7469 }
7470
7471 #[test]
7472 fn test_contains_all_false_when_one_key_missing() {
7473 let wm = WorkingMemory::new(10).unwrap();
7474 wm.set("a", "1").unwrap();
7475 assert!(!wm.contains_all(["a", "missing"]).unwrap());
7476 }
7477
7478 #[test]
7479 fn test_contains_all_true_for_empty_iter() {
7480 let wm = WorkingMemory::new(5).unwrap();
7481 assert!(wm.contains_all([]).unwrap());
7482 }
7483
7484 #[test]
7485 fn test_has_any_key_true_when_one_key_present() {
7486 let wm = WorkingMemory::new(10).unwrap();
7487 wm.set("x", "v").unwrap();
7488 assert!(wm.has_any_key(["x", "y"]).unwrap());
7489 }
7490
7491 #[test]
7492 fn test_has_any_key_false_when_none_present() {
7493 let wm = WorkingMemory::new(5).unwrap();
7494 assert!(!wm.has_any_key(["nope", "also_nope"]).unwrap());
7495 }
7496
7497 #[test]
7498 fn test_has_any_key_false_for_empty_iter() {
7499 let wm = WorkingMemory::new(5).unwrap();
7500 wm.set("a", "1").unwrap();
7501 assert!(!wm.has_any_key([]).unwrap());
7502 }
7503
7504 #[test]
7507 fn test_most_recalled_returns_none_for_new_agent() {
7508 let store = EpisodicStore::new();
7509 let agent = AgentId::new("fresh");
7510 assert!(store.most_recalled(&agent).unwrap().is_none());
7511 }
7512
7513 #[test]
7514 fn test_most_recalled_returns_highest_recall_count() {
7515 use std::sync::Arc;
7516 let store = EpisodicStore::new();
7517 let agent = AgentId::new("a");
7518 store.add_episode(agent.clone(), "low", 0.3).unwrap();
7519 store.add_episode(agent.clone(), "high", 0.9).unwrap();
7520 store.add_episode(agent.clone(), "mid", 0.6).unwrap();
7521 let items = store.recall(&agent, 3).unwrap();
7523 store.recall(&agent, 1).unwrap();
7528 let top = store.most_recalled(&agent).unwrap();
7529 assert!(top.is_some());
7530 assert!(top.unwrap().recall_count >= 1);
7532 }
7533
7534 #[test]
7535 fn test_most_recalled_single_episode() {
7536 let store = EpisodicStore::new();
7537 let agent = AgentId::new("solo");
7538 store.add_episode(agent.clone(), "only one", 0.7).unwrap();
7539 store.recall(&agent, 1).unwrap();
7540 let top = store.most_recalled(&agent).unwrap();
7541 assert_eq!(top.unwrap().content, "only one");
7542 }
7543
7544 #[test]
7547 fn test_max_importance_returns_highest_score() {
7548 let store = EpisodicStore::new();
7549 let agent = AgentId::new("a");
7550 store.add_episode(agent.clone(), "low", 0.1).unwrap();
7551 store.add_episode(agent.clone(), "high", 0.9).unwrap();
7552 let max = store.max_importance(&agent).unwrap().unwrap();
7553 assert!((max - 0.9).abs() < 1e-5);
7554 }
7555
7556 #[test]
7557 fn test_min_importance_returns_lowest_score() {
7558 let store = EpisodicStore::new();
7559 let agent = AgentId::new("b");
7560 store.add_episode(agent.clone(), "low", 0.1).unwrap();
7561 store.add_episode(agent.clone(), "high", 0.9).unwrap();
7562 let min = store.min_importance(&agent).unwrap().unwrap();
7563 assert!((min - 0.1).abs() < 1e-5);
7564 }
7565
7566 #[test]
7567 fn test_max_importance_none_for_empty_agent() {
7568 let store = EpisodicStore::new();
7569 let agent = AgentId::new("empty");
7570 assert!(store.max_importance(&agent).unwrap().is_none());
7571 }
7572
7573 #[test]
7574 fn test_values_matching_returns_pairs_with_pattern() {
7575 let wm = WorkingMemory::new(10).unwrap();
7576 wm.set("name", "alice wonder").unwrap();
7577 wm.set("city", "wonderland").unwrap();
7578 wm.set("role", "engineer").unwrap();
7579 let mut matches = wm.values_matching("wonder").unwrap();
7580 matches.sort_by_key(|(k, _)| k.clone());
7581 assert_eq!(
7582 matches,
7583 vec![
7584 ("city".to_string(), "wonderland".to_string()),
7585 ("name".to_string(), "alice wonder".to_string()),
7586 ]
7587 );
7588 }
7589
7590 #[test]
7591 fn test_values_matching_returns_empty_when_no_match() {
7592 let wm = WorkingMemory::new(5).unwrap();
7593 wm.set("a", "foo").unwrap();
7594 assert!(wm.values_matching("xyz").unwrap().is_empty());
7595 }
7596
7597 #[test]
7600 fn test_count_above_importance_correct_count() {
7601 let store = EpisodicStore::new();
7602 let agent = AgentId::new("agent");
7603 store.add_episode(agent.clone(), "low", 0.2).unwrap();
7604 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
7605 store.add_episode(agent.clone(), "high", 0.9).unwrap();
7606 assert_eq!(store.count_above_importance(&agent, 0.4).unwrap(), 2);
7607 }
7608
7609 #[test]
7610 fn test_count_above_importance_zero_for_empty_agent() {
7611 let store = EpisodicStore::new();
7612 let agent = AgentId::new("nobody");
7613 assert_eq!(store.count_above_importance(&agent, 0.0).unwrap(), 0);
7614 }
7615
7616 #[test]
7617 fn test_count_above_importance_threshold_is_exclusive() {
7618 let store = EpisodicStore::new();
7619 let agent = AgentId::new("ex");
7620 store.add_episode(agent.clone(), "exact", 0.5).unwrap();
7621 assert_eq!(store.count_above_importance(&agent, 0.5).unwrap(), 0);
7623 }
7624
7625 #[test]
7626 fn test_working_memory_value_length_some_for_existing_key() {
7627 let wm = WorkingMemory::new(10).unwrap();
7628 wm.set("greeting", "hello").unwrap();
7629 assert_eq!(wm.value_length("greeting").unwrap(), Some(5));
7630 }
7631
7632 #[test]
7633 fn test_working_memory_value_length_none_for_absent_key() {
7634 let wm = WorkingMemory::new(10).unwrap();
7635 assert!(wm.value_length("missing").unwrap().is_none());
7636 }
7637
7638 #[test]
7639 fn test_semantic_store_tags_for_returns_tags() {
7640 let store = SemanticStore::new();
7641 store
7642 .store("key1", "value1", vec!["alpha".to_string(), "beta".to_string()])
7643 .unwrap();
7644 let tags = store.tags_for("key1").unwrap().unwrap();
7645 assert_eq!(tags, vec!["alpha".to_string(), "beta".to_string()]);
7646 }
7647
7648 #[test]
7649 fn test_semantic_store_tags_for_none_for_missing_key() {
7650 let store = SemanticStore::new();
7651 assert!(store.tags_for("ghost").unwrap().is_none());
7652 }
7653
7654 #[test]
7655 fn test_semantic_store_tags_for_empty_tags() {
7656 let store = SemanticStore::new();
7657 store.store("k", "v", vec![]).unwrap();
7658 let tags = store.tags_for("k").unwrap().unwrap();
7659 assert!(tags.is_empty());
7660 }
7661
7662 #[test]
7665 fn test_peek_oldest_returns_oldest_without_removing() {
7666 let wm = WorkingMemory::new(10).unwrap();
7667 wm.set("first", "alpha").unwrap();
7668 wm.set("second", "beta").unwrap();
7669 let peeked = wm.peek_oldest().unwrap();
7670 assert_eq!(peeked, Some(("first".into(), "alpha".into())));
7671 assert_eq!(wm.len().unwrap(), 2);
7673 }
7674
7675 #[test]
7676 fn test_peek_oldest_empty_returns_none() {
7677 let wm = WorkingMemory::new(5).unwrap();
7678 assert_eq!(wm.peek_oldest().unwrap(), None);
7679 }
7680
7681 #[test]
7682 fn test_peek_oldest_does_not_remove_entry() {
7683 let wm = WorkingMemory::new(5).unwrap();
7684 wm.set("k", "v").unwrap();
7685 wm.peek_oldest().unwrap();
7686 assert_eq!(wm.get("k").unwrap(), Some("v".into()));
7687 }
7688
7689 #[test]
7690 fn test_semantic_store_values_returns_all_values() {
7691 let store = SemanticStore::new();
7692 store.store("k1", "hello", vec![]).unwrap();
7693 store.store("k2", "world", vec![]).unwrap();
7694 let vals = store.values().unwrap();
7695 assert_eq!(vals.len(), 2);
7696 assert!(vals.contains(&"hello".to_string()));
7697 assert!(vals.contains(&"world".to_string()));
7698 }
7699
7700 #[test]
7701 fn test_semantic_store_values_empty() {
7702 let store = SemanticStore::new();
7703 assert!(store.values().unwrap().is_empty());
7704 }
7705
7706 #[test]
7707 fn test_semantic_store_get_tags_returns_tags() {
7708 let store = SemanticStore::new();
7709 store.store("k1", "val", vec!["tag-a".to_string(), "tag-b".to_string()]).unwrap();
7710 let tags = store.get_tags("k1").unwrap();
7711 assert!(tags.is_some());
7712 let tags = tags.unwrap();
7713 assert!(tags.contains(&"tag-a".to_string()));
7714 assert!(tags.contains(&"tag-b".to_string()));
7715 }
7716
7717 #[test]
7718 fn test_semantic_store_get_tags_missing_key_returns_none() {
7719 let store = SemanticStore::new();
7720 assert!(store.get_tags("no-such-key").unwrap().is_none());
7721 }
7722
7723 #[test]
7726 fn test_working_memory_value_length_returns_char_count_r27() {
7727 let wm = WorkingMemory::new(10).unwrap();
7728 wm.set("k", "hello").unwrap();
7729 assert_eq!(wm.value_length("k").unwrap(), Some(5));
7730 }
7731
7732 #[test]
7733 fn test_working_memory_value_length_none_for_missing_key_r27() {
7734 let wm = WorkingMemory::new(10).unwrap();
7735 assert!(wm.value_length("nope").unwrap().is_none());
7736 }
7737
7738 #[test]
7739 fn test_working_memory_iter_sorted_returns_sorted_pairs() {
7740 let wm = WorkingMemory::new(10).unwrap();
7741 wm.set("b", "2").unwrap();
7742 wm.set("a", "1").unwrap();
7743 let pairs = wm.iter_sorted().unwrap();
7744 assert_eq!(pairs[0].0, "a");
7745 assert_eq!(pairs[1].0, "b");
7746 }
7747
7748 #[test]
7749 fn test_working_memory_drain_empties_store() {
7750 let wm = WorkingMemory::new(10).unwrap();
7751 wm.set("x", "1").unwrap();
7752 wm.set("y", "2").unwrap();
7753 let drained = wm.drain().unwrap();
7754 assert_eq!(drained.len(), 2);
7755 assert!(wm.is_empty().unwrap());
7756 }
7757
7758 #[test]
7759 fn test_working_memory_snapshot_returns_all_entries_r27() {
7760 let wm = WorkingMemory::new(10).unwrap();
7761 wm.set("a", "alpha").unwrap();
7762 wm.set("b", "beta").unwrap();
7763 let snap = wm.snapshot().unwrap();
7764 assert_eq!(snap.get("a").map(String::as_str), Some("alpha"));
7765 assert_eq!(snap.get("b").map(String::as_str), Some("beta"));
7766 }
7767
7768 #[test]
7769 fn test_working_memory_snapshot_empty_when_no_entries_r27() {
7770 let wm = WorkingMemory::new(5).unwrap();
7771 assert!(wm.snapshot().unwrap().is_empty());
7772 }
7773
7774 #[test]
7778 fn test_episodic_store_agent_count_zero_when_empty() {
7779 let store = EpisodicStore::new();
7780 assert_eq!(store.agent_count().unwrap(), 0);
7781 }
7782
7783 #[test]
7784 fn test_episodic_store_agent_count_distinct_agents() {
7785 let store = EpisodicStore::new();
7786 store.add_episode(AgentId::new("a"), "ep1", 0.5).unwrap();
7787 store.add_episode(AgentId::new("b"), "ep2", 0.7).unwrap();
7788 store.add_episode(AgentId::new("a"), "ep3", 0.3).unwrap();
7789 assert_eq!(store.agent_count().unwrap(), 2);
7790 }
7791
7792 #[test]
7793 fn test_working_memory_is_at_capacity_true_when_full() {
7794 let wm = WorkingMemory::new(2).unwrap();
7795 wm.set("k1", "v1").unwrap();
7796 wm.set("k2", "v2").unwrap();
7797 assert!(wm.is_at_capacity().unwrap());
7798 }
7799
7800 #[test]
7801 fn test_working_memory_is_at_capacity_false_when_not_full() {
7802 let wm = WorkingMemory::new(5).unwrap();
7803 wm.set("k1", "v1").unwrap();
7804 assert!(!wm.is_at_capacity().unwrap());
7805 }
7806
7807 #[test]
7808 fn test_remove_keys_starting_with_removes_matching_keys() {
7809 let wm = WorkingMemory::new(10).unwrap();
7810 wm.set("prefix_a", "1").unwrap();
7811 wm.set("prefix_b", "2").unwrap();
7812 wm.set("other", "3").unwrap();
7813 let removed = wm.remove_keys_starting_with("prefix_").unwrap();
7814 assert_eq!(removed, 2);
7815 assert!(wm.get("prefix_a").unwrap().is_none());
7816 assert!(wm.get("prefix_b").unwrap().is_none());
7817 assert_eq!(wm.get("other").unwrap(), Some("3".into()));
7818 }
7819
7820 #[test]
7821 fn test_remove_keys_starting_with_returns_zero_when_no_match() {
7822 let wm = WorkingMemory::new(5).unwrap();
7823 wm.set("foo", "bar").unwrap();
7824 assert_eq!(wm.remove_keys_starting_with("xyz_").unwrap(), 0);
7825 }
7826
7827 #[test]
7828 fn test_semantic_store_has_key_true_when_exists() {
7829 let store = SemanticStore::new();
7830 store.store("mykey", "val", vec![]).unwrap();
7831 assert!(store.has_key("mykey").unwrap());
7832 }
7833
7834 #[test]
7835 fn test_semantic_store_has_key_false_when_absent() {
7836 let store = SemanticStore::new();
7837 assert!(!store.has_key("ghost").unwrap());
7838 }
7839
7840 #[test]
7841 fn test_entry_count_with_tag_counts_correctly() {
7842 let store = SemanticStore::new();
7843 store.store("k1", "v1", vec!["rust".into(), "async".into()]).unwrap();
7844 store.store("k2", "v2", vec!["rust".into()]).unwrap();
7845 store.store("k3", "v3", vec!["python".into()]).unwrap();
7846 assert_eq!(store.entry_count_with_tag("rust").unwrap(), 2);
7847 assert_eq!(store.entry_count_with_tag("async").unwrap(), 1);
7848 assert_eq!(store.entry_count_with_tag("absent").unwrap(), 0);
7849 }
7850
7851 #[test]
7854 fn test_importance_sum_returns_sum_of_all_importances() {
7855 let store = EpisodicStore::new();
7856 let agent = AgentId::new("a");
7857 store.add_episode(agent.clone(), "e1", 0.2).unwrap();
7858 store.add_episode(agent.clone(), "e2", 0.3).unwrap();
7859 store.add_episode(agent.clone(), "e3", 0.5).unwrap();
7860 let sum = store.importance_sum(&agent).unwrap();
7861 assert!((sum - 1.0).abs() < 1e-5);
7862 }
7863
7864 #[test]
7865 fn test_importance_sum_zero_for_unknown_agent() {
7866 let store = EpisodicStore::new();
7867 let agent = AgentId::new("nobody");
7868 assert!((store.importance_sum(&agent).unwrap() - 0.0).abs() < 1e-9);
7869 }
7870
7871 #[test]
7872 fn test_update_content_changes_stored_content() {
7873 let store = EpisodicStore::new();
7874 let agent = AgentId::new("a");
7875 let id = store.add_episode(agent.clone(), "old content", 0.5).unwrap();
7876 let updated = store.update_content(&agent, &id, "new content").unwrap();
7877 assert!(updated);
7878 let items = store.recall_all(&agent).unwrap();
7879 assert_eq!(items[0].content, "new content");
7880 }
7881
7882 #[test]
7883 fn test_update_content_false_for_missing_id() {
7884 let store = EpisodicStore::new();
7885 let agent = AgentId::new("a");
7886 let fake_id = MemoryId::new("nonexistent");
7887 assert!(!store.update_content(&agent, &fake_id, "x").unwrap());
7888 }
7889
7890 #[test]
7891 fn test_recall_all_returns_all_episodes() {
7892 let store = EpisodicStore::new();
7893 let agent = AgentId::new("a");
7894 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
7895 store.add_episode(agent.clone(), "e2", 0.3).unwrap();
7896 store.add_episode(agent.clone(), "e3", 0.9).unwrap();
7897 let all = store.recall_all(&agent).unwrap();
7898 assert_eq!(all.len(), 3);
7899 }
7900
7901 #[test]
7902 fn test_top_n_returns_top_by_importance() {
7903 let store = EpisodicStore::new();
7904 let agent = AgentId::new("a");
7905 store.add_episode(agent.clone(), "low", 0.1).unwrap();
7906 store.add_episode(agent.clone(), "high", 0.9).unwrap();
7907 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
7908 let top = store.top_n(&agent, 2).unwrap();
7909 assert_eq!(top.len(), 2);
7910 assert_eq!(top[0].content, "high");
7911 assert_eq!(top[1].content, "mid");
7912 }
7913
7914 #[test]
7915 fn test_search_by_importance_range_filters_correctly() {
7916 let store = EpisodicStore::new();
7917 let agent = AgentId::new("a");
7918 store.add_episode(agent.clone(), "low", 0.1).unwrap();
7919 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
7920 store.add_episode(agent.clone(), "high", 0.9).unwrap();
7921 let results = store.search_by_importance_range(&agent, 0.4, 0.8, 0).unwrap();
7922 assert_eq!(results.len(), 1);
7923 assert_eq!(results[0].content, "mid");
7924 }
7925
7926 #[test]
7929 fn test_oldest_episode_returns_none_for_new_agent() {
7930 let store = EpisodicStore::new();
7931 let agent = AgentId::new("fresh");
7932 assert!(store.oldest_episode(&agent).unwrap().is_none());
7933 }
7934
7935 #[test]
7936 fn test_oldest_episode_returns_earliest_timestamp() {
7937 let store = EpisodicStore::new();
7938 let agent = AgentId::new("a");
7939 store.add_episode(agent.clone(), "first", 0.5).unwrap();
7940 store.add_episode(agent.clone(), "second", 0.7).unwrap();
7941 store.add_episode(agent.clone(), "third", 0.3).unwrap();
7942 let oldest = store.oldest_episode(&agent).unwrap().unwrap();
7943 assert_eq!(oldest.content, "first");
7945 }
7946
7947 #[test]
7948 fn test_semantic_store_remove_by_key_removes_all_matching() {
7949 let store = SemanticStore::new();
7950 store.store("target", "v1", vec![]).unwrap();
7951 store.store("target", "v2", vec![]).unwrap();
7952 store.store("keep", "vk", vec![]).unwrap();
7953 let removed = store.remove_by_key("target").unwrap();
7954 assert_eq!(removed, 2);
7955 assert!(!store.has_key("target").unwrap());
7956 assert!(store.has_key("keep").unwrap());
7957 }
7958
7959 #[test]
7960 fn test_semantic_store_remove_by_key_zero_for_absent_key() {
7961 let store = SemanticStore::new();
7962 assert_eq!(store.remove_by_key("ghost").unwrap(), 0);
7963 }
7964
7965 #[test]
7966 fn test_working_memory_total_value_bytes_sums_lengths() {
7967 let wm = WorkingMemory::new(10).unwrap();
7968 wm.set("a", "hello").unwrap(); wm.set("b", "world!").unwrap(); assert_eq!(wm.total_value_bytes().unwrap(), 11);
7971 }
7972
7973 #[test]
7974 fn test_working_memory_total_value_bytes_zero_when_empty() {
7975 let wm = WorkingMemory::new(5).unwrap();
7976 assert_eq!(wm.total_value_bytes().unwrap(), 0);
7977 }
7978
7979 #[test]
7982 fn test_agent_ids_returns_all_tracked_agents() {
7983 let store = EpisodicStore::new();
7984 let a1 = AgentId::new("alice");
7985 let a2 = AgentId::new("bob");
7986 store.add_episode(a1.clone(), "x", 0.5).unwrap();
7987 store.add_episode(a2.clone(), "y", 0.5).unwrap();
7988 let mut ids = store.agent_ids().unwrap();
7989 ids.sort_by_key(|id| id.as_str().to_string());
7990 assert_eq!(ids.len(), 2);
7991 assert_eq!(ids[0].as_str(), "alice");
7992 assert_eq!(ids[1].as_str(), "bob");
7993 }
7994
7995 #[test]
7996 fn test_agent_ids_empty_for_new_store() {
7997 let store = EpisodicStore::new();
7998 assert!(store.agent_ids().unwrap().is_empty());
7999 }
8000
8001 #[test]
8002 fn test_clear_for_removes_all_episodes_for_agent() {
8003 let store = EpisodicStore::new();
8004 let agent = AgentId::new("a");
8005 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
8006 store.add_episode(agent.clone(), "e2", 0.3).unwrap();
8007 let removed = store.clear_for(&agent).unwrap();
8008 assert_eq!(removed, 2);
8009 assert_eq!(store.count_for(&agent).unwrap(), 0);
8010 }
8011
8012 #[test]
8013 fn test_clear_for_returns_zero_for_unknown_agent() {
8014 let store = EpisodicStore::new();
8015 let agent = AgentId::new("ghost");
8016 assert_eq!(store.clear_for(&agent).unwrap(), 0);
8017 }
8018
8019 #[test]
8020 fn test_recall_since_returns_episodes_after_cutoff() {
8021 let store = EpisodicStore::new();
8022 let agent = AgentId::new("a");
8023 let past = chrono::Utc::now() - chrono::Duration::hours(2);
8024 let future_cutoff = chrono::Utc::now() + chrono::Duration::seconds(1);
8025 store.add_episode_at(agent.clone(), "old", 0.5, past).unwrap();
8027 store.add_episode(agent.clone(), "new", 0.5).unwrap();
8029 let future = store.recall_since(&agent, future_cutoff, 0).unwrap();
8031 assert!(future.is_empty());
8032 let all = store.recall_since(&agent, past - chrono::Duration::seconds(1), 0).unwrap();
8034 assert_eq!(all.len(), 2);
8035 }
8036
8037 #[test]
8040 fn test_sum_recall_counts_zero_for_new_agent() {
8041 let store = EpisodicStore::new();
8042 let agent = AgentId::new("new");
8043 assert_eq!(store.sum_recall_counts(&agent).unwrap(), 0);
8044 }
8045
8046 #[test]
8047 fn test_sum_recall_counts_increases_with_recalls() {
8048 let store = EpisodicStore::new();
8049 let agent = AgentId::new("a");
8050 store.add_episode(agent.clone(), "ep1", 0.5).unwrap();
8051 store.add_episode(agent.clone(), "ep2", 0.8).unwrap();
8052 store.recall(&agent, 2).unwrap();
8054 let total = store.sum_recall_counts(&agent).unwrap();
8055 assert!(total >= 2);
8056 }
8057
8058 #[test]
8061 fn test_max_recall_count_for_none_for_new_agent() {
8062 let store = EpisodicStore::new();
8063 let agent = AgentId::new("ghost");
8064 assert_eq!(store.max_recall_count_for(&agent).unwrap(), None);
8065 }
8066
8067 #[test]
8068 fn test_max_recall_count_for_returns_highest_after_recalls() {
8069 let store = EpisodicStore::new();
8070 let agent = AgentId::new("a");
8071 store.add_episode(agent.clone(), "ep1", 0.9).unwrap();
8072 store.add_episode(agent.clone(), "ep2", 0.1).unwrap();
8073 store.recall(&agent, 2).unwrap();
8075 store.recall(&agent, 1).unwrap();
8076 let max = store.max_recall_count_for(&agent).unwrap().unwrap();
8077 assert!(max >= 1);
8078 }
8079
8080 #[test]
8081 fn test_semantic_most_recent_key_none_when_empty() {
8082 let store = SemanticStore::new();
8083 assert_eq!(store.most_recent_key().unwrap(), None);
8084 }
8085
8086 #[test]
8087 fn test_semantic_most_recent_key_returns_last_inserted() {
8088 let store = SemanticStore::new();
8089 store.store("first", "v1", vec![]).unwrap();
8090 store.store("second", "v2", vec![]).unwrap();
8091 assert_eq!(store.most_recent_key().unwrap(), Some("second".to_string()));
8092 }
8093
8094 #[test]
8095 fn test_working_memory_max_key_length_zero_when_empty() {
8096 let mem = WorkingMemory::new(10).unwrap();
8097 assert_eq!(mem.max_key_length().unwrap(), 0);
8098 }
8099
8100 #[test]
8101 fn test_working_memory_max_key_length_returns_longest() {
8102 let mem = WorkingMemory::new(10).unwrap();
8103 mem.set("ab", "v1").unwrap();
8104 mem.set("abcde", "v2").unwrap();
8105 mem.set("abc", "v3").unwrap();
8106 assert_eq!(mem.max_key_length().unwrap(), 5);
8107 }
8108
8109 #[test]
8112 fn test_working_memory_set_if_absent_inserts_new_key() {
8113 let mem = WorkingMemory::new(10).unwrap();
8114 let inserted = mem.set_if_absent("fresh", "value").unwrap();
8115 assert!(inserted);
8116 assert_eq!(mem.get("fresh").unwrap(), Some("value".to_string()));
8117 }
8118
8119 #[test]
8120 fn test_working_memory_set_if_absent_does_not_overwrite_existing() {
8121 let mem = WorkingMemory::new(10).unwrap();
8122 mem.set("key", "original").unwrap();
8123 let inserted = mem.set_if_absent("key", "replacement").unwrap();
8124 assert!(!inserted);
8125 assert_eq!(mem.get("key").unwrap(), Some("original".to_string()));
8126 }
8127
8128 #[test]
8129 fn test_working_memory_set_if_absent_second_call_returns_false() {
8130 let mem = WorkingMemory::new(10).unwrap();
8131 assert!(mem.set_if_absent("k", "v1").unwrap());
8132 assert!(!mem.set_if_absent("k", "v2").unwrap());
8133 }
8134
8135 #[test]
8138 fn test_memory_item_has_tag_true_when_tag_present() {
8139 let item = MemoryItem::new(
8140 AgentId::new("a"),
8141 "content",
8142 0.5,
8143 vec!["important".to_string(), "work".to_string()],
8144 );
8145 assert!(item.has_tag("important"));
8146 assert!(item.has_tag("work"));
8147 }
8148
8149 #[test]
8150 fn test_memory_item_has_tag_false_when_tag_absent() {
8151 let item = MemoryItem::new(AgentId::new("a"), "content", 0.5, vec![]);
8152 assert!(!item.has_tag("missing"));
8153 }
8154
8155 #[test]
8156 fn test_memory_item_word_count_counts_words() {
8157 let item = MemoryItem::new(AgentId::new("a"), "one two three", 0.5, vec![]);
8158 assert_eq!(item.word_count(), 3);
8159 }
8160
8161 #[test]
8162 fn test_memory_item_word_count_zero_for_empty_content() {
8163 let item = MemoryItem::new(AgentId::new("a"), "", 0.5, vec![]);
8164 assert_eq!(item.word_count(), 0);
8165 }
8166
8167 #[test]
8168 fn test_decay_policy_apply_reduces_importance_after_one_half_life() {
8169 let policy = DecayPolicy::exponential(1.0).unwrap(); let decayed = policy.apply(1.0, 1.0); assert!((decayed - 0.5).abs() < 1e-5);
8172 }
8173
8174 #[test]
8175 fn test_decay_policy_apply_no_change_for_zero_age() {
8176 let policy = DecayPolicy::exponential(10.0).unwrap();
8177 let decayed = policy.apply(0.8, 0.0);
8178 assert!((decayed - 0.8).abs() < 1e-5);
8179 }
8180
8181 #[test]
8182 fn test_decay_policy_decay_item_reduces_importance_for_old_item() {
8183 let policy = DecayPolicy::exponential(0.0001).unwrap(); let mut item = MemoryItem::new(AgentId::new("a"), "old memory", 1.0, vec![]);
8185 item.timestamp = Utc::now() - chrono::Duration::hours(1);
8186 policy.decay_item(&mut item);
8187 assert!(item.importance < 1.0);
8188 }
8189
8190 #[test]
8191 fn test_episodic_export_returns_empty_for_unknown_agent() {
8192 let store = EpisodicStore::new();
8193 let agent = AgentId::new("ghost");
8194 let exported = store.export_agent_memory(&agent).unwrap();
8195 assert!(exported.is_empty());
8196 }
8197
8198 #[test]
8199 fn test_episodic_export_returns_all_stored_items() {
8200 let store = EpisodicStore::new();
8201 let agent = AgentId::new("a");
8202 store.add_episode(agent.clone(), "memory1", 0.9).unwrap();
8203 store.add_episode(agent.clone(), "memory2", 0.5).unwrap();
8204 let exported = store.export_agent_memory(&agent).unwrap();
8205 assert_eq!(exported.len(), 2);
8206 }
8207
8208 #[test]
8209 fn test_bump_recall_count_increases_by_amount() {
8210 let store = EpisodicStore::new();
8211 let agent = AgentId::new("a");
8212 store.add_episode(agent.clone(), "the content", 0.7).unwrap();
8213 store.bump_recall_count_by_content("the content", 5);
8214 let max = store.max_recall_count_for(&agent).unwrap().unwrap();
8215 assert_eq!(max, 5);
8216 }
8217
8218 #[test]
8219 fn test_bump_recall_count_no_effect_for_absent_content() {
8220 let store = EpisodicStore::new();
8221 let agent = AgentId::new("a");
8222 store.add_episode(agent.clone(), "existing", 0.5).unwrap();
8223 store.bump_recall_count_by_content("not here", 10);
8224 let max = store.max_recall_count_for(&agent).unwrap().unwrap();
8225 assert_eq!(max, 0);
8226 }
8227
8228 #[test]
8231 fn test_latest_episode_none_for_new_agent() {
8232 let store = EpisodicStore::new();
8233 let agent = AgentId::new("ghost");
8234 assert!(store.latest_episode(&agent).unwrap().is_none());
8235 }
8236
8237 #[test]
8238 fn test_latest_episode_returns_most_recent_by_timestamp() {
8239 let store = EpisodicStore::new();
8240 let agent = AgentId::new("a");
8241 let old = chrono::Utc::now() - chrono::Duration::hours(1);
8242 store.add_episode_at(agent.clone(), "old ep", 0.5, old).unwrap();
8243 store.add_episode(agent.clone(), "new ep", 0.8).unwrap();
8244 let latest = store.latest_episode(&agent).unwrap().unwrap();
8245 assert_eq!(latest.content, "new ep");
8246 }
8247
8248 #[test]
8249 fn test_semantic_oldest_key_none_when_empty() {
8250 let store = SemanticStore::new();
8251 assert!(store.oldest_key().unwrap().is_none());
8252 }
8253
8254 #[test]
8255 fn test_semantic_oldest_key_returns_first_inserted() {
8256 let store = SemanticStore::new();
8257 store.store("first", "value1", vec![]).unwrap();
8258 store.store("second", "value2", vec![]).unwrap();
8259 assert_eq!(store.oldest_key().unwrap().as_deref(), Some("first"));
8260 }
8261
8262 #[test]
8263 fn test_working_memory_key_count_matching_zero_when_empty() {
8264 let mem = WorkingMemory::new(10).unwrap();
8265 assert_eq!(mem.key_count_matching("foo").unwrap(), 0);
8266 }
8267
8268 #[test]
8269 fn test_working_memory_key_count_matching_counts_correctly() {
8270 let mem = WorkingMemory::new(10).unwrap();
8271 mem.set("foo_bar", "v1").unwrap();
8272 mem.set("foo_baz", "v2").unwrap();
8273 mem.set("other", "v3").unwrap();
8274 assert_eq!(mem.key_count_matching("foo").unwrap(), 2);
8275 }
8276
8277 #[test]
8278 fn test_working_memory_avg_value_length_zero_when_empty() {
8279 let mem = WorkingMemory::new(10).unwrap();
8280 assert!((mem.avg_value_length().unwrap() - 0.0).abs() < 1e-9);
8281 }
8282
8283 #[test]
8284 fn test_working_memory_avg_value_length_correct_mean() {
8285 let mem = WorkingMemory::new(10).unwrap();
8286 mem.set("k1", "ab").unwrap(); mem.set("k2", "abcd").unwrap(); assert!((mem.avg_value_length().unwrap() - 3.0).abs() < 1e-9);
8290 }
8291
8292 #[test]
8295 fn test_episodic_store_has_agent_false_when_empty() {
8296 let store = EpisodicStore::new();
8297 assert!(!store.has_agent(&AgentId::new("nobody")).unwrap());
8298 }
8299
8300 #[test]
8301 fn test_episodic_store_has_agent_true_after_episode() {
8302 let store = EpisodicStore::new();
8303 let agent = AgentId::new("agent-ha");
8304 store.add_episode(agent.clone(), "event", 0.5).unwrap();
8305 assert!(store.has_agent(&agent).unwrap());
8306 }
8307
8308 #[test]
8309 fn test_episodic_store_export_agent_memory_empty_for_unknown() {
8310 let store = EpisodicStore::new();
8311 let exported = store.export_agent_memory(&AgentId::new("ghost")).unwrap();
8312 assert!(exported.is_empty());
8313 }
8314
8315 #[test]
8316 fn test_episodic_store_export_agent_memory_returns_episodes() {
8317 let store = EpisodicStore::new();
8318 let agent = AgentId::new("agent-exp");
8319 store.add_episode(agent.clone(), "ep1", 0.9).unwrap();
8320 store.add_episode(agent.clone(), "ep2", 0.7).unwrap();
8321 let exported = store.export_agent_memory(&agent).unwrap();
8322 assert_eq!(exported.len(), 2);
8323 }
8324
8325 #[test]
8328 fn test_semantic_store_store_with_embedding_rejects_empty_vec() {
8329 let store = SemanticStore::new();
8330 let result = store.store_with_embedding("k", "v", vec![], vec![]);
8331 assert!(result.is_err());
8332 }
8333
8334 #[test]
8335 fn test_semantic_store_store_with_embedding_stores_entry() {
8336 let store = SemanticStore::new();
8337 let emb = vec![1.0f32, 0.0, 0.0];
8338 store.store_with_embedding("k1", "v1", vec![], emb).unwrap();
8339 assert_eq!(store.len().unwrap(), 1);
8340 }
8341
8342 #[test]
8343 fn test_semantic_store_store_with_embedding_rejects_dimension_mismatch() {
8344 let store = SemanticStore::new();
8345 store.store_with_embedding("k1", "v1", vec![], vec![1.0f32, 0.0]).unwrap();
8346 let result = store.store_with_embedding("k2", "v2", vec![], vec![1.0f32, 0.0, 0.0]);
8348 assert!(result.is_err());
8349 }
8350
8351 #[test]
8355 fn test_avg_importance_zero_for_new_agent() {
8356 let store = EpisodicStore::new();
8357 let agent = AgentId::new("ghost");
8358 assert!((store.avg_importance(&agent).unwrap() - 0.0).abs() < 1e-9);
8359 }
8360
8361 #[test]
8362 fn test_avg_importance_correct_mean() {
8363 let store = EpisodicStore::new();
8364 let agent = AgentId::new("a");
8365 store.add_episode(agent.clone(), "ep1", 0.2).unwrap();
8366 store.add_episode(agent.clone(), "ep2", 0.8).unwrap();
8367 assert!((store.avg_importance(&agent).unwrap() - 0.5).abs() < 1e-6);
8369 }
8370
8371 #[test]
8372 fn test_importance_range_none_for_new_agent() {
8373 let store = EpisodicStore::new();
8374 let agent = AgentId::new("ghost");
8375 assert!(store.importance_range(&agent).unwrap().is_none());
8376 }
8377
8378 #[test]
8379 fn test_importance_range_returns_min_max() {
8380 let store = EpisodicStore::new();
8381 let agent = AgentId::new("a");
8382 store.add_episode(agent.clone(), "ep1", 0.1).unwrap();
8383 store.add_episode(agent.clone(), "ep2", 0.9).unwrap();
8384 let (min, max) = store.importance_range(&agent).unwrap().unwrap();
8385 assert!((min - 0.1_f32).abs() < 1e-6);
8386 assert!((max - 0.9_f32).abs() < 1e-6);
8387 }
8388
8389 #[test]
8390 fn test_semantic_entries_without_tags_all_untagged() {
8391 let store = SemanticStore::new();
8392 store.store("k1", "v1", vec![]).unwrap();
8393 store.store("k2", "v2", vec![]).unwrap();
8394 assert_eq!(store.entries_without_tags().unwrap(), 2);
8395 }
8396
8397 #[test]
8398 fn test_semantic_entries_without_tags_mixed() {
8399 let store = SemanticStore::new();
8400 store.store("k1", "v1", vec!["tag".to_string()]).unwrap();
8401 store.store("k2", "v2", vec![]).unwrap();
8402 assert_eq!(store.entries_without_tags().unwrap(), 1);
8403 }
8404
8405 #[test]
8406 fn test_semantic_avg_tag_count_zero_when_empty() {
8407 let store = SemanticStore::new();
8408 assert!((store.avg_tag_count_per_entry().unwrap() - 0.0).abs() < 1e-9);
8409 }
8410
8411 #[test]
8412 fn test_semantic_avg_tag_count_correct_mean() {
8413 let store = SemanticStore::new();
8414 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);
8418 }
8419
8420 #[test]
8421 fn test_working_memory_longest_key_none_when_empty() {
8422 let mem = WorkingMemory::new(10).unwrap();
8423 assert!(mem.longest_key().unwrap().is_none());
8424 }
8425
8426 #[test]
8427 fn test_working_memory_longest_key_returns_longest() {
8428 let mem = WorkingMemory::new(10).unwrap();
8429 mem.set("ab", "v1").unwrap();
8430 mem.set("abcde", "v2").unwrap();
8431 assert_eq!(mem.longest_key().unwrap().as_deref(), Some("abcde"));
8432 }
8433
8434 #[test]
8435 fn test_working_memory_longest_value_none_when_empty() {
8436 let mem = WorkingMemory::new(10).unwrap();
8437 assert!(mem.longest_value().unwrap().is_none());
8438 }
8439
8440 #[test]
8441 fn test_working_memory_longest_value_returns_longest() {
8442 let mem = WorkingMemory::new(10).unwrap();
8443 mem.set("k1", "short").unwrap();
8444 mem.set("k2", "much longer value").unwrap();
8445 assert_eq!(mem.longest_value().unwrap().as_deref(), Some("much longer value"));
8446 }
8447
8448 #[test]
8451 fn test_semantic_store_with_embedding_rejects_empty_vector() {
8452 let store = SemanticStore::new();
8453 let result = store.store_with_embedding("k", "v", vec![], vec![]);
8454 assert!(result.is_err());
8455 }
8456
8457 #[test]
8458 fn test_semantic_store_with_embedding_stores_and_retrievable() {
8459 let store = SemanticStore::new();
8460 store.store_with_embedding("k", "v", vec![], vec![1.0, 0.0]).unwrap();
8461 let entry = store.retrieve_by_key("k").unwrap();
8462 assert_eq!(entry.map(|(val, _)| val), Some("v".to_string()));
8463 }
8464
8465 #[test]
8466 fn test_semantic_store_with_embedding_dimension_mismatch_errors() {
8467 let store = SemanticStore::new();
8468 store.store_with_embedding("k1", "v1", vec![], vec![1.0, 0.0]).unwrap();
8469 let result = store.store_with_embedding("k2", "v2", vec![], vec![1.0, 0.0, 0.0]);
8470 assert!(result.is_err());
8471 }
8472
8473 #[test]
8476 fn test_episodic_store_has_episodes_false_when_empty() {
8477 let store = EpisodicStore::new();
8478 let id = AgentId::new("agent-x");
8479 assert!(!store.has_episodes(&id).unwrap());
8480 }
8481
8482 #[test]
8483 fn test_episodic_store_has_episodes_true_after_recording() {
8484 let store = EpisodicStore::new();
8485 let id = AgentId::new("agent-y");
8486 store.add_episode(id.clone(), "e1", 0.8).unwrap();
8487 assert!(store.has_episodes(&id).unwrap());
8488 }
8489
8490 #[test]
8491 fn test_semantic_store_value_for_none_when_missing() {
8492 let store = SemanticStore::new();
8493 assert!(store.value_for("missing-key").unwrap().is_none());
8494 }
8495
8496 #[test]
8497 fn test_semantic_store_value_for_returns_stored_value() {
8498 let store = SemanticStore::new();
8499 store.store("mykey", "myvalue", vec![]).unwrap();
8500 assert_eq!(store.value_for("mykey").unwrap(), Some("myvalue".to_string()));
8501 }
8502
8503 #[test]
8504 fn test_working_memory_count_above_value_length_zero_when_empty() {
8505 let wm = WorkingMemory::new(10).unwrap();
8506 assert_eq!(wm.count_above_value_length(5).unwrap(), 0);
8507 }
8508
8509 #[test]
8510 fn test_working_memory_count_above_value_length_counts_correctly() {
8511 let wm = WorkingMemory::new(10).unwrap();
8512 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);
8517 }
8518
8519 #[test]
8522 fn test_total_episode_count_zero_when_empty() {
8523 let store = EpisodicStore::new();
8524 assert_eq!(store.total_episode_count().unwrap(), 0);
8525 }
8526
8527 #[test]
8528 fn test_total_episode_count_sums_across_agents() {
8529 let store = EpisodicStore::new();
8530 let a1 = AgentId::new("a1");
8531 let a2 = AgentId::new("a2");
8532 store.add_episode(a1.clone(), "e1", 0.5).unwrap();
8533 store.add_episode(a1.clone(), "e2", 0.6).unwrap();
8534 store.add_episode(a2.clone(), "e3", 0.7).unwrap();
8535 assert_eq!(store.total_episode_count().unwrap(), 3);
8536 }
8537
8538 #[test]
8541 fn test_agents_with_min_episodes_empty_when_below_min() {
8542 let store = EpisodicStore::new();
8543 let a = AgentId::new("a1");
8544 store.add_episode(a.clone(), "e1", 0.5).unwrap();
8545 assert!(store.agents_with_min_episodes(2).unwrap().is_empty());
8547 }
8548
8549 #[test]
8550 fn test_agents_with_min_episodes_includes_qualifying_agents() {
8551 let store = EpisodicStore::new();
8552 let a1 = AgentId::new("a1");
8553 let a2 = AgentId::new("a2");
8554 store.add_episode(a1.clone(), "e1", 0.5).unwrap();
8555 store.add_episode(a1.clone(), "e2", 0.6).unwrap();
8556 store.add_episode(a2.clone(), "only-one", 0.7).unwrap();
8557 let result = store.agents_with_min_episodes(2).unwrap();
8558 assert_eq!(result, vec![a1]);
8559 }
8560
8561 #[test]
8562 fn test_entries_with_no_tags_returns_empty_list_when_all_have_tags() {
8563 let store = SemanticStore::new();
8564 store.store("k1", "v1", vec!["tag".to_string()]).unwrap();
8565 assert!(store.entries_with_no_tags().unwrap().is_empty());
8566 }
8567
8568 #[test]
8569 fn test_entries_with_no_tags_returns_untagged_keys() {
8570 let store = SemanticStore::new();
8571 store.store("k1", "v1", vec![]).unwrap();
8572 store.store("k2", "v2", vec!["tag".to_string()]).unwrap();
8573 let result = store.entries_with_no_tags().unwrap();
8574 assert_eq!(result, vec!["k1".to_string()]);
8575 }
8576
8577 #[test]
8578 fn test_working_memory_longest_value_key_none_when_empty() {
8579 let wm = WorkingMemory::new(10).unwrap();
8580 assert!(wm.longest_value_key().unwrap().is_none());
8581 }
8582
8583 #[test]
8584 fn test_working_memory_longest_value_key_returns_key_with_longest_value() {
8585 let wm = WorkingMemory::new(10).unwrap();
8586 wm.set("short_key", "hi").unwrap();
8587 wm.set("long_key", "a much longer value string").unwrap();
8588 assert_eq!(wm.longest_value_key().unwrap(), Some("long_key".to_string()));
8589 }
8590
8591 #[test]
8594 fn test_agent_with_most_episodes_none_when_empty() {
8595 let store = EpisodicStore::new();
8596 assert!(store.agent_with_most_episodes().unwrap().is_none());
8597 }
8598
8599 #[test]
8600 fn test_agent_with_most_episodes_returns_agent_with_most() {
8601 let store = EpisodicStore::new();
8602 let a1 = AgentId::new("a1");
8603 let a2 = AgentId::new("a2");
8604 store.add_episode(a1.clone(), "e1", 0.5).unwrap();
8605 store.add_episode(a2.clone(), "e1", 0.5).unwrap();
8606 store.add_episode(a2.clone(), "e2", 0.6).unwrap();
8607 assert_eq!(store.agent_with_most_episodes().unwrap(), Some(a2));
8608 }
8609
8610 #[test]
8611 fn test_most_tagged_key_none_when_empty() {
8612 let store = SemanticStore::new();
8613 assert!(store.most_tagged_key().unwrap().is_none());
8614 }
8615
8616 #[test]
8617 fn test_most_tagged_key_returns_key_with_most_tags() {
8618 let store = SemanticStore::new();
8619 store.store("k1", "v1", vec!["a".to_string()]).unwrap();
8620 store.store("k2", "v2", vec!["a".to_string(), "b".to_string(), "c".to_string()]).unwrap();
8621 store.store("k3", "v3", vec![]).unwrap();
8622 assert_eq!(store.most_tagged_key().unwrap(), Some("k2".to_string()));
8623 }
8624
8625 #[test]
8626 fn test_value_lengths_empty_when_empty() {
8627 let wm = WorkingMemory::new(10).unwrap();
8628 assert!(wm.value_lengths().unwrap().is_empty());
8629 }
8630
8631 #[test]
8632 fn test_value_lengths_returns_all_pairs() {
8633 let wm = WorkingMemory::new(10).unwrap();
8634 wm.set("k", "hello").unwrap();
8635 let lengths = wm.value_lengths().unwrap();
8636 assert_eq!(lengths.len(), 1);
8637 assert_eq!(lengths[0], ("k".to_string(), 5));
8638 }
8639
8640 #[test]
8643 fn test_importance_variance_for_zero_when_fewer_than_two() {
8644 let store = EpisodicStore::new();
8645 let id = AgentId::new("a");
8646 store.add_episode(id.clone(), "e", 0.5).unwrap();
8647 assert!((store.importance_variance_for(&id).unwrap() - 0.0).abs() < 1e-6);
8648 }
8649
8650 #[test]
8651 fn test_importance_variance_for_nonzero_with_spread() {
8652 let store = EpisodicStore::new();
8653 let id = AgentId::new("a");
8654 store.add_episode(id.clone(), "e1", 0.0).unwrap();
8655 store.add_episode(id.clone(), "e2", 1.0).unwrap();
8656 let v = store.importance_variance_for(&id).unwrap();
8658 assert!((v - 0.25).abs() < 1e-5);
8659 }
8660
8661 #[test]
8662 fn test_count_matching_value_zero_when_no_match() {
8663 let store = SemanticStore::new();
8664 store.store("k", "hello world", vec![]).unwrap();
8665 assert_eq!(store.count_matching_value("xyz").unwrap(), 0);
8666 }
8667
8668 #[test]
8669 fn test_count_matching_value_counts_containing_entries() {
8670 let store = SemanticStore::new();
8671 store.store("k1", "hello world", vec![]).unwrap();
8672 store.store("k2", "world peace", vec![]).unwrap();
8673 store.store("k3", "goodbye", vec![]).unwrap();
8674 assert_eq!(store.count_matching_value("world").unwrap(), 2);
8675 }
8676
8677 #[test]
8678 fn test_keys_with_value_longer_than_empty_when_all_short() {
8679 let wm = WorkingMemory::new(10).unwrap();
8680 wm.set("k", "hi").unwrap();
8681 assert!(wm.keys_with_value_longer_than(10).unwrap().is_empty());
8682 }
8683
8684 #[test]
8685 fn test_keys_with_value_longer_than_returns_qualifying_keys() {
8686 let wm = WorkingMemory::new(10).unwrap();
8687 wm.set("short", "hi").unwrap();
8688 wm.set("long", "this is a longer value").unwrap();
8689 let keys = wm.keys_with_value_longer_than(5).unwrap();
8690 assert_eq!(keys, vec!["long".to_string()]);
8691 }
8692
8693 #[test]
8694 fn test_episodic_store_max_importance_overall_returns_highest() {
8695 let store = EpisodicStore::new();
8696 let agent = AgentId::new("a");
8697 store.add_episode(agent.clone(), "low", 0.2).unwrap();
8698 store.add_episode(agent.clone(), "high", 0.9).unwrap();
8699 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
8700 let max = store.max_importance_overall().unwrap();
8701 assert!((max.unwrap() - 0.9).abs() < 1e-6);
8702 }
8703
8704 #[test]
8705 fn test_episodic_store_max_importance_overall_empty_returns_none() {
8706 let store = EpisodicStore::new();
8707 assert!(store.max_importance_overall().unwrap().is_none());
8708 }
8709
8710 #[test]
8711 fn test_semantic_store_rename_tag_updates_all_occurrences() {
8712 let store = SemanticStore::new();
8713 store.store("k1", "v1", vec!["old".to_string(), "x".to_string()]).unwrap();
8714 store.store("k2", "v2", vec!["old".to_string()]).unwrap();
8715 let count = store.rename_tag("old", "new").unwrap();
8716 assert_eq!(count, 2);
8717 }
8718
8719 #[test]
8720 fn test_semantic_store_rename_tag_nonexistent_returns_zero() {
8721 let store = SemanticStore::new();
8722 store.store("k", "v", vec!["alpha".to_string()]).unwrap();
8723 assert_eq!(store.rename_tag("missing", "new").unwrap(), 0);
8724 }
8725
8726 #[test]
8727 fn test_working_memory_entry_count_reflects_stored_entries() {
8728 let wm = WorkingMemory::new(10).unwrap();
8729 assert_eq!(wm.entry_count().unwrap(), 0);
8730 wm.set("a", "1").unwrap();
8731 wm.set("b", "2").unwrap();
8732 assert_eq!(wm.entry_count().unwrap(), 2);
8733 }
8734
8735 #[test]
8736 fn test_episode_count_for_returns_correct_count() {
8737 let store = EpisodicStore::new();
8738 let agent = AgentId::new("a");
8739 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
8740 store.add_episode(agent.clone(), "e2", 0.5).unwrap();
8741 assert_eq!(store.episode_count_for(&agent).unwrap(), 2);
8742 }
8743
8744 #[test]
8745 fn test_episode_count_for_unknown_agent_returns_zero() {
8746 let store = EpisodicStore::new();
8747 assert_eq!(store.episode_count_for(&AgentId::new("x")).unwrap(), 0);
8748 }
8749
8750 #[test]
8751 fn test_semantic_store_unique_tags_returns_sorted_distinct_tags() {
8752 let store = SemanticStore::new();
8753 store.store("k1", "v1", vec!["b".to_string(), "a".to_string()]).unwrap();
8754 store.store("k2", "v2", vec!["a".to_string(), "c".to_string()]).unwrap();
8755 assert_eq!(store.unique_tags().unwrap(), vec!["a", "b", "c"]);
8756 }
8757
8758 #[test]
8759 fn test_semantic_store_unique_tags_empty_returns_empty() {
8760 let store = SemanticStore::new();
8761 assert!(store.unique_tags().unwrap().is_empty());
8762 }
8763
8764 #[test]
8765 fn test_working_memory_count_matching_prefix_counts_correctly() {
8766 let wm = WorkingMemory::new(10).unwrap();
8767 wm.set("user:a", "1").unwrap();
8768 wm.set("user:b", "2").unwrap();
8769 wm.set("other", "3").unwrap();
8770 assert_eq!(wm.count_matching_prefix("user:").unwrap(), 2);
8771 assert_eq!(wm.count_matching_prefix("other").unwrap(), 1);
8772 assert_eq!(wm.count_matching_prefix("none").unwrap(), 0);
8773 }
8774
8775 #[test]
8776 fn test_episodic_store_total_content_bytes_sums_lengths() {
8777 let store = EpisodicStore::new();
8778 let agent = AgentId::new("a");
8779 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);
8782 }
8783
8784 #[test]
8785 fn test_episodic_store_total_content_bytes_unknown_agent_returns_zero() {
8786 let store = EpisodicStore::new();
8787 assert_eq!(store.total_content_bytes(&AgentId::new("x")).unwrap(), 0);
8788 }
8789
8790 #[test]
8791 fn test_episodic_store_avg_content_length_correct_mean() {
8792 let store = EpisodicStore::new();
8793 let agent = AgentId::new("a");
8794 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);
8797 }
8798
8799 #[test]
8800 fn test_episodic_store_avg_content_length_empty_returns_zero() {
8801 let store = EpisodicStore::new();
8802 assert_eq!(store.avg_content_length(&AgentId::new("x")).unwrap(), 0.0);
8803 }
8804
8805 #[test]
8806 fn test_semantic_store_keys_for_tag_returns_matching_keys() {
8807 let store = SemanticStore::new();
8808 store.store("k1", "v1", vec!["rust".to_string()]).unwrap();
8809 store.store("k2", "v2", vec!["python".to_string()]).unwrap();
8810 store.store("k3", "v3", vec!["rust".to_string(), "async".to_string()]).unwrap();
8811 let mut keys = store.keys_for_tag("rust").unwrap();
8812 keys.sort_unstable();
8813 assert_eq!(keys, vec!["k1", "k3"]);
8814 }
8815
8816 #[test]
8817 fn test_semantic_store_keys_for_tag_nonexistent_tag_returns_empty() {
8818 let store = SemanticStore::new();
8819 store.store("k", "v", vec!["rust".to_string()]).unwrap();
8820 assert!(store.keys_for_tag("missing").unwrap().is_empty());
8821 }
8822
8823 #[test]
8824 fn test_count_episodes_with_tag_returns_correct_count() {
8825 let store = EpisodicStore::new();
8826 let agent = AgentId::new("a");
8827 store.add_episode_with_tags(agent.clone(), "e1", 0.5, vec!["ai".to_string()]).unwrap();
8828 store.add_episode_with_tags(agent.clone(), "e2", 0.5, vec!["ai".to_string(), "ml".to_string()]).unwrap();
8829 store.add_episode_with_tags(agent.clone(), "e3", 0.5, vec!["ml".to_string()]).unwrap();
8830 assert_eq!(store.count_episodes_with_tag(&agent, "ai").unwrap(), 2);
8831 assert_eq!(store.count_episodes_with_tag(&agent, "ml").unwrap(), 2);
8832 }
8833
8834 #[test]
8835 fn test_episodes_with_content_returns_matching_content() {
8836 let store = EpisodicStore::new();
8837 let agent = AgentId::new("a");
8838 store.add_episode(agent.clone(), "rust is great", 0.5).unwrap();
8839 store.add_episode(agent.clone(), "python is fun", 0.5).unwrap();
8840 store.add_episode(agent.clone(), "rust and python", 0.5).unwrap();
8841 let matches = store.episodes_with_content(&agent, "rust").unwrap();
8842 assert_eq!(matches.len(), 2);
8843 }
8844
8845 #[test]
8846 fn test_semantic_store_most_common_tag_returns_most_frequent() {
8847 let store = SemanticStore::new();
8848 store.store("k1", "v1", vec!["a".to_string(), "b".to_string()]).unwrap();
8849 store.store("k2", "v2", vec!["a".to_string()]).unwrap();
8850 store.store("k3", "v3", vec!["b".to_string()]).unwrap();
8851 let tag = store.most_common_tag().unwrap();
8853 assert!(tag.is_some());
8854 }
8855
8856 #[test]
8857 fn test_semantic_store_most_common_tag_empty_returns_none() {
8858 let store = SemanticStore::new();
8859 assert!(store.most_common_tag().unwrap().is_none());
8860 }
8861
8862 #[test]
8863 fn test_working_memory_pairs_starting_with_returns_matching_pairs() {
8864 let wm = WorkingMemory::new(10).unwrap();
8865 wm.set("user:name", "alice").unwrap();
8866 wm.set("user:age", "30").unwrap();
8867 wm.set("sys:mode", "prod").unwrap();
8868 let pairs = wm.pairs_starting_with("user:").unwrap();
8869 assert_eq!(pairs.len(), 2);
8870 assert!(pairs.iter().all(|(k, _)| k.starts_with("user:")));
8871 }
8872
8873 #[test]
8874 fn test_working_memory_total_key_bytes_sums_key_lengths() {
8875 let wm = WorkingMemory::new(10).unwrap();
8876 wm.set("ab", "x").unwrap(); wm.set("cde", "y").unwrap(); assert_eq!(wm.total_key_bytes().unwrap(), 5);
8879 }
8880
8881 #[test]
8882 fn test_working_memory_min_key_length_returns_shortest() {
8883 let wm = WorkingMemory::new(10).unwrap();
8884 wm.set("ab", "x").unwrap();
8885 wm.set("abcd", "y").unwrap();
8886 assert_eq!(wm.min_key_length().unwrap(), 2);
8887 }
8888
8889 #[test]
8890 fn test_working_memory_min_key_length_empty_returns_zero() {
8891 let wm = WorkingMemory::new(10).unwrap();
8892 assert_eq!(wm.min_key_length().unwrap(), 0);
8893 }
8894
8895 #[test]
8896 fn test_episodic_store_content_lengths_returns_lengths_in_order() {
8897 let store = EpisodicStore::new();
8898 let agent = AgentId::new("a");
8899 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]);
8902 }
8903
8904 #[test]
8905 fn test_semantic_store_remove_entries_with_tag_removes_matching() {
8906 let store = SemanticStore::new();
8907 store.store("k1", "v1", vec!["old".to_string()]).unwrap();
8908 store.store("k2", "v2", vec!["keep".to_string()]).unwrap();
8909 store.store("k3", "v3", vec!["old".to_string(), "keep".to_string()]).unwrap();
8910 let removed = store.remove_entries_with_tag("old").unwrap();
8911 assert_eq!(removed, 2);
8912 assert_eq!(store.len().unwrap(), 1);
8913 }
8914
8915 #[test]
8918 fn test_episodic_store_max_content_length_returns_longest() {
8919 let store = EpisodicStore::new();
8920 let agent = AgentId::new("a");
8921 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);
8925 }
8926
8927 #[test]
8928 fn test_episodic_store_max_content_length_unknown_agent_returns_zero() {
8929 let store = EpisodicStore::new();
8930 assert_eq!(store.max_content_length(&AgentId::new("x")).unwrap(), 0);
8931 }
8932
8933 #[test]
8934 fn test_episodic_store_min_content_length_returns_shortest() {
8935 let store = EpisodicStore::new();
8936 let agent = AgentId::new("a");
8937 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);
8940 }
8941
8942 #[test]
8943 fn test_episodic_store_min_content_length_unknown_agent_returns_zero() {
8944 let store = EpisodicStore::new();
8945 assert_eq!(store.min_content_length(&AgentId::new("x")).unwrap(), 0);
8946 }
8947
8948 #[test]
8949 fn test_semantic_store_total_value_bytes_sums_value_lengths() {
8950 let store = SemanticStore::new();
8951 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);
8955 }
8956
8957 #[test]
8958 fn test_semantic_store_total_value_bytes_empty_returns_zero() {
8959 let store = SemanticStore::new();
8960 assert_eq!(store.total_value_bytes().unwrap(), 0);
8961 }
8962
8963 #[test]
8966 fn test_episodic_store_agents_with_episodes_returns_sorted_ids() {
8967 let store = EpisodicStore::new();
8968 let b = AgentId::new("b");
8969 let a = AgentId::new("a");
8970 store.add_episode(b.clone(), "episode b", 0.5).unwrap();
8971 store.add_episode(a.clone(), "episode a", 0.5).unwrap();
8972 let agents = store.agents_with_episodes().unwrap();
8973 assert_eq!(agents, vec![a, b]);
8974 }
8975
8976 #[test]
8977 fn test_episodic_store_agents_with_episodes_empty_returns_empty() {
8978 let store = EpisodicStore::new();
8979 assert!(store.agents_with_episodes().unwrap().is_empty());
8980 }
8981
8982 #[test]
8983 fn test_episodic_store_high_importance_count_counts_above_threshold() {
8984 let store = EpisodicStore::new();
8985 let agent = AgentId::new("a");
8986 store.add_episode(agent.clone(), "low", 0.3).unwrap();
8987 store.add_episode(agent.clone(), "high", 0.8).unwrap();
8988 store.add_episode(agent.clone(), "med", 0.6).unwrap();
8989 assert_eq!(store.high_importance_count(&agent, 0.5).unwrap(), 2);
8990 }
8991
8992 #[test]
8993 fn test_episodic_store_high_importance_count_unknown_agent_returns_zero() {
8994 let store = EpisodicStore::new();
8995 assert_eq!(store.high_importance_count(&AgentId::new("x"), 0.5).unwrap(), 0);
8996 }
8997
8998 #[test]
8999 fn test_semantic_store_avg_value_bytes_returns_mean() {
9000 let store = SemanticStore::new();
9001 store.store("k1", "ab", vec![]).unwrap(); store.store("k2", "abcd", vec![]).unwrap(); let avg = store.avg_value_bytes().unwrap();
9004 assert!((avg - 3.0).abs() < 1e-9);
9005 }
9006
9007 #[test]
9008 fn test_semantic_store_avg_value_bytes_empty_returns_zero() {
9009 let store = SemanticStore::new();
9010 assert_eq!(store.avg_value_bytes().unwrap(), 0.0);
9011 }
9012
9013 #[test]
9014 fn test_semantic_store_max_value_bytes_returns_longest() {
9015 let store = SemanticStore::new();
9016 store.store("k1", "hi", vec![]).unwrap(); store.store("k2", "hello!", vec![]).unwrap(); assert_eq!(store.max_value_bytes().unwrap(), 6);
9019 }
9020
9021 #[test]
9022 fn test_semantic_store_max_value_bytes_empty_returns_zero() {
9023 let store = SemanticStore::new();
9024 assert_eq!(store.max_value_bytes().unwrap(), 0);
9025 }
9026
9027 #[test]
9030 fn test_episodic_store_content_contains_count_counts_matches() {
9031 let store = EpisodicStore::new();
9032 let agent = AgentId::new("a");
9033 store.add_episode(agent.clone(), "rust is great", 0.5).unwrap();
9034 store.add_episode(agent.clone(), "python is ok", 0.5).unwrap();
9035 store.add_episode(agent.clone(), "rust rocks", 0.5).unwrap();
9036 assert_eq!(store.content_contains_count(&agent, "rust").unwrap(), 2);
9037 assert_eq!(store.content_contains_count(&agent, "java").unwrap(), 0);
9038 }
9039
9040 #[test]
9041 fn test_episodic_store_content_contains_count_unknown_agent_returns_zero() {
9042 let store = EpisodicStore::new();
9043 assert_eq!(store.content_contains_count(&AgentId::new("x"), "anything").unwrap(), 0);
9044 }
9045
9046 #[test]
9047 fn test_semantic_store_min_value_bytes_returns_shortest() {
9048 let store = SemanticStore::new();
9049 store.store("k1", "hello world", vec![]).unwrap(); store.store("k2", "hi", vec![]).unwrap(); assert_eq!(store.min_value_bytes().unwrap(), 2);
9052 }
9053
9054 #[test]
9055 fn test_semantic_store_min_value_bytes_empty_returns_zero() {
9056 let store = SemanticStore::new();
9057 assert_eq!(store.min_value_bytes().unwrap(), 0);
9058 }
9059
9060 #[test]
9061 fn test_working_memory_max_value_length_returns_longest() {
9062 let wm = WorkingMemory::new(10).unwrap();
9063 wm.set("k1", "ab").unwrap(); wm.set("k2", "abcde").unwrap(); assert_eq!(wm.max_value_length().unwrap(), 5);
9066 }
9067
9068 #[test]
9069 fn test_working_memory_max_value_length_empty_returns_zero() {
9070 let wm = WorkingMemory::new(10).unwrap();
9071 assert_eq!(wm.max_value_length().unwrap(), 0);
9072 }
9073
9074 #[test]
9077 fn test_episodic_store_episodes_by_importance_returns_desc_order() {
9078 let store = EpisodicStore::new();
9079 let agent = AgentId::new("a");
9080 store.add_episode(agent.clone(), "low", 0.2).unwrap();
9081 store.add_episode(agent.clone(), "high", 0.9).unwrap();
9082 store.add_episode(agent.clone(), "med", 0.5).unwrap();
9083 let contents = store.episodes_by_importance(&agent).unwrap();
9084 assert_eq!(contents[0], "high");
9085 assert_eq!(contents[2], "low");
9086 }
9087
9088 #[test]
9089 fn test_episodic_store_episodes_by_importance_empty_agent_returns_empty() {
9090 let store = EpisodicStore::new();
9091 assert!(store.episodes_by_importance(&AgentId::new("x")).unwrap().is_empty());
9092 }
9093
9094 #[test]
9095 fn test_semantic_store_all_keys_returns_sorted_keys() {
9096 let store = SemanticStore::new();
9097 store.store("banana", "v1", vec![]).unwrap();
9098 store.store("apple", "v2", vec![]).unwrap();
9099 store.store("cherry", "v3", vec![]).unwrap();
9100 assert_eq!(store.all_keys().unwrap(), vec!["apple", "banana", "cherry"]);
9101 }
9102
9103 #[test]
9104 fn test_semantic_store_all_keys_empty_returns_empty() {
9105 let store = SemanticStore::new();
9106 assert!(store.all_keys().unwrap().is_empty());
9107 }
9108
9109 #[test]
9110 fn test_working_memory_min_value_length_returns_shortest() {
9111 let wm = WorkingMemory::new(10).unwrap();
9112 wm.set("k1", "ab").unwrap(); wm.set("k2", "abcde").unwrap(); assert_eq!(wm.min_value_length().unwrap(), 2);
9115 }
9116
9117 #[test]
9118 fn test_working_memory_min_value_length_empty_returns_zero() {
9119 let wm = WorkingMemory::new(10).unwrap();
9120 assert_eq!(wm.min_value_length().unwrap(), 0);
9121 }
9122
9123 #[test]
9126 fn test_episodic_store_episode_count_above_importance_counts_correctly() {
9127 let store = EpisodicStore::new();
9128 let agent = AgentId::new("a");
9129 store.add_episode(agent.clone(), "low", 0.2).unwrap();
9130 store.add_episode(agent.clone(), "high", 0.9).unwrap();
9131 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
9132 assert_eq!(store.episode_count_above_importance(&agent, 0.4).unwrap(), 2);
9133 assert_eq!(store.episode_count_above_importance(&agent, 0.8).unwrap(), 1);
9134 }
9135
9136 #[test]
9137 fn test_episodic_store_episode_count_above_importance_unknown_agent_returns_zero() {
9138 let store = EpisodicStore::new();
9139 assert_eq!(store.episode_count_above_importance(&AgentId::new("x"), 0.5).unwrap(), 0);
9140 }
9141
9142 #[test]
9143 fn test_episodic_store_low_importance_episodes_sorted_ascending() {
9144 let store = EpisodicStore::new();
9145 let agent = AgentId::new("a");
9146 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
9147 store.add_episode(agent.clone(), "low", 0.1).unwrap();
9148 store.add_episode(agent.clone(), "high", 0.9).unwrap();
9149 let items = store.low_importance_episodes(&agent, 0.6).unwrap();
9150 assert_eq!(items.len(), 2);
9151 assert!(items[0].importance <= items[1].importance);
9152 }
9153
9154 #[test]
9155 fn test_episodic_store_low_importance_episodes_empty_when_none_below_threshold() {
9156 let store = EpisodicStore::new();
9157 let agent = AgentId::new("a");
9158 store.add_episode(agent.clone(), "high", 0.9).unwrap();
9159 assert!(store.low_importance_episodes(&agent, 0.5).unwrap().is_empty());
9160 }
9161
9162 #[test]
9163 fn test_working_memory_values_containing_returns_matching_values() {
9164 let wm = WorkingMemory::new(10).unwrap();
9165 wm.set("k1", "hello world").unwrap();
9166 wm.set("k2", "goodbye world").unwrap();
9167 wm.set("k3", "something else").unwrap();
9168 let result = wm.values_containing("world").unwrap();
9169 assert_eq!(result.len(), 2);
9170 assert!(result.iter().all(|v| v.contains("world")));
9171 }
9172
9173 #[test]
9174 fn test_working_memory_values_containing_returns_empty_when_no_match() {
9175 let wm = WorkingMemory::new(10).unwrap();
9176 wm.set("k1", "hello").unwrap();
9177 assert!(wm.values_containing("xyz").unwrap().is_empty());
9178 }
9179
9180 #[test]
9183 fn test_semantic_store_keys_with_prefix_returns_matching_keys_sorted() {
9184 let store = SemanticStore::new();
9185 store.store("user:alice", "data-a", vec![]).unwrap();
9186 store.store("user:bob", "data-b", vec![]).unwrap();
9187 store.store("session:123", "sess", vec![]).unwrap();
9188 let keys = store.keys_with_prefix("user:").unwrap();
9189 assert_eq!(keys, vec!["user:alice", "user:bob"]);
9190 }
9191
9192 #[test]
9193 fn test_semantic_store_keys_with_prefix_returns_empty_when_no_match() {
9194 let store = SemanticStore::new();
9195 store.store("abc", "v", vec![]).unwrap();
9196 assert!(store.keys_with_prefix("xyz").unwrap().is_empty());
9197 }
9198
9199 #[test]
9200 fn test_semantic_store_keys_with_prefix_empty_prefix_returns_all_keys() {
9201 let store = SemanticStore::new();
9202 store.store("b", "v2", vec![]).unwrap();
9203 store.store("a", "v1", vec![]).unwrap();
9204 let keys = store.keys_with_prefix("").unwrap();
9205 assert_eq!(keys, vec!["a", "b"]);
9206 }
9207
9208 #[test]
9211 fn test_episodic_store_episodes_sorted_by_timestamp_oldest_first() {
9212 use std::thread::sleep;
9213 use std::time::Duration;
9214 let store = EpisodicStore::new();
9215 let agent = AgentId::new("a");
9216 store.add_episode(agent.clone(), "first", 0.5).unwrap();
9217 sleep(Duration::from_millis(2));
9218 store.add_episode(agent.clone(), "second", 0.5).unwrap();
9219 let items = store.episodes_sorted_by_timestamp(&agent).unwrap();
9220 assert_eq!(items.len(), 2);
9221 assert!(items[0].timestamp <= items[1].timestamp);
9222 }
9223
9224 #[test]
9225 fn test_episodic_store_episodes_sorted_by_timestamp_unknown_agent_empty() {
9226 let store = EpisodicStore::new();
9227 assert!(store.episodes_sorted_by_timestamp(&AgentId::new("unknown")).unwrap().is_empty());
9228 }
9229
9230 #[test]
9231 fn test_working_memory_keys_sorted_returns_alphabetical_order() {
9232 let wm = WorkingMemory::new(10).unwrap();
9233 wm.set("banana", "b").unwrap();
9234 wm.set("apple", "a").unwrap();
9235 wm.set("cherry", "c").unwrap();
9236 assert_eq!(wm.keys_sorted().unwrap(), vec!["apple", "banana", "cherry"]);
9237 }
9238
9239 #[test]
9240 fn test_working_memory_keys_sorted_empty_returns_empty() {
9241 let wm = WorkingMemory::new(10).unwrap();
9242 assert!(wm.keys_sorted().unwrap().is_empty());
9243 }
9244
9245 #[test]
9248 fn test_memory_item_content_len_returns_byte_length() {
9249 let item = MemoryItem::new(AgentId::new("a"), "hello", 0.5, vec![]);
9250 assert_eq!(item.content_len(), 5);
9251 }
9252
9253 #[test]
9254 fn test_memory_item_tag_count_returns_count() {
9255 let item = MemoryItem::new(AgentId::new("a"), "x", 0.5, vec!["t1".into(), "t2".into()]);
9256 assert_eq!(item.tag_count(), 2);
9257 }
9258
9259 #[test]
9260 fn test_memory_item_tag_count_zero_when_no_tags() {
9261 let item = MemoryItem::new(AgentId::new("a"), "x", 0.5, vec![]);
9262 assert_eq!(item.tag_count(), 0);
9263 }
9264
9265 #[test]
9266 fn test_memory_item_add_tag_adds_new_tag() {
9267 let mut item = MemoryItem::new(AgentId::new("a"), "x", 0.5, vec![]);
9268 assert!(item.add_tag("rust"));
9269 assert!(item.has_tag("rust"));
9270 assert_eq!(item.tag_count(), 1);
9271 }
9272
9273 #[test]
9274 fn test_memory_item_add_tag_returns_false_for_duplicate() {
9275 let mut item = MemoryItem::new(AgentId::new("a"), "x", 0.5, vec!["rust".into()]);
9276 assert!(!item.add_tag("rust"));
9277 assert_eq!(item.tag_count(), 1);
9278 }
9279
9280 #[test]
9281 fn test_memory_item_remove_tag_removes_existing_tag() {
9282 let mut item = MemoryItem::new(AgentId::new("a"), "x", 0.5, vec!["rust".into()]);
9283 assert!(item.remove_tag("rust"));
9284 assert!(!item.has_tag("rust"));
9285 assert_eq!(item.tag_count(), 0);
9286 }
9287
9288 #[test]
9289 fn test_memory_item_remove_tag_returns_false_when_not_present() {
9290 let mut item = MemoryItem::new(AgentId::new("a"), "x", 0.5, vec![]);
9291 assert!(!item.remove_tag("missing"));
9292 }
9293
9294 #[test]
9295 fn test_memory_item_is_high_importance_true_above_threshold() {
9296 let item = MemoryItem::new(AgentId::new("a"), "x", 0.9, vec![]);
9297 assert!(item.is_high_importance(0.7));
9298 }
9299
9300 #[test]
9301 fn test_memory_item_is_high_importance_false_at_threshold() {
9302 let item = MemoryItem::new(AgentId::new("a"), "x", 0.7, vec![]);
9304 assert!(!item.is_high_importance(0.7));
9305 }
9306
9307 #[test]
9308 fn test_memory_item_is_high_importance_false_below_threshold() {
9309 let item = MemoryItem::new(AgentId::new("a"), "x", 0.3, vec![]);
9310 assert!(!item.is_high_importance(0.5));
9311 }
9312
9313 #[test]
9316 fn test_episodic_store_recall_count_for_returns_zero_initially() {
9317 let store = EpisodicStore::new();
9318 let agent = AgentId::new("a");
9319 let id = store.add_episode(agent.clone(), "content", 0.5).unwrap();
9320 let count = store.recall_count_for(&agent, &id).unwrap();
9321 assert_eq!(count, Some(0));
9322 }
9323
9324 #[test]
9325 fn test_episodic_store_recall_count_for_increments_after_recall() {
9326 let store = EpisodicStore::new();
9327 let agent = AgentId::new("a");
9328 let id = store.add_episode(agent.clone(), "content", 0.5).unwrap();
9329 store.recall(&agent, 10).unwrap();
9330 let count = store.recall_count_for(&agent, &id).unwrap();
9331 assert_eq!(count, Some(1));
9332 }
9333
9334 #[test]
9335 fn test_episodic_store_recall_count_for_returns_none_for_unknown_id() {
9336 let store = EpisodicStore::new();
9337 let agent = AgentId::new("a");
9338 let missing = MemoryId::new("no-such-id");
9339 assert_eq!(store.recall_count_for(&agent, &missing).unwrap(), None);
9340 }
9341
9342 #[test]
9345 fn test_working_memory_value_for_longest_key_returns_correct_value() {
9346 let wm = WorkingMemory::new(10).unwrap();
9347 wm.set("short", "v1").unwrap();
9348 wm.set("this-is-longer", "v2").unwrap();
9349 assert_eq!(wm.value_for_longest_key().unwrap(), Some("v2".to_string()));
9350 }
9351
9352 #[test]
9353 fn test_working_memory_value_for_longest_key_returns_none_when_empty() {
9354 let wm = WorkingMemory::new(10).unwrap();
9355 assert_eq!(wm.value_for_longest_key().unwrap(), None);
9356 }
9357
9358 #[test]
9361 fn test_decay_policy_display_format() {
9362 let p = DecayPolicy::exponential(24.0).unwrap();
9363 assert_eq!(p.to_string(), "Exponential(half_life=24.0h)");
9364 }
9365
9366 #[test]
9367 fn test_decay_policy_display_fractional_hours() {
9368 let p = DecayPolicy::exponential(1.5).unwrap();
9369 assert_eq!(p.to_string(), "Exponential(half_life=1.5h)");
9370 }
9371
9372 #[test]
9375 fn test_episodic_store_all_unique_tags_returns_sorted_deduped() {
9376 let store = EpisodicStore::new();
9377 let agent = AgentId::new("a");
9378 store.add_episode_with_tags(
9379 agent.clone(), "c1", 0.5, vec!["rust".into(), "ai".into()]
9380 ).unwrap();
9381 store.add_episode_with_tags(
9382 agent.clone(), "c2", 0.5, vec!["ai".into(), "ml".into()]
9383 ).unwrap();
9384 let tags = store.all_unique_tags(&agent).unwrap();
9385 assert_eq!(tags, vec!["ai", "ml", "rust"]);
9386 }
9387
9388 #[test]
9389 fn test_episodic_store_all_unique_tags_empty_for_unknown_agent() {
9390 let store = EpisodicStore::new();
9391 assert!(store.all_unique_tags(&AgentId::new("unknown")).unwrap().is_empty());
9392 }
9393
9394 #[test]
9395 fn test_episodic_store_episodes_with_tag_returns_matching() {
9396 let store = EpisodicStore::new();
9397 let agent = AgentId::new("a");
9398 store.add_episode_with_tags(
9399 agent.clone(), "tagged", 0.5, vec!["rust".into()]
9400 ).unwrap();
9401 store.add_episode(agent.clone(), "no-tag", 0.5).unwrap();
9402 let result = store.episodes_with_tag(&agent, "rust").unwrap();
9403 assert_eq!(result.len(), 1);
9404 assert_eq!(result[0].content, "tagged");
9405 }
9406
9407 #[test]
9408 fn test_episodic_store_episodes_with_tag_empty_when_no_match() {
9409 let store = EpisodicStore::new();
9410 let agent = AgentId::new("a");
9411 store.add_episode(agent.clone(), "no-tag", 0.5).unwrap();
9412 assert!(store.episodes_with_tag(&agent, "missing-tag").unwrap().is_empty());
9413 }
9414
9415 #[test]
9416 fn test_working_memory_contains_value_true_when_present() {
9417 let wm = WorkingMemory::new(10).unwrap();
9418 wm.set("k", "hello").unwrap();
9419 assert!(wm.contains_value("hello").unwrap());
9420 }
9421
9422 #[test]
9423 fn test_working_memory_contains_value_false_when_absent() {
9424 let wm = WorkingMemory::new(10).unwrap();
9425 assert!(!wm.contains_value("anything").unwrap());
9426 }
9427
9428 #[test]
9431 fn test_working_memory_shortest_key_returns_shortest() {
9432 let wm = WorkingMemory::new(10).unwrap();
9433 wm.set("abc", "v").unwrap();
9434 wm.set("a", "v").unwrap();
9435 wm.set("ab", "v").unwrap();
9436 assert_eq!(wm.shortest_key().unwrap(), Some("a".to_string()));
9437 }
9438
9439 #[test]
9440 fn test_working_memory_shortest_key_returns_none_when_empty() {
9441 let wm = WorkingMemory::new(10).unwrap();
9442 assert_eq!(wm.shortest_key().unwrap(), None);
9443 }
9444
9445 #[test]
9446 fn test_working_memory_count_below_value_length_counts_correctly() {
9447 let wm = WorkingMemory::new(10).unwrap();
9448 wm.set("k1", "hi").unwrap(); wm.set("k2", "hello").unwrap(); wm.set("k3", "yo").unwrap(); assert_eq!(wm.count_below_value_length(5).unwrap(), 2);
9453 }
9454
9455 #[test]
9456 fn test_working_memory_count_below_value_length_zero_for_empty_store() {
9457 let wm = WorkingMemory::new(10).unwrap();
9458 assert_eq!(wm.count_below_value_length(100).unwrap(), 0);
9459 }
9460
9461 #[test]
9462 fn test_episodic_store_most_recent_episode_returns_latest() {
9463 use std::thread::sleep;
9464 use std::time::Duration;
9465 let store = EpisodicStore::new();
9466 let agent = AgentId::new("a");
9467 store.add_episode(agent.clone(), "old", 0.5).unwrap();
9468 sleep(Duration::from_millis(5));
9469 store.add_episode(agent.clone(), "new", 0.5).unwrap();
9470 let ep = store.most_recent_episode(&agent).unwrap().unwrap();
9471 assert_eq!(ep.content, "new");
9472 }
9473
9474 #[test]
9475 fn test_episodic_store_most_recent_episode_returns_none_for_unknown_agent() {
9476 let store = EpisodicStore::new();
9477 assert!(store.most_recent_episode(&AgentId::new("nobody")).unwrap().is_none());
9478 }
9479
9480 #[test]
9483 fn test_episodic_store_most_recalled_episode_returns_highest_recall_count() {
9484 let store = EpisodicStore::new();
9485 let agent = AgentId::new("a");
9486 store.add_episode(agent.clone(), "low", 0.5).unwrap();
9487 let id_high = store.add_episode(agent.clone(), "high", 0.8).unwrap();
9488 store.recall(&agent, 10).unwrap();
9490 store.recall(&agent, 10).unwrap();
9491 let ep = store.most_recalled_episode(&agent).unwrap().unwrap();
9492 assert_eq!(ep.id, id_high);
9493 }
9494
9495 #[test]
9496 fn test_episodic_store_most_recalled_episode_returns_none_for_unknown_agent() {
9497 let store = EpisodicStore::new();
9498 assert!(store.most_recalled_episode(&AgentId::new("nobody")).unwrap().is_none());
9499 }
9500
9501 #[test]
9502 fn test_working_memory_value_for_shortest_key_returns_correct_value() {
9503 let wm = WorkingMemory::new(10).unwrap();
9504 wm.set("abc", "long-key").unwrap();
9505 wm.set("a", "short-key").unwrap();
9506 wm.set("ab", "mid-key").unwrap();
9507 assert_eq!(wm.value_for_shortest_key().unwrap(), Some("short-key".to_string()));
9508 }
9509
9510 #[test]
9511 fn test_working_memory_value_for_shortest_key_returns_none_when_empty() {
9512 let wm = WorkingMemory::new(10).unwrap();
9513 assert_eq!(wm.value_for_shortest_key().unwrap(), None);
9514 }
9515
9516 #[test]
9519 fn test_episodic_store_episode_count_before_counts_older_episodes() {
9520 use chrono::{Duration, Utc};
9521 let store = EpisodicStore::new();
9522 let agent = AgentId::new("a");
9523 store.add_episode(agent.clone(), "old", 0.5).unwrap();
9524 store.add_episode(agent.clone(), "newer", 0.5).unwrap();
9525 let future = Utc::now() + Duration::hours(1);
9527 assert_eq!(store.episode_count_before(&agent, future).unwrap(), 2);
9528 let past = Utc::now() - Duration::hours(1);
9530 assert_eq!(store.episode_count_before(&agent, past).unwrap(), 0);
9531 }
9532
9533 #[test]
9534 fn test_episodic_store_episode_count_before_zero_for_unknown_agent() {
9535 use chrono::Utc;
9536 let store = EpisodicStore::new();
9537 let future = Utc::now();
9538 assert_eq!(store.episode_count_before(&AgentId::new("nobody"), future).unwrap(), 0);
9539 }
9540
9541 #[test]
9542 fn test_working_memory_entry_byte_pairs_returns_correct_lengths() {
9543 let wm = WorkingMemory::new(10).unwrap();
9544 wm.set("ab", "hello").unwrap();
9545 let pairs = wm.entry_byte_pairs().unwrap();
9546 assert_eq!(pairs.len(), 1);
9547 assert_eq!(pairs[0], (2, 5));
9548 }
9549
9550 #[test]
9551 fn test_working_memory_entry_byte_pairs_empty_for_empty_store() {
9552 let wm = WorkingMemory::new(10).unwrap();
9553 assert!(wm.entry_byte_pairs().unwrap().is_empty());
9554 }
9555
9556 #[test]
9557 fn test_working_memory_key_with_longest_value_returns_correct_key() {
9558 let wm = WorkingMemory::new(10).unwrap();
9559 wm.set("k1", "short").unwrap();
9560 wm.set("k2", "much longer value here").unwrap();
9561 assert_eq!(wm.key_with_longest_value().unwrap(), Some("k2".to_string()));
9562 }
9563
9564 #[test]
9565 fn test_working_memory_key_with_longest_value_returns_none_when_empty() {
9566 let wm = WorkingMemory::new(10).unwrap();
9567 assert_eq!(wm.key_with_longest_value().unwrap(), None);
9568 }
9569
9570 #[test]
9573 fn test_key_value_pairs_above_length_returns_matching_pairs() {
9574 let wm = WorkingMemory::new(100).unwrap();
9575 wm.set("short", "hi").unwrap();
9576 wm.set("long", "hello world").unwrap();
9577 let pairs = wm.key_value_pairs_above_length(5).unwrap();
9578 assert_eq!(pairs.len(), 1);
9579 assert_eq!(pairs[0].0, "long");
9580 assert_eq!(pairs[0].1, "hello world");
9581 }
9582
9583 #[test]
9584 fn test_key_value_pairs_above_length_empty_when_none_qualify() {
9585 let wm = WorkingMemory::new(100).unwrap();
9586 wm.set("a", "x").unwrap();
9587 assert!(wm.key_value_pairs_above_length(100).unwrap().is_empty());
9588 }
9589
9590 #[test]
9591 fn test_key_value_pairs_above_length_empty_for_empty_store() {
9592 let wm = WorkingMemory::new(100).unwrap();
9593 assert!(wm.key_value_pairs_above_length(0).unwrap().is_empty());
9594 }
9595
9596 #[test]
9599 fn test_episodic_store_episode_ids_returns_all_ids() {
9600 let store = EpisodicStore::new();
9601 let agent = AgentId::new("a");
9602 let id1 = store.add_episode(agent.clone(), "first", 0.5).unwrap();
9603 let id2 = store.add_episode(agent.clone(), "second", 0.5).unwrap();
9604 let mut ids = store.episode_ids(&agent).unwrap();
9605 ids.sort_unstable_by(|a, b| a.as_str().cmp(b.as_str()));
9606 assert!(ids.contains(&id1));
9607 assert!(ids.contains(&id2));
9608 assert_eq!(ids.len(), 2);
9609 }
9610
9611 #[test]
9612 fn test_episodic_store_episode_ids_empty_for_unknown_agent() {
9613 let store = EpisodicStore::new();
9614 assert!(store.episode_ids(&AgentId::new("nobody")).unwrap().is_empty());
9615 }
9616
9617 #[test]
9620 fn test_episode_count_all_agents_sums_across_agents() {
9621 let store = EpisodicStore::new();
9622 let a = AgentId::new("r45-cnt-a");
9623 let b = AgentId::new("r45-cnt-b");
9624 store.add_episode(a.clone(), "ep1", 0.5).unwrap();
9625 store.add_episode(a.clone(), "ep2", 0.5).unwrap();
9626 store.add_episode(b.clone(), "ep3", 0.5).unwrap();
9627 assert_eq!(store.episode_count_all_agents().unwrap(), 3);
9628 }
9629
9630 #[test]
9631 fn test_episode_count_all_agents_zero_for_empty_store() {
9632 let store = EpisodicStore::new();
9633 assert_eq!(store.episode_count_all_agents().unwrap(), 0);
9634 }
9635
9636 #[test]
9637 fn test_working_memory_increment_inserts_zero_when_absent() {
9638 let wm = WorkingMemory::new(100).unwrap();
9639 let v = wm.increment("counter-r45", 5).unwrap();
9640 assert_eq!(v, 5);
9641 assert_eq!(wm.get("counter-r45").unwrap().unwrap(), "5");
9642 }
9643
9644 #[test]
9645 fn test_working_memory_increment_adds_to_existing_value() {
9646 let wm = WorkingMemory::new(100).unwrap();
9647 wm.set("n-r45", "10").unwrap();
9648 let v = wm.increment("n-r45", 3).unwrap();
9649 assert_eq!(v, 13);
9650 }
9651
9652 #[test]
9653 fn test_working_memory_increment_returns_error_for_non_integer_value() {
9654 let wm = WorkingMemory::new(100).unwrap();
9655 wm.set("bad-r45", "not_a_number").unwrap();
9656 assert!(wm.increment("bad-r45", 1).is_err());
9657 }
9658
9659 #[test]
9660 fn test_working_memory_increment_negative_step() {
9661 let wm = WorkingMemory::new(100).unwrap();
9662 wm.set("n2-r45", "10").unwrap();
9663 let v = wm.increment("n2-r45", -4).unwrap();
9664 assert_eq!(v, 6);
9665 }
9666
9667 #[test]
9670 fn test_all_episode_ids_returns_ids_across_agents() {
9671 let store = EpisodicStore::new();
9672 let a = AgentId::new("r45-all-a");
9673 let b = AgentId::new("r45-all-b");
9674 let id1 = store.add_episode(a.clone(), "ep1", 0.5).unwrap();
9675 let id2 = store.add_episode(b.clone(), "ep2", 0.5).unwrap();
9676 let ids = store.all_episode_ids().unwrap();
9677 assert!(ids.contains(&id1));
9678 assert!(ids.contains(&id2));
9679 assert_eq!(ids.len(), 2);
9680 }
9681
9682 #[test]
9683 fn test_all_episode_ids_empty_for_empty_store() {
9684 let store = EpisodicStore::new();
9685 assert!(store.all_episode_ids().unwrap().is_empty());
9686 }
9687
9688 #[test]
9689 fn test_working_memory_non_empty_values_filters_empty() {
9690 let wm = WorkingMemory::new(100).unwrap();
9691 wm.set("k1-r45", "hello").unwrap();
9692 wm.set("k2-r45", "").unwrap();
9693 wm.set("k3-r45", "world").unwrap();
9694 let vals = wm.non_empty_values().unwrap();
9695 assert_eq!(vals.len(), 2);
9696 assert!(vals.contains(&"hello".to_string()));
9697 assert!(vals.contains(&"world".to_string()));
9698 }
9699
9700 #[test]
9701 fn test_working_memory_non_empty_values_empty_for_empty_store() {
9702 let wm = WorkingMemory::new(100).unwrap();
9703 assert!(wm.non_empty_values().unwrap().is_empty());
9704 }
9705
9706 #[test]
9709 fn test_episodic_store_episodes_above_importance_filters_correctly() {
9710 let store = EpisodicStore::new();
9711 let agent = AgentId::new("r46-a");
9712 store.add_episode(agent.clone(), "low", 0.3).unwrap();
9713 store.add_episode(agent.clone(), "high", 0.9).unwrap();
9714 let result = store.episodes_above_importance(&agent, 0.5).unwrap();
9715 assert_eq!(result.len(), 1);
9716 assert_eq!(result[0].content, "high");
9717 }
9718
9719 #[test]
9720 fn test_episodic_store_episodes_above_importance_empty_for_unknown_agent() {
9721 let store = EpisodicStore::new();
9722 assert!(store.episodes_above_importance(&AgentId::new("nobody-r46"), 0.5).unwrap().is_empty());
9723 }
9724
9725 #[test]
9726 fn test_working_memory_total_bytes_sums_key_and_value_lengths() {
9727 let wm = WorkingMemory::new(10).unwrap();
9728 wm.set("ab", "xyz").unwrap(); assert_eq!(wm.total_bytes().unwrap(), 5);
9730 }
9731
9732 #[test]
9733 fn test_working_memory_total_bytes_zero_for_empty_store() {
9734 let wm = WorkingMemory::new(10).unwrap();
9735 assert_eq!(wm.total_bytes().unwrap(), 0);
9736 }
9737
9738 #[test]
9741 fn test_episodic_store_episodes_between_returns_in_range() {
9742 use chrono::{Duration, Utc};
9743 let store = EpisodicStore::new();
9744 let agent = AgentId::new("r47-eb");
9745 store.add_episode(agent.clone(), "old", 0.5).unwrap();
9746 let past = Utc::now() - Duration::hours(1);
9747 let future = Utc::now() + Duration::hours(1);
9748 let result = store.episodes_between(&agent, past, future).unwrap();
9749 assert_eq!(result.len(), 1);
9750 }
9751
9752 #[test]
9753 fn test_episodic_store_episodes_between_empty_for_out_of_range() {
9754 use chrono::{Duration, Utc};
9755 let store = EpisodicStore::new();
9756 let agent = AgentId::new("r47-eb2");
9757 store.add_episode(agent.clone(), "ep", 0.5).unwrap();
9758 let far_past_start = Utc::now() - Duration::hours(10);
9759 let far_past_end = Utc::now() - Duration::hours(5);
9760 assert!(store.episodes_between(&agent, far_past_start, far_past_end).unwrap().is_empty());
9761 }
9762
9763 #[test]
9764 fn test_working_memory_max_key_bytes_returns_longest_key_len() {
9765 let wm = WorkingMemory::new(10).unwrap();
9766 wm.set("ab", "x").unwrap();
9767 wm.set("abcde", "y").unwrap();
9768 assert_eq!(wm.max_key_bytes().unwrap(), 5);
9769 }
9770
9771 #[test]
9772 fn test_working_memory_max_key_bytes_zero_for_empty_store() {
9773 let wm = WorkingMemory::new(10).unwrap();
9774 assert_eq!(wm.max_key_bytes().unwrap(), 0);
9775 }
9776
9777 #[test]
9778 fn test_working_memory_value_length_histogram_buckets_values() {
9779 let wm = WorkingMemory::new(10).unwrap();
9780 wm.set("k1", "ab").unwrap(); wm.set("k2", "abcde").unwrap(); wm.set("k3", "ab").unwrap(); let hist = wm.value_length_histogram(5).unwrap();
9785 assert!(!hist.is_empty());
9791 }
9792
9793 #[test]
9794 fn test_working_memory_value_length_histogram_empty_for_zero_bucket_size() {
9795 let wm = WorkingMemory::new(10).unwrap();
9796 wm.set("k", "v").unwrap();
9797 assert!(wm.value_length_histogram(0).unwrap().is_empty());
9798 }
9799
9800 #[test]
9803 fn test_episodic_store_weighted_importance_sum_sums_importances() {
9804 let store = EpisodicStore::new();
9805 let agent = AgentId::new("r48-wis");
9806 store.add_episode(agent.clone(), "ep1", 0.3).unwrap();
9807 store.add_episode(agent.clone(), "ep2", 0.7).unwrap();
9808 let sum = store.weighted_importance_sum(&agent).unwrap();
9809 assert!((sum - 1.0).abs() < 1e-5);
9810 }
9811
9812 #[test]
9813 fn test_episodic_store_weighted_importance_sum_zero_for_unknown_agent() {
9814 let store = EpisodicStore::new();
9815 let agent = AgentId::new("r48-wis-missing");
9816 assert_eq!(store.weighted_importance_sum(&agent).unwrap(), 0.0);
9817 }
9818
9819 #[test]
9820 fn test_episodic_store_episode_content_lengths_returns_lengths() {
9821 let store = EpisodicStore::new();
9822 let agent = AgentId::new("r48-ecl");
9823 store.add_episode(agent.clone(), "hi", 0.5).unwrap();
9824 store.add_episode(agent.clone(), "hello", 0.5).unwrap();
9825 let mut lengths = store.episode_content_lengths(&agent).unwrap();
9826 lengths.sort_unstable();
9827 assert_eq!(lengths, vec![2, 5]);
9828 }
9829
9830 #[test]
9831 fn test_episodic_store_episode_content_lengths_empty_for_unknown_agent() {
9832 let store = EpisodicStore::new();
9833 let agent = AgentId::new("r48-ecl-missing");
9834 assert!(store.episode_content_lengths(&agent).unwrap().is_empty());
9835 }
9836
9837 #[test]
9838 fn test_working_memory_semantic_key_count_counts_matching_keys() {
9839 let wm = WorkingMemory::new(10).unwrap();
9840 wm.set("intent_primary", "v1").unwrap();
9841 wm.set("intent_secondary", "v2").unwrap();
9842 wm.set("goal_main", "v3").unwrap();
9843 assert_eq!(wm.semantic_key_count("intent").unwrap(), 2);
9844 }
9845
9846 #[test]
9847 fn test_working_memory_semantic_key_count_zero_when_no_match() {
9848 let wm = WorkingMemory::new(10).unwrap();
9849 wm.set("context_a", "v").unwrap();
9850 assert_eq!(wm.semantic_key_count("intent").unwrap(), 0);
9851 }
9852
9853 #[test]
9856 fn test_episode_count_by_agent_maps_agents_to_counts() {
9857 let store = EpisodicStore::new();
9858 let a1 = AgentId::new("r49-ecba-1");
9859 let a2 = AgentId::new("r49-ecba-2");
9860 store.add_episode(a1.clone(), "ep1", 0.5).unwrap();
9861 store.add_episode(a1.clone(), "ep2", 0.5).unwrap();
9862 store.add_episode(a2.clone(), "ep3", 0.5).unwrap();
9863 let counts = store.episode_count_by_agent().unwrap();
9864 assert_eq!(*counts.get(&a1).unwrap(), 2);
9865 assert_eq!(*counts.get(&a2).unwrap(), 1);
9866 }
9867
9868 #[test]
9869 fn test_episode_count_by_agent_empty_for_empty_store() {
9870 let store = EpisodicStore::new();
9871 assert!(store.episode_count_by_agent().unwrap().is_empty());
9872 }
9873
9874 #[test]
9875 fn test_working_memory_longest_value_bytes_returns_max_value_len() {
9876 let wm = WorkingMemory::new(10).unwrap();
9877 wm.set("k1", "ab").unwrap();
9878 wm.set("k2", "abcde").unwrap();
9879 assert_eq!(wm.longest_value_bytes().unwrap(), 5);
9880 }
9881
9882 #[test]
9883 fn test_working_memory_longest_value_bytes_zero_for_empty_store() {
9884 let wm = WorkingMemory::new(10).unwrap();
9885 assert_eq!(wm.longest_value_bytes().unwrap(), 0);
9886 }
9887
9888 #[test]
9892 fn test_episodes_between_returns_episodes_in_range() {
9893 use chrono::{TimeZone, Utc};
9894 let store = EpisodicStore::new();
9895 let agent = AgentId::new("r47-eb-a");
9896 let t0 = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
9897 let t1 = Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap();
9898 let t2 = Utc.with_ymd_and_hms(2024, 1, 3, 0, 0, 0).unwrap();
9899 store.add_episode_at(agent.clone(), "early", 0.5, t0).unwrap();
9900 store.add_episode_at(agent.clone(), "mid", 0.5, t1).unwrap();
9901 store.add_episode_at(agent.clone(), "late", 0.5, t2).unwrap();
9902 let result = store.episodes_between(&agent, t0, t2).unwrap();
9904 assert_eq!(result.len(), 2);
9905 }
9906
9907 #[test]
9908 fn test_episodes_between_returns_empty_for_unknown_agent() {
9909 use chrono::{TimeZone, Utc};
9910 let store = EpisodicStore::new();
9911 let t0 = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
9912 let t1 = Utc.with_ymd_and_hms(2024, 1, 2, 0, 0, 0).unwrap();
9913 assert!(
9914 store
9915 .episodes_between(&AgentId::new("nobody-r47"), t0, t1)
9916 .unwrap()
9917 .is_empty()
9918 );
9919 }
9920
9921 #[test]
9922 fn test_working_memory_max_key_bytes_returns_longest_key_length() {
9923 let wm = WorkingMemory::new(10).unwrap();
9924 wm.set("ab", "v").unwrap(); wm.set("abcde", "v").unwrap(); assert_eq!(wm.max_key_bytes().unwrap(), 5);
9927 }
9928
9929 #[test]
9930 fn test_working_memory_max_key_bytes_zero_for_empty_store_r47() {
9931 let wm = WorkingMemory::new(10).unwrap();
9932 assert_eq!(wm.max_key_bytes().unwrap(), 0);
9933 }
9934
9935 #[test]
9936 fn test_working_memory_value_length_histogram_groups_by_bucket() {
9937 let wm = WorkingMemory::new(10).unwrap();
9938 wm.set("k1", "ab").unwrap(); wm.set("k2", "abcde").unwrap(); wm.set("k3", "abcdefghijk").unwrap(); let hist = wm.value_length_histogram(10).unwrap();
9942 assert_eq!(hist.len(), 2);
9944 assert_eq!(hist[0], (0, 2));
9945 assert_eq!(hist[1], (10, 1));
9946 }
9947
9948 #[test]
9949 fn test_working_memory_value_length_histogram_empty_for_zero_bucket_size_r47() {
9950 let wm = WorkingMemory::new(10).unwrap();
9951 wm.set("k", "v").unwrap();
9952 assert!(wm.value_length_histogram(0).unwrap().is_empty());
9953 }
9954
9955 #[test]
9956 fn test_episodes_tagged_with_all_returns_matching_episodes() {
9957 let store = EpisodicStore::new();
9958 let agent = AgentId::new("r47-twa-a");
9959 store
9960 .add_episode_with_tags(agent.clone(), "both", 0.5, vec!["x".into(), "y".into()])
9961 .unwrap();
9962 store
9963 .add_episode_with_tags(agent.clone(), "only_x", 0.5, vec!["x".into()])
9964 .unwrap();
9965 let result = store.episodes_tagged_with_all(&agent, &["x", "y"]).unwrap();
9966 assert_eq!(result.len(), 1);
9967 assert_eq!(result[0].content, "both");
9968 }
9969
9970 #[test]
9971 fn test_episodes_tagged_with_all_empty_tags_returns_empty() {
9972 let store = EpisodicStore::new();
9973 let agent = AgentId::new("r47-twa-b");
9974 store.add_episode(agent.clone(), "ep", 0.5).unwrap();
9975 assert!(store.episodes_tagged_with_all(&agent, &[]).unwrap().is_empty());
9976 }
9977
9978 #[test]
9979 fn test_content_word_count_total_sums_across_episodes() {
9980 let store = EpisodicStore::new();
9981 let agent = AgentId::new("r47-cwct-a");
9982 store.add_episode(agent.clone(), "one two three", 0.5).unwrap(); store.add_episode(agent.clone(), "four five", 0.5).unwrap(); assert_eq!(store.content_word_count_total(&agent).unwrap(), 5);
9985 }
9986
9987 #[test]
9988 fn test_content_word_count_total_zero_for_unknown_agent() {
9989 let store = EpisodicStore::new();
9990 assert_eq!(
9991 store
9992 .content_word_count_total(&AgentId::new("nobody-r47-cwct"))
9993 .unwrap(),
9994 0
9995 );
9996 }
9997
9998 #[test]
10002 fn test_agents_sorted_by_episode_count_most_episodes_first() {
10003 let store = EpisodicStore::new();
10004 let a = AgentId::new("r49-asbec-a");
10005 let b = AgentId::new("r49-asbec-b");
10006 store.add_episode(a.clone(), "e1", 0.5).unwrap();
10007 store.add_episode(b.clone(), "e2", 0.5).unwrap();
10008 store.add_episode(b.clone(), "e3", 0.5).unwrap();
10009 let sorted = store.agents_sorted_by_episode_count().unwrap();
10010 assert_eq!(sorted[0].as_str(), b.as_str());
10011 assert_eq!(sorted[1].as_str(), a.as_str());
10012 }
10013
10014 #[test]
10015 fn test_agents_sorted_by_episode_count_empty_for_empty_store() {
10016 let store = EpisodicStore::new();
10017 assert!(store.agents_sorted_by_episode_count().unwrap().is_empty());
10018 }
10019
10020 #[test]
10021 fn test_episodes_with_min_word_count_returns_qualifying_episodes() {
10022 let store = EpisodicStore::new();
10023 let agent = AgentId::new("r49-ewmwc-a");
10024 store.add_episode(agent.clone(), "one", 0.5).unwrap(); store.add_episode(agent.clone(), "one two three", 0.5).unwrap(); let result = store.episodes_with_min_word_count(&agent, 2).unwrap();
10027 assert_eq!(result.len(), 1);
10028 assert_eq!(result[0].content, "one two three");
10029 }
10030
10031 #[test]
10032 fn test_episodes_with_min_word_count_empty_for_unknown_agent() {
10033 let store = EpisodicStore::new();
10034 assert!(
10035 store
10036 .episodes_with_min_word_count(&AgentId::new("nobody-r49"), 1)
10037 .unwrap()
10038 .is_empty()
10039 );
10040 }
10041
10042 #[test]
10043 fn test_working_memory_values_sorted_returns_alphabetically_sorted_values() {
10044 let wm = WorkingMemory::new(10).unwrap();
10045 wm.set("k1", "banana").unwrap();
10046 wm.set("k2", "apple").unwrap();
10047 wm.set("k3", "cherry").unwrap();
10048 let sorted = wm.values_sorted().unwrap();
10049 assert_eq!(sorted, vec!["apple", "banana", "cherry"]);
10050 }
10051
10052 #[test]
10053 fn test_working_memory_values_sorted_empty_for_empty_store() {
10054 let wm = WorkingMemory::new(10).unwrap();
10055 assert!(wm.values_sorted().unwrap().is_empty());
10056 }
10057
10058 #[test]
10061 fn test_episodes_above_content_bytes_returns_long_episodes() {
10062 let store = EpisodicStore::new();
10063 let agent = AgentId::new("r50-eacb");
10064 store.add_episode(agent.clone(), "hi", 0.5).unwrap();
10065 store.add_episode(agent.clone(), "long content here", 0.5).unwrap();
10066 let result = store.episodes_above_content_bytes(&agent, 5).unwrap();
10067 assert_eq!(result.len(), 1);
10068 assert_eq!(result[0].content, "long content here");
10069 }
10070
10071 #[test]
10072 fn test_episodes_above_content_bytes_empty_for_unknown_agent() {
10073 let store = EpisodicStore::new();
10074 let agent = AgentId::new("r50-eacb-missing");
10075 assert!(store.episodes_above_content_bytes(&agent, 0).unwrap().is_empty());
10076 }
10077
10078 #[test]
10079 fn test_value_count_above_bytes_counts_long_values() {
10080 let wm = WorkingMemory::new(10).unwrap();
10081 wm.set("k1", "ab").unwrap();
10082 wm.set("k2", "abcdef").unwrap();
10083 assert_eq!(wm.value_count_above_bytes(3).unwrap(), 1);
10084 }
10085
10086 #[test]
10087 fn test_value_count_above_bytes_zero_for_empty_store() {
10088 let wm = WorkingMemory::new(10).unwrap();
10089 assert_eq!(wm.value_count_above_bytes(0).unwrap(), 0);
10090 }
10091
10092 #[test]
10095 fn test_most_important_episode_returns_highest_importance() {
10096 let store = EpisodicStore::new();
10097 let agent = AgentId::new("r47-imp-a");
10098 store.add_episode(agent.clone(), "low", 0.2).unwrap();
10099 store.add_episode(agent.clone(), "high", 0.9).unwrap();
10100 let result = store.most_important_episode(&agent).unwrap();
10101 assert!(result.is_some());
10102 assert_eq!(result.unwrap().content, "high");
10103 }
10104
10105 #[test]
10106 fn test_most_important_episode_returns_none_for_unknown_agent() {
10107 let store = EpisodicStore::new();
10108 assert!(store.most_important_episode(&AgentId::new("nobody-r47")).unwrap().is_none());
10109 }
10110
10111 #[test]
10112 fn test_count_keys_above_bytes_counts_long_keys() {
10113 let wm = WorkingMemory::new(100).unwrap();
10114 wm.set("ab", "v").unwrap(); wm.set("abcdef", "v").unwrap(); assert_eq!(wm.count_keys_above_bytes(3).unwrap(), 1);
10117 }
10118
10119 #[test]
10120 fn test_count_keys_above_bytes_zero_for_empty_store() {
10121 let wm = WorkingMemory::new(100).unwrap();
10122 assert_eq!(wm.count_keys_above_bytes(0).unwrap(), 0);
10123 }
10124
10125 #[test]
10129 fn test_most_tagged_episode_returns_episode_with_most_tags() {
10130 let store = EpisodicStore::new();
10131 let agent = AgentId::new("r50-mte-a");
10132 store
10133 .add_episode_with_tags(agent.clone(), "many", 0.5, vec!["a".into(), "b".into(), "c".into()])
10134 .unwrap();
10135 store
10136 .add_episode_with_tags(agent.clone(), "few", 0.5, vec!["a".into()])
10137 .unwrap();
10138 let result = store.most_tagged_episode(&agent).unwrap().unwrap();
10139 assert_eq!(result.content, "many");
10140 }
10141
10142 #[test]
10143 fn test_most_tagged_episode_none_for_unknown_agent() {
10144 let store = EpisodicStore::new();
10145 assert!(store.most_tagged_episode(&AgentId::new("nobody-r50")).unwrap().is_none());
10146 }
10147
10148 #[test]
10149 fn test_tag_frequency_counts_each_tag() {
10150 let store = EpisodicStore::new();
10151 let agent = AgentId::new("r50-tf-a");
10152 store
10153 .add_episode_with_tags(agent.clone(), "e1", 0.5, vec!["x".into(), "y".into()])
10154 .unwrap();
10155 store
10156 .add_episode_with_tags(agent.clone(), "e2", 0.5, vec!["x".into()])
10157 .unwrap();
10158 let freq = store.tag_frequency(&agent).unwrap();
10159 assert_eq!(freq["x"], 2);
10160 assert_eq!(freq["y"], 1);
10161 }
10162
10163 #[test]
10164 fn test_tag_frequency_empty_for_unknown_agent() {
10165 let store = EpisodicStore::new();
10166 assert!(store.tag_frequency(&AgentId::new("nobody-r50-tf")).unwrap().is_empty());
10167 }
10168
10169 #[test]
10172 fn test_unique_agents_count_reflects_distinct_agents() {
10173 let store = EpisodicStore::new();
10174 let a1 = AgentId::new("r51-uac-1");
10175 let a2 = AgentId::new("r51-uac-2");
10176 store.add_episode(a1.clone(), "ep", 0.5).unwrap();
10177 store.add_episode(a2.clone(), "ep", 0.5).unwrap();
10178 assert_eq!(store.unique_agents_count().unwrap(), 2);
10179 }
10180
10181 #[test]
10182 fn test_unique_agents_count_zero_for_empty_store() {
10183 let store = EpisodicStore::new();
10184 assert_eq!(store.unique_agents_count().unwrap(), 0);
10185 }
10186
10187 #[test]
10188 fn test_key_value_pairs_sorted_returns_alphabetical_pairs() {
10189 let wm = WorkingMemory::new(10).unwrap();
10190 wm.set("b_key", "v2").unwrap();
10191 wm.set("a_key", "v1").unwrap();
10192 let pairs = wm.key_value_pairs_sorted().unwrap();
10193 assert_eq!(pairs[0].0, "a_key");
10194 assert_eq!(pairs[1].0, "b_key");
10195 }
10196
10197 #[test]
10198 fn test_key_value_pairs_sorted_empty_for_empty_store() {
10199 let wm = WorkingMemory::new(10).unwrap();
10200 assert!(wm.key_value_pairs_sorted().unwrap().is_empty());
10201 }
10202
10203 #[test]
10206 fn test_episode_count_with_tag_counts_matching_episodes() {
10207 let store = EpisodicStore::new();
10208 let agent = AgentId::new("a1");
10209 store
10210 .add_episode_with_tags(agent.clone(), "content", 0.5, vec!["rust".into(), "ai".into()])
10211 .unwrap();
10212 store
10213 .add_episode_with_tags(agent.clone(), "other", 0.3, vec!["rust".into()])
10214 .unwrap();
10215 assert_eq!(store.episode_count_with_tag(&agent, "rust").unwrap(), 2);
10216 assert_eq!(store.episode_count_with_tag(&agent, "ai").unwrap(), 1);
10217 }
10218
10219 #[test]
10220 fn test_episode_count_with_tag_zero_for_unknown_agent() {
10221 let store = EpisodicStore::new();
10222 let agent = AgentId::new("unknown");
10223 assert_eq!(store.episode_count_with_tag(&agent, "rust").unwrap(), 0);
10224 }
10225
10226 #[test]
10227 fn test_has_duplicate_values_true_when_two_keys_share_value() {
10228 let wm = WorkingMemory::new(10).unwrap();
10229 wm.set("k1", "same").unwrap();
10230 wm.set("k2", "same").unwrap();
10231 assert!(wm.has_duplicate_values().unwrap());
10232 }
10233
10234 #[test]
10235 fn test_has_duplicate_values_false_for_unique_values() {
10236 let wm = WorkingMemory::new(10).unwrap();
10237 wm.set("k1", "one").unwrap();
10238 wm.set("k2", "two").unwrap();
10239 assert!(!wm.has_duplicate_values().unwrap());
10240 }
10241
10242 #[test]
10243 fn test_has_duplicate_values_false_for_empty_store() {
10244 let wm = WorkingMemory::new(10).unwrap();
10245 assert!(!wm.has_duplicate_values().unwrap());
10246 }
10247
10248 #[test]
10251 fn test_episodes_with_tag_count_counts_tagged_items() {
10252 let store = EpisodicStore::new();
10253 let agent = AgentId::new("r52-ewtc-1");
10254 store.add_episode(agent.clone(), "no tags episode", 0.5).unwrap();
10255 store
10256 .add_episode_with_tags(agent.clone(), "tagged content", 0.8, vec!["relevant".to_string()])
10257 .unwrap();
10258 assert_eq!(store.episodes_with_tag_count(&agent).unwrap(), 1);
10259 }
10260
10261 #[test]
10262 fn test_episodes_with_tag_count_zero_for_unknown_agent() {
10263 let store = EpisodicStore::new();
10264 let agent = AgentId::new("r52-ewtc-unknown");
10265 assert_eq!(store.episodes_with_tag_count(&agent).unwrap(), 0);
10266 }
10267
10268 #[test]
10269 fn test_has_key_starting_with_true_when_prefix_matches() {
10270 let wm = WorkingMemory::new(10).unwrap();
10271 wm.set("config_timeout", "30").unwrap();
10272 assert!(wm.has_key_starting_with("config_").unwrap());
10273 }
10274
10275 #[test]
10276 fn test_has_key_starting_with_false_when_no_match() {
10277 let wm = WorkingMemory::new(10).unwrap();
10278 wm.set("data_x", "v").unwrap();
10279 assert!(!wm.has_key_starting_with("config_").unwrap());
10280 }
10281
10282 #[test]
10283 fn test_has_key_starting_with_false_for_empty_store() {
10284 let wm = WorkingMemory::new(10).unwrap();
10285 assert!(!wm.has_key_starting_with("any").unwrap());
10286 }
10287
10288 #[test]
10291 fn test_newest_episode_returns_most_recent() {
10292 let store = EpisodicStore::new();
10293 let agent = AgentId::new("r53-ne-1");
10294 store.add_episode(agent.clone(), "first", 0.5).unwrap();
10295 store.add_episode(agent.clone(), "second", 0.7).unwrap();
10296 let newest = store.newest_episode(&agent).unwrap();
10297 assert!(newest.is_some());
10298 assert_eq!(newest.unwrap().content, "second");
10299 }
10300
10301 #[test]
10302 fn test_newest_episode_none_for_unknown_agent() {
10303 let store = EpisodicStore::new();
10304 let agent = AgentId::new("r53-ne-unknown");
10305 assert!(store.newest_episode(&agent).unwrap().is_none());
10306 }
10307
10308 #[test]
10309 fn test_value_bytes_total_sums_value_lengths() {
10310 let wm = WorkingMemory::new(10).unwrap();
10311 wm.set("k1", "abc").unwrap();
10312 wm.set("k2", "de").unwrap();
10313 assert_eq!(wm.value_bytes_total().unwrap(), 5);
10314 }
10315
10316 #[test]
10317 fn test_value_bytes_total_zero_for_empty_store() {
10318 let wm = WorkingMemory::new(10).unwrap();
10319 assert_eq!(wm.value_bytes_total().unwrap(), 0);
10320 }
10321
10322 #[test]
10323 fn test_all_keys_sorted_returns_alphabetical_keys() {
10324 let wm = WorkingMemory::new(10).unwrap();
10325 wm.set("zebra", "v").unwrap();
10326 wm.set("alpha", "v").unwrap();
10327 wm.set("mango", "v").unwrap();
10328 let keys = wm.all_keys_sorted().unwrap();
10329 assert_eq!(keys, vec!["alpha", "mango", "zebra"]);
10330 }
10331
10332 #[test]
10333 fn test_all_keys_sorted_empty_for_empty_store() {
10334 let wm = WorkingMemory::new(10).unwrap();
10335 assert!(wm.all_keys_sorted().unwrap().is_empty());
10336 }
10337
10338 #[test]
10341 fn test_shortest_value_returns_smallest_value() {
10342 let wm = WorkingMemory::new(10).unwrap();
10343 wm.set("k1", "hi").unwrap();
10344 wm.set("k2", "hello").unwrap();
10345 assert_eq!(wm.shortest_value().unwrap(), Some("hi".to_string()));
10346 }
10347
10348 #[test]
10349 fn test_shortest_value_none_for_empty_store() {
10350 let wm = WorkingMemory::new(10).unwrap();
10351 assert_eq!(wm.shortest_value().unwrap(), None);
10352 }
10353
10354 #[test]
10355 fn test_shortest_value_tie_broken_lexicographically() {
10356 let wm = WorkingMemory::new(10).unwrap();
10357 wm.set("k1", "bb").unwrap();
10358 wm.set("k2", "aa").unwrap();
10359 assert_eq!(wm.shortest_value().unwrap(), Some("aa".to_string()));
10360 }
10361
10362 #[test]
10365 fn test_key_count_starting_with_counts_matching_keys() {
10366 let wm = WorkingMemory::new(10).unwrap();
10367 wm.set("cfg_host", "localhost").unwrap();
10368 wm.set("cfg_port", "8080").unwrap();
10369 wm.set("debug_mode", "true").unwrap();
10370 assert_eq!(wm.key_count_starting_with("cfg_").unwrap(), 2);
10371 }
10372
10373 #[test]
10374 fn test_key_count_starting_with_zero_when_no_match() {
10375 let wm = WorkingMemory::new(10).unwrap();
10376 wm.set("alpha", "v").unwrap();
10377 assert_eq!(wm.key_count_starting_with("zzz").unwrap(), 0);
10378 }
10379
10380 #[test]
10381 fn test_key_count_starting_with_zero_for_empty_store() {
10382 let wm = WorkingMemory::new(10).unwrap();
10383 assert_eq!(wm.key_count_starting_with("any").unwrap(), 0);
10384 }
10385
10386 #[test]
10389 fn test_agent_ids_with_episodes_returns_sorted_ids() {
10390 let store = EpisodicStore::new();
10391 let a1 = AgentId::new("r54-z-agent");
10392 let a2 = AgentId::new("r54-a-agent");
10393 store.add_episode(a1.clone(), "ep", 0.5).unwrap();
10394 store.add_episode(a2.clone(), "ep", 0.5).unwrap();
10395 let ids = store.agent_ids_with_episodes().unwrap();
10396 assert_eq!(ids.len(), 2);
10397 assert_eq!(ids[0].as_str(), "r54-a-agent");
10398 assert_eq!(ids[1].as_str(), "r54-z-agent");
10399 }
10400
10401 #[test]
10402 fn test_agent_ids_with_episodes_empty_for_empty_store() {
10403 let store = EpisodicStore::new();
10404 assert!(store.agent_ids_with_episodes().unwrap().is_empty());
10405 }
10406
10407 #[test]
10408 fn test_episode_importance_avg_returns_mean() {
10409 let store = EpisodicStore::new();
10410 let agent = AgentId::new("r54-eia-1");
10411 store.add_episode(agent.clone(), "ep1", 0.5).unwrap();
10412 store.add_episode(agent.clone(), "ep2", 1.0).unwrap();
10413 let avg = store.episode_importance_avg(&agent).unwrap();
10414 assert!((avg - 0.75).abs() < 1e-5);
10415 }
10416
10417 #[test]
10418 fn test_episode_importance_avg_zero_for_unknown_agent() {
10419 let store = EpisodicStore::new();
10420 let agent = AgentId::new("r54-eia-unknown");
10421 assert_eq!(store.episode_importance_avg(&agent).unwrap(), 0.0);
10422 }
10423
10424 #[test]
10425 fn test_key_count_returns_number_of_entries() {
10426 let wm = WorkingMemory::new(10).unwrap();
10427 wm.set("a", "1").unwrap();
10428 wm.set("b", "2").unwrap();
10429 assert_eq!(wm.key_count().unwrap(), 2);
10430 }
10431
10432 #[test]
10433 fn test_key_count_zero_for_empty_store() {
10434 let wm = WorkingMemory::new(10).unwrap();
10435 assert_eq!(wm.key_count().unwrap(), 0);
10436 }
10437
10438 #[test]
10439 fn test_value_for_key_or_default_returns_value_when_present() {
10440 let wm = WorkingMemory::new(10).unwrap();
10441 wm.set("host", "localhost").unwrap();
10442 assert_eq!(wm.value_for_key_or_default("host", "unknown").unwrap(), "localhost");
10443 }
10444
10445 #[test]
10446 fn test_value_for_key_or_default_returns_default_when_absent() {
10447 let wm = WorkingMemory::new(10).unwrap();
10448 assert_eq!(wm.value_for_key_or_default("missing", "fallback").unwrap(), "fallback");
10449 }
10450
10451 #[test]
10454 fn test_agent_episode_counts_returns_correct_counts() {
10455 let store = EpisodicStore::new();
10456 let a1 = AgentId::new("r55-aec-1");
10457 let a2 = AgentId::new("r55-aec-2");
10458 store.add_episode(a1.clone(), "ep1", 0.5).unwrap();
10459 store.add_episode(a1.clone(), "ep2", 0.7).unwrap();
10460 store.add_episode(a2.clone(), "ep3", 0.3).unwrap();
10461 let counts = store.agent_episode_counts().unwrap();
10462 assert_eq!(counts.get(&a1).copied(), Some(2));
10463 assert_eq!(counts.get(&a2).copied(), Some(1));
10464 }
10465
10466 #[test]
10467 fn test_agent_episode_counts_empty_for_empty_store() {
10468 let store = EpisodicStore::new();
10469 assert!(store.agent_episode_counts().unwrap().is_empty());
10470 }
10471
10472 #[test]
10473 fn test_episodes_for_agent_sorted_by_importance_descending() {
10474 let store = EpisodicStore::new();
10475 let agent = AgentId::new("r55-esbi-1");
10476 store.add_episode(agent.clone(), "low", 0.2).unwrap();
10477 store.add_episode(agent.clone(), "high", 0.9).unwrap();
10478 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
10479 let sorted = store.episodes_for_agent_sorted_by_importance(&agent).unwrap();
10480 assert_eq!(sorted[0].importance, 0.9);
10481 assert_eq!(sorted[2].importance, 0.2);
10482 }
10483
10484 #[test]
10485 fn test_entries_with_key_prefix_returns_matching_pairs() {
10486 let wm = WorkingMemory::new(10).unwrap();
10487 wm.set("cfg_host", "localhost").unwrap();
10488 wm.set("cfg_port", "8080").unwrap();
10489 wm.set("debug_mode", "true").unwrap();
10490 let entries = wm.entries_with_key_prefix("cfg_").unwrap();
10491 assert_eq!(entries.len(), 2);
10492 assert_eq!(entries[0].0, "cfg_host");
10493 assert_eq!(entries[1].0, "cfg_port");
10494 }
10495
10496 #[test]
10497 fn test_entries_with_key_prefix_empty_when_no_match() {
10498 let wm = WorkingMemory::new(10).unwrap();
10499 wm.set("data_x", "v").unwrap();
10500 assert!(wm.entries_with_key_prefix("cfg_").unwrap().is_empty());
10501 }
10502
10503 #[test]
10506 fn test_episodes_below_importance_filters_correctly() {
10507 let store = EpisodicStore::new();
10508 let agent = AgentId::new("a1");
10509 store.add_episode(agent.clone(), "low", 0.2).unwrap();
10510 store.add_episode(agent.clone(), "high", 0.9).unwrap();
10511 let low = store.episodes_below_importance(&agent, 0.5).unwrap();
10512 assert_eq!(low.len(), 1);
10513 assert_eq!(low[0].content, "low");
10514 }
10515
10516 #[test]
10517 fn test_episodes_below_importance_empty_for_unknown_agent() {
10518 let store = EpisodicStore::new();
10519 let agent = AgentId::new("unknown");
10520 assert!(store.episodes_below_importance(&agent, 0.5).unwrap().is_empty());
10521 }
10522
10523 #[test]
10524 fn test_keys_with_value_equal_to_returns_correct_keys() {
10525 let wm = WorkingMemory::new(10).unwrap();
10526 wm.set("k1", "hello").unwrap();
10527 wm.set("k2", "world").unwrap();
10528 wm.set("k3", "hello").unwrap();
10529 let mut keys = wm.keys_with_value_equal_to("hello").unwrap();
10530 keys.sort();
10531 assert_eq!(keys, vec!["k1", "k3"]);
10532 }
10533
10534 #[test]
10535 fn test_keys_with_value_equal_to_empty_when_no_match() {
10536 let wm = WorkingMemory::new(10).unwrap();
10537 wm.set("k1", "hello").unwrap();
10538 assert!(wm.keys_with_value_equal_to("world").unwrap().is_empty());
10539 }
10540
10541 #[test]
10544 fn test_all_values_sorted_returns_sorted_values() {
10545 let wm = WorkingMemory::new(10).unwrap();
10546 wm.set("k1", "banana").unwrap();
10547 wm.set("k2", "apple").unwrap();
10548 wm.set("k3", "cherry").unwrap();
10549 let values = wm.all_values_sorted().unwrap();
10550 assert_eq!(values, vec!["apple", "banana", "cherry"]);
10551 }
10552
10553 #[test]
10554 fn test_all_values_sorted_empty_for_empty_store() {
10555 let wm = WorkingMemory::new(10).unwrap();
10556 assert!(wm.all_values_sorted().unwrap().is_empty());
10557 }
10558
10559 #[test]
10560 fn test_keys_with_value_prefix_returns_matching_keys() {
10561 let wm = WorkingMemory::new(10).unwrap();
10562 wm.set("host", "http://example.com").unwrap();
10563 wm.set("api", "http://api.example.com").unwrap();
10564 wm.set("name", "alice").unwrap();
10565 let mut keys = wm.keys_with_value_prefix("http://").unwrap();
10566 keys.sort();
10567 assert_eq!(keys, vec!["api", "host"]);
10568 }
10569
10570 #[test]
10571 fn test_keys_with_value_prefix_empty_when_no_match() {
10572 let wm = WorkingMemory::new(10).unwrap();
10573 wm.set("k1", "hello").unwrap();
10574 assert!(wm.keys_with_value_prefix("zzz").unwrap().is_empty());
10575 }
10576
10577 #[test]
10578 fn test_agents_with_episodes_above_count_returns_high_activity_agents() {
10579 let store = EpisodicStore::new();
10580 let a1 = AgentId::new("agent-high");
10581 let a2 = AgentId::new("agent-low");
10582 store.add_episode(a1.clone(), "e1", 0.5).unwrap();
10583 store.add_episode(a1.clone(), "e2", 0.5).unwrap();
10584 store.add_episode(a1.clone(), "e3", 0.5).unwrap();
10585 store.add_episode(a2.clone(), "e1", 0.5).unwrap();
10586 let above = store.agents_with_episodes_above_count(2).unwrap();
10587 assert_eq!(above.len(), 1);
10588 assert_eq!(above[0], a1);
10589 }
10590
10591 #[test]
10592 fn test_agents_with_episodes_above_count_empty_when_none_qualify() {
10593 let store = EpisodicStore::new();
10594 let agent = AgentId::new("a");
10595 store.add_episode(agent.clone(), "e1", 0.5).unwrap();
10596 assert!(store.agents_with_episodes_above_count(5).unwrap().is_empty());
10597 }
10598
10599 #[test]
10602 fn test_episodes_matching_content_returns_matching_episodes() {
10603 let store = EpisodicStore::new();
10604 let agent = AgentId::new("r56-emc-1");
10605 store.add_episode(agent.clone(), "user asked about Rust", 0.5).unwrap();
10606 store.add_episode(agent.clone(), "user asked about Python", 0.5).unwrap();
10607 let matched = store.episodes_matching_content(&agent, "Rust").unwrap();
10608 assert_eq!(matched.len(), 1);
10609 assert!(matched[0].content.contains("Rust"));
10610 }
10611
10612 #[test]
10613 fn test_episodes_matching_content_empty_for_no_match() {
10614 let store = EpisodicStore::new();
10615 let agent = AgentId::new("r56-emc-2");
10616 store.add_episode(agent.clone(), "something else", 0.5).unwrap();
10617 assert!(store.episodes_matching_content(&agent, "Rust").unwrap().is_empty());
10618 }
10619
10620 #[test]
10621 fn test_top_agent_by_importance_returns_highest_total_agent() {
10622 let store = EpisodicStore::new();
10623 let a1 = AgentId::new("r56-tabi-low");
10624 let a2 = AgentId::new("r56-tabi-high");
10625 store.add_episode(a1.clone(), "ep", 0.2).unwrap();
10626 store.add_episode(a2.clone(), "ep", 0.9).unwrap();
10627 let top = store.top_agent_by_importance().unwrap();
10628 assert_eq!(top.unwrap().as_str(), "r56-tabi-high");
10629 }
10630
10631 #[test]
10632 fn test_top_agent_by_importance_none_for_empty_store() {
10633 let store = EpisodicStore::new();
10634 assert!(store.top_agent_by_importance().unwrap().is_none());
10635 }
10636
10637 #[test]
10638 fn test_has_value_equal_to_true_when_match_exists() {
10639 let wm = WorkingMemory::new(10).unwrap();
10640 wm.set("key", "exact_value").unwrap();
10641 assert!(wm.has_value_equal_to("exact_value").unwrap());
10642 }
10643
10644 #[test]
10645 fn test_has_value_equal_to_false_when_no_match() {
10646 let wm = WorkingMemory::new(10).unwrap();
10647 wm.set("key", "other").unwrap();
10648 assert!(!wm.has_value_equal_to("exact_value").unwrap());
10649 }
10650
10651 #[test]
10652 fn test_value_contains_true_when_substr_in_value() {
10653 let wm = WorkingMemory::new(10).unwrap();
10654 wm.set("url", "https://example.com/path").unwrap();
10655 assert!(wm.value_contains("url", "example.com").unwrap());
10656 }
10657
10658 #[test]
10659 fn test_value_contains_false_when_key_absent() {
10660 let wm = WorkingMemory::new(10).unwrap();
10661 assert!(!wm.value_contains("missing", "anything").unwrap());
10662 }
10663
10664 #[test]
10667 fn test_avg_episode_importance_returns_mean() {
10668 let store = EpisodicStore::new();
10669 let agent = AgentId::new("r50-avg-1");
10670 store.add_episode(agent.clone(), "e1", 0.4).unwrap();
10671 store.add_episode(agent.clone(), "e2", 0.8).unwrap();
10672 let avg = store.avg_episode_importance(&agent).unwrap();
10673 assert!((avg - 0.6).abs() < 1e-6);
10674 }
10675
10676 #[test]
10677 fn test_avg_episode_importance_zero_for_unknown_agent() {
10678 let store = EpisodicStore::new();
10679 let agent = AgentId::new("r50-avg-unknown");
10680 assert_eq!(store.avg_episode_importance(&agent).unwrap(), 0.0);
10681 }
10682
10683 #[test]
10684 fn test_episode_content_bytes_total_correct() {
10685 let store = EpisodicStore::new();
10686 let agent = AgentId::new("r50-bytes-1");
10687 store.add_episode(agent.clone(), "hello", 0.5).unwrap();
10688 store.add_episode(agent.clone(), "world!", 0.5).unwrap();
10689 let total = store.episode_content_bytes_total(&agent).unwrap();
10690 assert_eq!(total, 11); }
10692
10693 #[test]
10694 fn test_episode_content_bytes_total_zero_for_unknown_agent() {
10695 let store = EpisodicStore::new();
10696 let agent = AgentId::new("r50-bytes-unknown");
10697 assert_eq!(store.episode_content_bytes_total(&agent).unwrap(), 0);
10698 }
10699
10700 #[test]
10703 fn test_episodes_after_timestamp_returns_newer_episodes() {
10704 use chrono::{Duration, Utc};
10705 let store = EpisodicStore::new();
10706 let agent = AgentId::new("r57-eat-1");
10707 store.add_episode(agent.clone(), "old", 0.5).unwrap();
10708 let cutoff = Utc::now();
10709 store.add_episode(agent.clone(), "new", 0.7).unwrap();
10710 let after = store.episodes_after_timestamp(&agent, cutoff).unwrap();
10711 assert_eq!(after.len(), 1);
10712 assert_eq!(after[0].content, "new");
10713 let _ = Duration::seconds(0); }
10715
10716 #[test]
10717 fn test_agent_with_min_importance_avg_returns_lowest() {
10718 let store = EpisodicStore::new();
10719 let a1 = AgentId::new("r57-awmia-hi");
10720 let a2 = AgentId::new("r57-awmia-lo");
10721 store.add_episode(a1.clone(), "ep", 0.9).unwrap();
10722 store.add_episode(a2.clone(), "ep", 0.1).unwrap();
10723 let min_agent = store.agent_with_min_importance_avg().unwrap();
10724 assert_eq!(min_agent.unwrap().as_str(), "r57-awmia-lo");
10725 }
10726
10727 #[test]
10728 fn test_agent_with_min_importance_avg_none_for_empty_store() {
10729 let store = EpisodicStore::new();
10730 assert!(store.agent_with_min_importance_avg().unwrap().is_none());
10731 }
10732
10733 #[test]
10734 fn test_keys_without_prefix_returns_non_matching_keys() {
10735 let wm = WorkingMemory::new(10).unwrap();
10736 wm.set("cfg_host", "v").unwrap();
10737 wm.set("data_x", "v").unwrap();
10738 wm.set("data_y", "v").unwrap();
10739 let keys = wm.keys_without_prefix("cfg_").unwrap();
10740 assert_eq!(keys, vec!["data_x", "data_y"]);
10741 }
10742
10743 #[test]
10744 fn test_keys_without_prefix_empty_when_all_match() {
10745 let wm = WorkingMemory::new(10).unwrap();
10746 wm.set("cfg_a", "v").unwrap();
10747 assert!(wm.keys_without_prefix("cfg_").unwrap().is_empty());
10748 }
10749
10750 #[test]
10751 fn test_count_values_equal_to_returns_correct_count() {
10752 let wm = WorkingMemory::new(10).unwrap();
10753 wm.set("k1", "same").unwrap();
10754 wm.set("k2", "same").unwrap();
10755 wm.set("k3", "other").unwrap();
10756 assert_eq!(wm.count_values_equal_to("same").unwrap(), 2);
10757 }
10758
10759 #[test]
10760 fn test_count_values_equal_to_zero_for_no_match() {
10761 let wm = WorkingMemory::new(10).unwrap();
10762 wm.set("k", "v").unwrap();
10763 assert_eq!(wm.count_values_equal_to("missing").unwrap(), 0);
10764 }
10765
10766 #[test]
10769 fn test_has_episodes_for_agent_true_after_adding_episode() {
10770 let store = EpisodicStore::new();
10771 let agent = AgentId::new("agent-r57");
10772 store.add_episode_with_tags(agent.clone(), "content", 0.5_f32, vec![]).unwrap();
10773 assert!(store.has_episodes_for_agent(&agent).unwrap());
10774 }
10775
10776 #[test]
10777 fn test_has_episodes_for_agent_false_for_unknown_agent() {
10778 let store = EpisodicStore::new();
10779 let agent = AgentId::new("nobody-r57");
10780 assert!(!store.has_episodes_for_agent(&agent).unwrap());
10781 }
10782
10783 #[test]
10784 fn test_all_values_non_empty_true_for_non_empty_values() {
10785 let wm = WorkingMemory::new(10).unwrap();
10786 wm.set("a", "hello").unwrap();
10787 wm.set("b", "world").unwrap();
10788 assert!(wm.all_values_non_empty().unwrap());
10789 }
10790
10791 #[test]
10792 fn test_all_values_non_empty_false_when_empty_value_present() {
10793 let wm = WorkingMemory::new(10).unwrap();
10794 wm.set("a", "hello").unwrap();
10795 wm.set("b", "").unwrap();
10796 assert!(!wm.all_values_non_empty().unwrap());
10797 }
10798
10799 #[test]
10800 fn test_all_values_non_empty_true_for_empty_store() {
10801 let wm = WorkingMemory::new(10).unwrap();
10802 assert!(wm.all_values_non_empty().unwrap());
10803 }
10804
10805 #[test]
10808 fn test_highest_importance_episode_returns_max() {
10809 let store = EpisodicStore::new();
10810 let agent = AgentId::new("r58-hie-1");
10811 store.add_episode(agent.clone(), "low", 0.2).unwrap();
10812 store.add_episode(agent.clone(), "high", 0.9).unwrap();
10813 let top = store.highest_importance_episode(&agent).unwrap();
10814 assert_eq!(top.unwrap().importance, 0.9);
10815 }
10816
10817 #[test]
10818 fn test_highest_importance_episode_none_for_empty_agent() {
10819 let store = EpisodicStore::new();
10820 let agent = AgentId::new("r58-hie-unknown");
10821 assert!(store.highest_importance_episode(&agent).unwrap().is_none());
10822 }
10823
10824 #[test]
10825 fn test_avg_episode_content_words_returns_mean() {
10826 let store = EpisodicStore::new();
10827 let agent = AgentId::new("r58-aecw-1");
10828 store.add_episode(agent.clone(), "one two", 0.5).unwrap();
10829 store.add_episode(agent.clone(), "a b c d", 0.5).unwrap();
10830 let avg = store.avg_episode_content_words(&agent).unwrap();
10831 assert_eq!(avg, 3.0);
10832 }
10833
10834 #[test]
10835 fn test_has_duplicate_content_true_when_duplicates_exist() {
10836 let store = EpisodicStore::new();
10837 let agent = AgentId::new("r58-hdc-1");
10838 store.add_episode(agent.clone(), "same content", 0.5).unwrap();
10839 store.add_episode(agent.clone(), "same content", 0.7).unwrap();
10840 assert!(store.has_duplicate_content(&agent).unwrap());
10841 }
10842
10843 #[test]
10844 fn test_has_duplicate_content_false_when_all_unique() {
10845 let store = EpisodicStore::new();
10846 let agent = AgentId::new("r58-hdc-2");
10847 store.add_episode(agent.clone(), "first", 0.5).unwrap();
10848 store.add_episode(agent.clone(), "second", 0.5).unwrap();
10849 assert!(!store.has_duplicate_content(&agent).unwrap());
10850 }
10851
10852 #[test]
10853 fn test_value_starts_with_true_when_value_has_prefix() {
10854 let wm = WorkingMemory::new(10).unwrap();
10855 wm.set("url", "https://example.com").unwrap();
10856 assert!(wm.value_starts_with("url", "https://").unwrap());
10857 }
10858
10859 #[test]
10860 fn test_value_starts_with_false_when_key_absent() {
10861 let wm = WorkingMemory::new(10).unwrap();
10862 assert!(!wm.value_starts_with("missing", "https://").unwrap());
10863 }
10864
10865 #[test]
10868 fn test_value_with_max_bytes_returns_longest_value() {
10869 let wm = WorkingMemory::new(10).unwrap();
10870 wm.set("k1", "hi").unwrap();
10871 wm.set("k2", "hello world").unwrap();
10872 wm.set("k3", "ok").unwrap();
10873 assert_eq!(wm.value_with_max_bytes().unwrap(), Some("hello world".to_string()));
10874 }
10875
10876 #[test]
10877 fn test_value_with_max_bytes_none_for_empty_store() {
10878 let wm = WorkingMemory::new(10).unwrap();
10879 assert_eq!(wm.value_with_max_bytes().unwrap(), None);
10880 }
10881
10882 #[test]
10885 fn test_values_longer_than_returns_matching_keys() {
10886 let wm = WorkingMemory::new(10).unwrap();
10887 wm.set("short", "hi").unwrap();
10888 wm.set("long", "hello world").unwrap();
10889 let keys = wm.values_longer_than(4).unwrap();
10890 assert_eq!(keys, vec!["long".to_string()]);
10891 }
10892
10893 #[test]
10894 fn test_values_longer_than_empty_when_none_qualify() {
10895 let wm = WorkingMemory::new(10).unwrap();
10896 wm.set("k", "ab").unwrap();
10897 let keys = wm.values_longer_than(100).unwrap();
10898 assert!(keys.is_empty());
10899 }
10900
10901 #[test]
10902 fn test_has_key_with_prefix_true() {
10903 let wm = WorkingMemory::new(10).unwrap();
10904 wm.set("agent:config", "val").unwrap();
10905 assert!(wm.has_key_with_prefix("agent:").unwrap());
10906 }
10907
10908 #[test]
10909 fn test_has_key_with_prefix_false() {
10910 let wm = WorkingMemory::new(10).unwrap();
10911 wm.set("other:config", "val").unwrap();
10912 assert!(!wm.has_key_with_prefix("agent:").unwrap());
10913 }
10914
10915 #[test]
10916 fn test_min_episode_importance_returns_minimum() {
10917 let store = EpisodicStore::new();
10918 let agent = AgentId::new("r59-min-imp");
10919 store.add_episode(agent.clone(), "first", 0.8).unwrap();
10920 store.add_episode(agent.clone(), "second", 0.2).unwrap();
10921 store.add_episode(agent.clone(), "third", 0.5).unwrap();
10922 let min = store.min_episode_importance(&agent).unwrap();
10923 assert!((min.unwrap() - 0.2_f32).abs() < 1e-6);
10924 }
10925
10926 #[test]
10927 fn test_min_episode_importance_none_for_unknown_agent() {
10928 let store = EpisodicStore::new();
10929 let agent = AgentId::new("r59-min-unknown");
10930 assert!(store.min_episode_importance(&agent).unwrap().is_none());
10931 }
10932
10933 #[test]
10936 fn test_value_count_matching_prefix_correct() {
10937 let wm = WorkingMemory::new(10).unwrap();
10938 wm.set("k1", "hello world").unwrap();
10939 wm.set("k2", "hello there").unwrap();
10940 wm.set("k3", "goodbye").unwrap();
10941 assert_eq!(wm.value_count_matching_prefix("hello").unwrap(), 2);
10942 }
10943
10944 #[test]
10945 fn test_value_count_matching_prefix_zero_when_none_match() {
10946 let wm = WorkingMemory::new(10).unwrap();
10947 wm.set("k1", "nope").unwrap();
10948 assert_eq!(wm.value_count_matching_prefix("yes").unwrap(), 0);
10949 }
10950
10951 #[test]
10952 fn test_count_episodes_in_window_correct() {
10953 use chrono::Utc;
10954 let store = EpisodicStore::new();
10955 let agent = AgentId::new("r60-window");
10956 store.add_episode(agent.clone(), "ep1", 0.5).unwrap();
10957 store.add_episode(agent.clone(), "ep2", 0.5).unwrap();
10958 let start = Utc::now() - chrono::Duration::hours(1);
10960 let end = Utc::now() + chrono::Duration::hours(1);
10961 let count = store.count_episodes_in_window(&agent, start, end).unwrap();
10962 assert_eq!(count, 2);
10963 }
10964
10965 #[test]
10966 fn test_count_episodes_in_window_zero_for_future_window() {
10967 use chrono::Utc;
10968 let store = EpisodicStore::new();
10969 let agent = AgentId::new("r60-future");
10970 store.add_episode(agent.clone(), "ep", 0.5).unwrap();
10971 let start = Utc::now() + chrono::Duration::hours(1);
10972 let end = Utc::now() + chrono::Duration::hours(2);
10973 let count = store.count_episodes_in_window(&agent, start, end).unwrap();
10974 assert_eq!(count, 0);
10975 }
10976
10977 #[test]
10980 fn test_total_tag_count_sums_tags_across_episodes() {
10981 let store = EpisodicStore::new();
10982 let agent = AgentId::new("r61-tags");
10983 store
10984 .add_episode_with_tags(agent.clone(), "ep1", 0.5, vec!["a".to_string(), "b".to_string()])
10985 .unwrap();
10986 store
10987 .add_episode_with_tags(agent.clone(), "ep2", 0.5, vec!["c".to_string()])
10988 .unwrap();
10989 assert_eq!(store.total_tag_count(&agent).unwrap(), 3);
10990 }
10991
10992 #[test]
10993 fn test_total_tag_count_zero_for_unknown_agent() {
10994 let store = EpisodicStore::new();
10995 let agent = AgentId::new("r61-tags-unknown");
10996 assert_eq!(store.total_tag_count(&agent).unwrap(), 0);
10997 }
10998
10999 #[test]
11000 fn test_avg_tag_count_per_episode_correct() {
11001 let store = EpisodicStore::new();
11002 let agent = AgentId::new("r61-avg-tags");
11003 store
11004 .add_episode_with_tags(agent.clone(), "e1", 0.5, vec!["x".to_string(), "y".to_string()])
11005 .unwrap();
11006 store
11007 .add_episode_with_tags(agent.clone(), "e2", 0.5, vec![])
11008 .unwrap();
11009 assert!((store.avg_tag_count_per_episode(&agent).unwrap() - 1.0).abs() < 1e-9);
11011 }
11012
11013 #[test]
11014 fn test_avg_tag_count_per_episode_zero_for_unknown_agent() {
11015 let store = EpisodicStore::new();
11016 let agent = AgentId::new("r61-avg-tags-unknown");
11017 assert_eq!(store.avg_tag_count_per_episode(&agent).unwrap(), 0.0);
11018 }
11019
11020 #[test]
11021 fn test_keys_with_suffix_returns_matching_keys() {
11022 let wm = WorkingMemory::new(10).unwrap();
11023 wm.set("config.json", "data").unwrap();
11024 wm.set("readme.md", "doc").unwrap();
11025 wm.set("schema.json", "meta").unwrap();
11026 let keys = wm.keys_with_suffix(".json").unwrap();
11027 assert_eq!(keys, vec!["config.json".to_string(), "schema.json".to_string()]);
11028 }
11029
11030 #[test]
11031 fn test_keys_with_suffix_empty_when_none_match() {
11032 let wm = WorkingMemory::new(10).unwrap();
11033 wm.set("hello", "world").unwrap();
11034 let keys = wm.keys_with_suffix(".json").unwrap();
11035 assert!(keys.is_empty());
11036 }
11037
11038 #[test]
11039 fn test_avg_key_length_correct() {
11040 let wm = WorkingMemory::new(10).unwrap();
11041 wm.set("ab", "v").unwrap(); wm.set("abcd", "v").unwrap(); let avg = wm.avg_key_length().unwrap();
11044 assert!((avg - 3.0).abs() < 1e-9);
11045 }
11046
11047 #[test]
11048 fn test_avg_key_length_zero_for_empty_store() {
11049 let wm = WorkingMemory::new(10).unwrap();
11050 assert_eq!(wm.avg_key_length().unwrap(), 0.0);
11051 }
11052
11053 #[test]
11056 fn test_episodes_above_importance_count_correct() {
11057 let store = EpisodicStore::new();
11058 let agent = AgentId::new("r62-above");
11059 store.add_episode(agent.clone(), "e1", 0.8).unwrap();
11060 store.add_episode(agent.clone(), "e2", 0.3).unwrap();
11061 store.add_episode(agent.clone(), "e3", 0.6).unwrap();
11062 assert_eq!(store.episodes_above_importance_count(&agent, 0.5).unwrap(), 2);
11063 }
11064
11065 #[test]
11066 fn test_episodes_above_importance_count_zero_for_unknown_agent() {
11067 let store = EpisodicStore::new();
11068 let agent = AgentId::new("r62-above-unknown");
11069 assert_eq!(store.episodes_above_importance_count(&agent, 0.5).unwrap(), 0);
11070 }
11071
11072 #[test]
11073 fn test_tag_union_collects_all_unique_tags() {
11074 let store = EpisodicStore::new();
11075 let agent = AgentId::new("r62-union");
11076 store
11077 .add_episode_with_tags(agent.clone(), "e1", 0.5, vec!["a".to_string(), "b".to_string()])
11078 .unwrap();
11079 store
11080 .add_episode_with_tags(agent.clone(), "e2", 0.5, vec!["b".to_string(), "c".to_string()])
11081 .unwrap();
11082 let union = store.tag_union(&agent).unwrap();
11083 assert_eq!(union.len(), 3);
11084 assert!(union.contains("a"));
11085 assert!(union.contains("b"));
11086 assert!(union.contains("c"));
11087 }
11088
11089 #[test]
11090 fn test_tag_union_empty_for_unknown_agent() {
11091 let store = EpisodicStore::new();
11092 let agent = AgentId::new("r62-union-unknown");
11093 assert!(store.tag_union(&agent).unwrap().is_empty());
11094 }
11095
11096 #[test]
11097 fn test_entries_sorted_by_key_length_orders_by_length_then_alpha() {
11098 let wm = WorkingMemory::new(10).unwrap();
11099 wm.set("cc", "v").unwrap();
11100 wm.set("a", "v").unwrap();
11101 wm.set("bbb", "v").unwrap();
11102 let pairs = wm.entries_sorted_by_key_length().unwrap();
11103 let keys: Vec<&str> = pairs.iter().map(|(k, _)| k.as_str()).collect();
11104 assert_eq!(keys, vec!["a", "cc", "bbb"]);
11105 }
11106
11107 #[test]
11108 fn test_entries_sorted_by_key_length_empty_for_empty_store() {
11109 let wm = WorkingMemory::new(10).unwrap();
11110 assert!(wm.entries_sorted_by_key_length().unwrap().is_empty());
11111 }
11112
11113 #[test]
11116 fn test_episode_most_recent_returns_newest() {
11117 use std::thread::sleep;
11118 use std::time::Duration;
11119 let store = EpisodicStore::new();
11120 let agent = AgentId::new("r63-recent");
11121 store.add_episode(agent.clone(), "first", 0.5).unwrap();
11122 sleep(Duration::from_millis(2));
11123 store.add_episode(agent.clone(), "latest", 0.8).unwrap();
11124 let recent = store.episode_most_recent(&agent).unwrap();
11125 assert_eq!(recent.map(|m| m.content), Some("latest".to_string()));
11126 }
11127
11128 #[test]
11129 fn test_episode_most_recent_none_for_unknown_agent() {
11130 let store = EpisodicStore::new();
11131 let agent = AgentId::new("r63-recent-unknown");
11132 assert!(store.episode_most_recent(&agent).unwrap().is_none());
11133 }
11134
11135 #[test]
11136 fn test_episodes_by_importance_range_correct() {
11137 let store = EpisodicStore::new();
11138 let agent = AgentId::new("r63-range");
11139 store.add_episode(agent.clone(), "low", 0.1).unwrap();
11140 store.add_episode(agent.clone(), "mid", 0.5).unwrap();
11141 store.add_episode(agent.clone(), "high", 0.9).unwrap();
11142 let range = store.episodes_by_importance_range(&agent, 0.3, 0.7).unwrap();
11143 assert_eq!(range.len(), 1);
11144 assert_eq!(range[0].content, "mid");
11145 }
11146
11147 #[test]
11148 fn test_episodes_by_importance_range_empty_for_unknown_agent() {
11149 let store = EpisodicStore::new();
11150 let agent = AgentId::new("r63-range-unknown");
11151 assert!(store.episodes_by_importance_range(&agent, 0.0, 1.0).unwrap().is_empty());
11152 }
11153
11154 #[test]
11155 fn test_value_bytes_max_returns_max() {
11156 let wm = WorkingMemory::new(10).unwrap();
11157 wm.set("k1", "short").unwrap();
11158 wm.set("k2", "much longer value").unwrap();
11159 assert_eq!(wm.value_bytes_max().unwrap(), "much longer value".len());
11160 }
11161
11162 #[test]
11163 fn test_value_bytes_max_zero_for_empty_store() {
11164 let wm = WorkingMemory::new(10).unwrap();
11165 assert_eq!(wm.value_bytes_max().unwrap(), 0);
11166 }
11167
11168 #[test]
11169 fn test_total_content_words_returns_total_word_count() {
11170 let store = EpisodicStore::new();
11171 let agent = AgentId::new("r51-words-1");
11172 store.add_episode(agent.clone(), "hello world", 0.5).unwrap();
11173 store.add_episode(agent.clone(), "one two three", 0.5).unwrap();
11174 assert_eq!(store.total_content_words(&agent).unwrap(), 5);
11175 }
11176
11177 #[test]
11178 fn test_total_content_words_zero_for_unknown_agent() {
11179 let store = EpisodicStore::new();
11180 let agent = AgentId::new("r51-words-unknown");
11181 assert_eq!(store.total_content_words(&agent).unwrap(), 0);
11182 }
11183
11184 #[test]
11187 fn test_agent_count_zero_for_empty_store() {
11188 let store = EpisodicStore::new();
11189 assert_eq!(store.agent_count().unwrap(), 0);
11190 }
11191
11192 #[test]
11193 fn test_agent_count_reflects_distinct_agents() {
11194 let store = EpisodicStore::new();
11195 let a = AgentId::new("r58-a");
11196 let b = AgentId::new("r58-b");
11197 store.add_episode_with_tags(a.clone(), "x", 0.5, vec![]).unwrap();
11198 store.add_episode_with_tags(a.clone(), "y", 0.5, vec![]).unwrap();
11199 store.add_episode_with_tags(b.clone(), "z", 0.5, vec![]).unwrap();
11200 assert_eq!(store.agent_count().unwrap(), 2);
11201 }
11202
11203 #[test]
11204 fn test_max_key_length_returns_longest() {
11205 let wm = WorkingMemory::new(10).unwrap();
11206 wm.set("short", "v").unwrap();
11207 wm.set("a-longer-key", "v").unwrap();
11208 assert_eq!(wm.max_key_length().unwrap(), "a-longer-key".len());
11209 }
11210
11211 #[test]
11212 fn test_max_key_length_zero_for_empty_store() {
11213 let wm = WorkingMemory::new(10).unwrap();
11214 assert_eq!(wm.max_key_length().unwrap(), 0);
11215 }
11216
11217 #[test]
11220 fn test_episodes_with_importance_above_filters_correctly() {
11221 let store = EpisodicStore::new();
11222 let agent = AgentId::new("r59-epia-agent");
11223 store.add_episode_with_tags(agent.clone(), "low", 0.2, vec![]).unwrap();
11224 store.add_episode_with_tags(agent.clone(), "high", 0.9, vec![]).unwrap();
11225 store.add_episode_with_tags(agent.clone(), "mid", 0.5, vec![]).unwrap();
11226 let result = store.episodes_with_importance_above(&agent, 0.4).unwrap();
11227 assert_eq!(result.len(), 2);
11228 }
11229
11230 #[test]
11231 fn test_episodes_with_importance_above_empty_for_unknown_agent() {
11232 let store = EpisodicStore::new();
11233 let agent = AgentId::new("r59-epia-unknown");
11234 let result = store.episodes_with_importance_above(&agent, 0.0).unwrap();
11235 assert!(result.is_empty());
11236 }
11237
11238 #[test]
11239 fn test_value_char_count_returns_correct_count() {
11240 let wm = WorkingMemory::new(10).unwrap();
11241 wm.set("greeting", "héllo").unwrap();
11242 assert_eq!(wm.value_char_count("greeting").unwrap(), 5);
11243 }
11244
11245 #[test]
11246 fn test_value_char_count_zero_for_missing_key() {
11247 let wm = WorkingMemory::new(10).unwrap();
11248 assert_eq!(wm.value_char_count("missing").unwrap(), 0);
11249 }
11250
11251 #[test]
11254 fn test_agent_episode_importance_sum_correct() {
11255 let store = EpisodicStore::new();
11256 let agent = AgentId::new("r60-importance-sum");
11257 store.add_episode_with_tags(agent.clone(), "a", 0.4, vec![]).unwrap();
11258 store.add_episode_with_tags(agent.clone(), "b", 0.6, vec![]).unwrap();
11259 let sum = store.agent_episode_importance_sum(&agent).unwrap();
11260 assert!((sum - 1.0).abs() < 1e-6);
11261 }
11262
11263 #[test]
11264 fn test_agent_episode_importance_sum_zero_for_unknown_agent() {
11265 let store = EpisodicStore::new();
11266 let agent = AgentId::new("r60-importance-sum-unknown");
11267 assert_eq!(store.agent_episode_importance_sum(&agent).unwrap(), 0.0);
11268 }
11269
11270 #[test]
11271 fn test_keys_longer_than_filters_correctly() {
11272 let wm = WorkingMemory::new(10).unwrap();
11273 wm.set("ab", "v").unwrap();
11274 wm.set("abcdef", "v").unwrap();
11275 wm.set("xyz", "v").unwrap();
11276 let mut keys = wm.keys_longer_than(3).unwrap();
11277 keys.sort();
11278 assert_eq!(keys, vec!["abcdef".to_string()]);
11279 }
11280
11281 #[test]
11282 fn test_keys_longer_than_empty_when_none_qualify() {
11283 let wm = WorkingMemory::new(10).unwrap();
11284 wm.set("hi", "v").unwrap();
11285 assert!(wm.keys_longer_than(10).unwrap().is_empty());
11286 }
11287
11288 #[test]
11291 fn test_episodes_with_content_containing_returns_matches() {
11292 let store = EpisodicStore::new();
11293 let agent = AgentId::new("r59-content");
11294 store.add_episode_with_tags(agent.clone(), "hello world", 0.5, vec![]).unwrap();
11295 store.add_episode_with_tags(agent.clone(), "goodbye", 0.5, vec![]).unwrap();
11296 let result = store.episodes_with_content_containing(&agent, "hello").unwrap();
11297 assert_eq!(result.len(), 1);
11298 assert!(result[0].content.contains("hello"));
11299 }
11300
11301 #[test]
11302 fn test_episodes_with_content_containing_empty_for_no_match() {
11303 let store = EpisodicStore::new();
11304 let agent = AgentId::new("r59-content-miss");
11305 store.add_episode_with_tags(agent.clone(), "hello", 0.5, vec![]).unwrap();
11306 let result = store.episodes_with_content_containing(&agent, "xyz").unwrap();
11307 assert!(result.is_empty());
11308 }
11309
11310 #[test]
11311 fn test_average_value_length_computes_mean() {
11312 let wm = WorkingMemory::new(10).unwrap();
11313 wm.set("a", "hi").unwrap(); wm.set("b", "hello").unwrap(); let avg = wm.average_value_length().unwrap();
11317 assert!((avg - 3.5).abs() < 1e-9);
11318 }
11319
11320 #[test]
11321 fn test_average_value_length_zero_for_empty_store() {
11322 let wm = WorkingMemory::new(10).unwrap();
11323 assert_eq!(wm.average_value_length().unwrap(), 0.0);
11324 }
11325
11326 #[test]
11329 fn test_episodes_min_importance_returns_minimum() {
11330 let store = EpisodicStore::new();
11331 let agent = AgentId::new("r61-min-imp");
11332 store.add_episode_with_tags(agent.clone(), "a", 0.8, vec![]).unwrap();
11333 store.add_episode_with_tags(agent.clone(), "b", 0.2, vec![]).unwrap();
11334 let min = store.episodes_min_importance(&agent).unwrap();
11335 assert!((min.unwrap() - 0.2_f32).abs() < 1e-6);
11336 }
11337
11338 #[test]
11339 fn test_episodes_min_importance_none_for_unknown_agent() {
11340 let store = EpisodicStore::new();
11341 let agent = AgentId::new("r61-min-imp-unknown");
11342 assert!(store.episodes_min_importance(&agent).unwrap().is_none());
11343 }
11344
11345 #[test]
11346 fn test_episode_min_content_words_returns_minimum() {
11347 let store = EpisodicStore::new();
11348 let agent = AgentId::new("r61-min-words");
11349 store.add_episode_with_tags(agent.clone(), "one two three", 0.5, vec![]).unwrap();
11350 store.add_episode_with_tags(agent.clone(), "hello", 0.5, vec![]).unwrap();
11351 assert_eq!(store.episode_min_content_words(&agent).unwrap(), 1);
11352 }
11353
11354 #[test]
11355 fn test_episode_min_content_words_zero_for_unknown_agent() {
11356 let store = EpisodicStore::new();
11357 let agent = AgentId::new("r61-min-words-unknown");
11358 assert_eq!(store.episode_min_content_words(&agent).unwrap(), 0);
11359 }
11360
11361 #[test]
11362 fn test_keys_shorter_than_filters_correctly() {
11363 let wm = WorkingMemory::new(10).unwrap();
11364 wm.set("a", "v").unwrap();
11365 wm.set("abcdef", "v").unwrap();
11366 wm.set("abc", "v").unwrap();
11367 let mut keys = wm.keys_shorter_than(3).unwrap();
11368 keys.sort();
11369 assert_eq!(keys, vec!["a".to_string()]);
11370 }
11371
11372 #[test]
11373 fn test_keys_shorter_than_empty_when_none_qualify() {
11374 let wm = WorkingMemory::new(10).unwrap();
11375 wm.set("long_key", "v").unwrap();
11376 assert!(wm.keys_shorter_than(1).unwrap().is_empty());
11377 }
11378
11379 #[test]
11382 fn test_episode_max_content_words_returns_maximum() {
11383 let store = EpisodicStore::new();
11384 let agent = AgentId::new("r62-max-words");
11385 store.add_episode_with_tags(agent.clone(), "one", 0.5, vec![]).unwrap();
11386 store.add_episode_with_tags(agent.clone(), "one two three four", 0.5, vec![]).unwrap();
11387 assert_eq!(store.episode_max_content_words(&agent).unwrap(), 4);
11388 }
11389
11390 #[test]
11391 fn test_episode_max_content_words_zero_for_unknown_agent() {
11392 let store = EpisodicStore::new();
11393 let agent = AgentId::new("r62-max-words-unknown");
11394 assert_eq!(store.episode_max_content_words(&agent).unwrap(), 0);
11395 }
11396
11397 #[test]
11398 fn test_total_items_counts_across_all_agents() {
11399 let store = EpisodicStore::new();
11400 let a = AgentId::new("r62-total-a");
11401 let b = AgentId::new("r62-total-b");
11402 store.add_episode_with_tags(a.clone(), "x", 0.5, vec![]).unwrap();
11403 store.add_episode_with_tags(a.clone(), "y", 0.5, vec![]).unwrap();
11404 store.add_episode_with_tags(b.clone(), "z", 0.5, vec![]).unwrap();
11405 assert_eq!(store.total_items().unwrap(), 3);
11406 }
11407
11408 #[test]
11409 fn test_all_episodes_returns_all_items() {
11410 let store = EpisodicStore::new();
11411 let a = AgentId::new("r62-all-a");
11412 let b = AgentId::new("r62-all-b");
11413 store.add_episode_with_tags(a.clone(), "ep1", 0.5, vec![]).unwrap();
11414 store.add_episode_with_tags(b.clone(), "ep2", 0.5, vec![]).unwrap();
11415 assert_eq!(store.all_episodes().unwrap().len(), 2);
11416 }
11417
11418 #[test]
11419 fn test_count_keys_below_bytes_correct() {
11420 let wm = WorkingMemory::new(10).unwrap();
11421 wm.set("a", "v").unwrap();
11422 wm.set("ab", "v").unwrap();
11423 wm.set("abcdefgh", "v").unwrap();
11424 assert_eq!(wm.count_keys_below_bytes(3).unwrap(), 2);
11425 }
11426
11427 #[test]
11428 fn test_count_keys_below_bytes_zero_when_none_qualify() {
11429 let wm = WorkingMemory::new(10).unwrap();
11430 wm.set("long_key", "v").unwrap();
11431 assert_eq!(wm.count_keys_below_bytes(1).unwrap(), 0);
11432 }
11433
11434 #[test]
11437 fn test_min_episode_count_returns_agent_with_fewest() {
11438 let store = EpisodicStore::new();
11439 let a = AgentId::new("r63-min-a");
11440 let b = AgentId::new("r63-min-b");
11441 store.add_episode_with_tags(a.clone(), "x", 0.5, vec![]).unwrap();
11442 store.add_episode_with_tags(a.clone(), "y", 0.5, vec![]).unwrap();
11443 store.add_episode_with_tags(b.clone(), "z", 0.5, vec![]).unwrap();
11444 let result = store.min_episode_count().unwrap().unwrap();
11445 assert_eq!(result.0, b);
11446 assert_eq!(result.1, 1);
11447 }
11448
11449 #[test]
11450 fn test_min_episode_count_none_for_empty_store() {
11451 let store = EpisodicStore::new();
11452 assert!(store.min_episode_count().unwrap().is_none());
11453 }
11454
11455 #[test]
11456 fn test_values_with_prefix_returns_matching_values() {
11457 let wm = WorkingMemory::new(10).unwrap();
11458 wm.set("k1", "prefix:hello").unwrap();
11459 wm.set("k2", "other:world").unwrap();
11460 wm.set("k3", "prefix:bye").unwrap();
11461 let mut vals = wm.values_with_prefix("prefix:").unwrap();
11462 vals.sort();
11463 assert_eq!(vals, vec!["prefix:bye".to_string(), "prefix:hello".to_string()]);
11464 }
11465
11466 #[test]
11467 fn test_values_with_prefix_empty_when_none_match() {
11468 let wm = WorkingMemory::new(10).unwrap();
11469 wm.set("k1", "hello").unwrap();
11470 assert!(wm.values_with_prefix("xyz:").unwrap().is_empty());
11471 }
11472
11473 #[test]
11476 fn test_max_episode_importance_returns_maximum() {
11477 let store = EpisodicStore::new();
11478 let agent = AgentId::new("r63-max-imp");
11479 store.add_episode_with_tags(agent.clone(), "a", 0.3, vec![]).unwrap();
11480 store.add_episode_with_tags(agent.clone(), "b", 0.9, vec![]).unwrap();
11481 store.add_episode_with_tags(agent.clone(), "c", 0.6, vec![]).unwrap();
11482 let max = store.max_episode_importance(&agent).unwrap();
11483 assert!((max.unwrap() - 0.9_f32).abs() < 1e-6);
11484 }
11485
11486 #[test]
11487 fn test_max_episode_importance_none_for_unknown_agent() {
11488 let store = EpisodicStore::new();
11489 let agent = AgentId::new("r63-max-imp-unknown");
11490 assert!(store.max_episode_importance(&agent).unwrap().is_none());
11491 }
11492
11493 #[test]
11494 fn test_max_episode_importance_single_episode() {
11495 let store = EpisodicStore::new();
11496 let agent = AgentId::new("r63-max-imp-single");
11497 store.add_episode_with_tags(agent.clone(), "only", 0.42, vec![]).unwrap();
11498 let max = store.max_episode_importance(&agent).unwrap();
11499 assert!((max.unwrap() - 0.42_f32).abs() < 1e-6);
11500 }
11501
11502 #[test]
11505 fn test_episodes_in_range_returns_filtered_episodes() {
11506 let store = EpisodicStore::new();
11507 let agent = AgentId::new("r64-ep-range");
11508 store.add_episode_with_tags(agent.clone(), "low", 0.1, vec![]).unwrap();
11509 store.add_episode_with_tags(agent.clone(), "mid", 0.5, vec![]).unwrap();
11510 store.add_episode_with_tags(agent.clone(), "high", 0.9, vec![]).unwrap();
11511 let results = store.episodes_in_range(&agent, 0.3, 0.7).unwrap();
11512 assert_eq!(results.len(), 1);
11513 assert_eq!(results[0].content, "mid");
11514 }
11515
11516 #[test]
11517 fn test_episodes_in_range_empty_for_unknown_agent() {
11518 let store = EpisodicStore::new();
11519 let agent = AgentId::new("r64-ep-range-unknown");
11520 assert!(store.episodes_in_range(&agent, 0.0, 1.0).unwrap().is_empty());
11521 }
11522
11523 #[test]
11524 fn test_agent_importance_range_returns_min_max() {
11525 let store = EpisodicStore::new();
11526 let agent = AgentId::new("r64-imp-range");
11527 store.add_episode_with_tags(agent.clone(), "a", 0.2, vec![]).unwrap();
11528 store.add_episode_with_tags(agent.clone(), "b", 0.8, vec![]).unwrap();
11529 let range = store.agent_importance_range(&agent).unwrap();
11530 let (min, max) = range.unwrap();
11531 assert!((min - 0.2_f32).abs() < 1e-6);
11532 assert!((max - 0.8_f32).abs() < 1e-6);
11533 }
11534
11535 #[test]
11536 fn test_agent_importance_range_none_for_unknown_agent() {
11537 let store = EpisodicStore::new();
11538 let agent = AgentId::new("r64-imp-range-unknown");
11539 assert!(store.agent_importance_range(&agent).unwrap().is_none());
11540 }
11541
11542 #[test]
11543 fn test_values_with_suffix_returns_matching() {
11544 let wm = WorkingMemory::new(10).unwrap();
11545 wm.set("k1", "hello world").unwrap();
11546 wm.set("k2", "foo world").unwrap();
11547 wm.set("k3", "bar xyz").unwrap();
11548 let mut vals = wm.values_with_suffix("world").unwrap();
11549 vals.sort();
11550 assert_eq!(vals, vec!["foo world", "hello world"]);
11551 }
11552
11553 #[test]
11554 fn test_values_with_suffix_empty_when_none_match() {
11555 let wm = WorkingMemory::new(10).unwrap();
11556 wm.set("k1", "hello").unwrap();
11557 assert!(wm.values_with_suffix("xyz").unwrap().is_empty());
11558 }
11559}