1use std::collections::HashMap;
19
20use super::block::{MemoryBlock, MemoryBlockId, MemoryBlockType};
21use crate::constants::{
22 CORE_MEMORY_BLOCK_SIZE_BYTES_MAX, CORE_MEMORY_SIZE_BYTES_MAX, CORE_MEMORY_SIZE_BYTES_MIN,
23};
24
25#[derive(Debug, Clone, thiserror::Error)]
27pub enum CoreMemoryError {
28 #[error("core memory full: {current_bytes}/{max_bytes} bytes, need {requested_bytes}")]
30 Full {
31 current_bytes: usize,
33 max_bytes: usize,
35 requested_bytes: usize,
37 },
38
39 #[error("block not found: {block_type}")]
41 BlockNotFound {
42 block_type: String,
44 },
45
46 #[error("block too large: {size_bytes} bytes exceeds max {max_bytes}")]
48 BlockTooLarge {
49 size_bytes: usize,
51 max_bytes: usize,
53 },
54
55 #[error("too many blocks: {count} exceeds max {max_count}")]
57 TooManyBlocks {
58 count: usize,
60 max_count: usize,
62 },
63}
64
65pub type CoreMemoryResult<T> = Result<T, CoreMemoryError>;
67
68#[derive(Debug, Clone)]
70pub struct CoreMemoryConfig {
71 pub max_bytes: usize,
73}
74
75impl CoreMemoryConfig {
76 #[must_use]
82 pub fn new(max_bytes: usize) -> Self {
83 assert!(
85 max_bytes >= CORE_MEMORY_SIZE_BYTES_MIN,
86 "max_bytes {} below minimum {}",
87 max_bytes,
88 CORE_MEMORY_SIZE_BYTES_MIN
89 );
90 assert!(
91 max_bytes <= CORE_MEMORY_SIZE_BYTES_MAX,
92 "max_bytes {} exceeds maximum {}",
93 max_bytes,
94 CORE_MEMORY_SIZE_BYTES_MAX
95 );
96
97 Self { max_bytes }
98 }
99}
100
101impl Default for CoreMemoryConfig {
102 fn default() -> Self {
103 Self {
104 max_bytes: CORE_MEMORY_SIZE_BYTES_MAX,
105 }
106 }
107}
108
109#[derive(Debug)]
131pub struct CoreMemory {
132 config: CoreMemoryConfig,
134 blocks_by_type: HashMap<MemoryBlockType, MemoryBlock>,
136 current_bytes: usize,
138 clock_ms: u64,
141}
142
143impl CoreMemory {
144 #[must_use]
146 pub fn new() -> Self {
147 Self::with_config(CoreMemoryConfig::default())
148 }
149
150 #[must_use]
152 pub fn with_config(config: CoreMemoryConfig) -> Self {
153 Self {
154 config,
155 blocks_by_type: HashMap::new(),
156 current_bytes: 0,
157 clock_ms: 0,
158 }
159 }
160
161 pub fn set_clock_ms(&mut self, ms: u64) {
165 self.clock_ms = ms;
166 }
167
168 #[must_use]
170 pub fn clock_ms(&self) -> u64 {
171 self.clock_ms
172 }
173
174 pub fn set_block(
182 &mut self,
183 block_type: MemoryBlockType,
184 content: impl Into<String>,
185 ) -> CoreMemoryResult<MemoryBlockId> {
186 let content = content.into();
187 let new_size = content.len();
188
189 if new_size > CORE_MEMORY_BLOCK_SIZE_BYTES_MAX {
191 return Err(CoreMemoryError::BlockTooLarge {
192 size_bytes: new_size,
193 max_bytes: CORE_MEMORY_BLOCK_SIZE_BYTES_MAX,
194 });
195 }
196
197 let old_size = self
199 .blocks_by_type
200 .get(&block_type)
201 .map(|b| b.size_bytes())
202 .unwrap_or(0);
203 let projected_size = self.current_bytes - old_size + new_size;
204
205 if projected_size > self.config.max_bytes {
207 return Err(CoreMemoryError::Full {
208 current_bytes: self.current_bytes,
209 max_bytes: self.config.max_bytes,
210 requested_bytes: new_size,
211 });
212 }
213
214 let block = MemoryBlock::new(block_type, content, self.clock_ms);
216 let id = block.id();
217
218 self.blocks_by_type.insert(block_type, block);
219 self.current_bytes = projected_size;
220
221 assert!(
223 self.current_bytes <= self.config.max_bytes,
224 "size invariant violated"
225 );
226
227 Ok(id)
228 }
229
230 pub fn set_block_with_label(
235 &mut self,
236 block_type: MemoryBlockType,
237 label: impl Into<String>,
238 content: impl Into<String>,
239 ) -> CoreMemoryResult<MemoryBlockId> {
240 let label = label.into();
241 let content = content.into();
242 let new_size = content.len();
243
244 if new_size > CORE_MEMORY_BLOCK_SIZE_BYTES_MAX {
246 return Err(CoreMemoryError::BlockTooLarge {
247 size_bytes: new_size,
248 max_bytes: CORE_MEMORY_BLOCK_SIZE_BYTES_MAX,
249 });
250 }
251
252 let old_size = self
254 .blocks_by_type
255 .get(&block_type)
256 .map(|b| b.size_bytes())
257 .unwrap_or(0);
258 let projected_size = self.current_bytes - old_size + new_size;
259
260 if projected_size > self.config.max_bytes {
262 return Err(CoreMemoryError::Full {
263 current_bytes: self.current_bytes,
264 max_bytes: self.config.max_bytes,
265 requested_bytes: new_size,
266 });
267 }
268
269 let block = MemoryBlock::with_label(block_type, label, content, self.clock_ms);
270 let id = block.id();
271
272 self.blocks_by_type.insert(block_type, block);
273 self.current_bytes = projected_size;
274
275 Ok(id)
276 }
277
278 #[must_use]
280 pub fn get_block(&self, block_type: MemoryBlockType) -> Option<&MemoryBlock> {
281 self.blocks_by_type.get(&block_type)
282 }
283
284 #[must_use]
286 pub fn get_content(&self, block_type: MemoryBlockType) -> Option<&str> {
287 self.blocks_by_type.get(&block_type).map(|b| b.content())
288 }
289
290 #[must_use]
292 pub fn has_block(&self, block_type: MemoryBlockType) -> bool {
293 self.blocks_by_type.contains_key(&block_type)
294 }
295
296 pub fn remove_block(&mut self, block_type: MemoryBlockType) -> CoreMemoryResult<MemoryBlock> {
301 match self.blocks_by_type.remove(&block_type) {
302 Some(block) => {
303 self.current_bytes -= block.size_bytes();
304
305 assert!(
307 self.current_bytes <= self.config.max_bytes,
308 "size invariant violated after removal"
309 );
310
311 Ok(block)
312 }
313 None => Err(CoreMemoryError::BlockNotFound {
314 block_type: block_type.to_string(),
315 }),
316 }
317 }
318
319 pub fn clear(&mut self) {
321 self.blocks_by_type.clear();
322 self.current_bytes = 0;
323
324 assert_eq!(self.current_bytes, 0, "size must be zero after clear");
326 }
327
328 #[must_use]
330 pub fn block_count(&self) -> usize {
331 self.blocks_by_type.len()
332 }
333
334 #[must_use]
336 pub fn used_bytes(&self) -> usize {
337 self.current_bytes
338 }
339
340 #[must_use]
342 pub fn available_bytes(&self) -> usize {
343 self.config.max_bytes.saturating_sub(self.current_bytes)
344 }
345
346 #[must_use]
348 pub fn max_bytes(&self) -> usize {
349 self.config.max_bytes
350 }
351
352 #[must_use]
354 pub fn utilization(&self) -> f64 {
355 if self.config.max_bytes == 0 {
356 return 0.0;
357 }
358 self.current_bytes as f64 / self.config.max_bytes as f64
359 }
360
361 #[must_use]
363 pub fn is_empty(&self) -> bool {
364 self.blocks_by_type.is_empty()
365 }
366
367 pub fn blocks_ordered(&self) -> impl Iterator<Item = &MemoryBlock> {
371 MemoryBlockType::all_ordered()
372 .iter()
373 .filter_map(|bt| self.blocks_by_type.get(bt))
374 }
375
376 #[must_use]
393 pub fn render(&self) -> String {
394 let mut output = String::with_capacity(self.current_bytes + 256);
395 output.push_str("<core_memory>\n");
396
397 for block in self.blocks_ordered() {
398 output.push_str(&block.render());
399 output.push('\n');
400 }
401
402 output.push_str("</core_memory>");
403 output
404 }
405
406 #[must_use]
408 pub fn config(&self) -> &CoreMemoryConfig {
409 &self.config
410 }
411}
412
413impl Default for CoreMemory {
414 fn default() -> Self {
415 Self::new()
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use super::*;
422
423 #[test]
424 fn test_core_memory_new() {
425 let core = CoreMemory::new();
426 assert_eq!(core.used_bytes(), 0);
427 assert_eq!(core.max_bytes(), CORE_MEMORY_SIZE_BYTES_MAX);
428 assert!(core.is_empty());
429 }
430
431 #[test]
432 fn test_core_memory_with_config() {
433 let config = CoreMemoryConfig::new(16 * 1024);
434 let core = CoreMemory::with_config(config);
435 assert_eq!(core.max_bytes(), 16 * 1024);
436 }
437
438 #[test]
439 fn test_set_block() {
440 let mut core = CoreMemory::new();
441
442 let id = core.set_block(MemoryBlockType::System, "Hello").unwrap();
443 assert!(!id.as_uuid().is_nil());
444
445 assert!(core.has_block(MemoryBlockType::System));
446 assert_eq!(core.get_content(MemoryBlockType::System), Some("Hello"));
447 assert_eq!(core.used_bytes(), 5);
448 }
449
450 #[test]
451 fn test_set_block_replaces() {
452 let mut core = CoreMemory::new();
453
454 core.set_block(MemoryBlockType::System, "Hello").unwrap();
455 assert_eq!(core.used_bytes(), 5);
456
457 core.set_block(MemoryBlockType::System, "Hi").unwrap();
458 assert_eq!(core.used_bytes(), 2);
459 assert_eq!(core.get_content(MemoryBlockType::System), Some("Hi"));
460 }
461
462 #[test]
463 fn test_set_block_with_label() {
464 let mut core = CoreMemory::new();
465
466 core.set_block_with_label(MemoryBlockType::Facts, "prefs", "Likes cats")
467 .unwrap();
468
469 let block = core.get_block(MemoryBlockType::Facts).unwrap();
470 assert_eq!(block.label(), Some("prefs"));
471 assert_eq!(block.content(), "Likes cats");
472 }
473
474 #[test]
475 fn test_remove_block() {
476 let mut core = CoreMemory::new();
477 core.set_block(MemoryBlockType::System, "Hello").unwrap();
478
479 let removed = core.remove_block(MemoryBlockType::System).unwrap();
480 assert_eq!(removed.content(), "Hello");
481 assert!(!core.has_block(MemoryBlockType::System));
482 assert_eq!(core.used_bytes(), 0);
483 }
484
485 #[test]
486 fn test_remove_block_not_found() {
487 let mut core = CoreMemory::new();
488 let result = core.remove_block(MemoryBlockType::System);
489 assert!(matches!(result, Err(CoreMemoryError::BlockNotFound { .. })));
490 }
491
492 #[test]
493 fn test_clear() {
494 let mut core = CoreMemory::new();
495 core.set_block(MemoryBlockType::System, "Hello").unwrap();
496 core.set_block(MemoryBlockType::Human, "World").unwrap();
497
498 core.clear();
499
500 assert!(core.is_empty());
501 assert_eq!(core.used_bytes(), 0);
502 }
503
504 #[test]
505 fn test_capacity_limit() {
506 let config = CoreMemoryConfig::new(CORE_MEMORY_SIZE_BYTES_MIN); let mut core = CoreMemory::with_config(config);
508
509 let content = "x".repeat(CORE_MEMORY_SIZE_BYTES_MIN - 100);
511 core.set_block(MemoryBlockType::System, content).unwrap();
512
513 let result = core.set_block(MemoryBlockType::Human, "x".repeat(200));
515 assert!(matches!(result, Err(CoreMemoryError::Full { .. })));
516 }
517
518 #[test]
519 fn test_block_too_large() {
520 let mut core = CoreMemory::new();
521 let content = "x".repeat(CORE_MEMORY_BLOCK_SIZE_BYTES_MAX + 1);
522
523 let result = core.set_block(MemoryBlockType::System, content);
524 assert!(matches!(result, Err(CoreMemoryError::BlockTooLarge { .. })));
525 }
526
527 #[test]
528 fn test_utilization() {
529 let mut core = CoreMemory::new();
530 assert_eq!(core.utilization(), 0.0);
531
532 let content = "x".repeat(CORE_MEMORY_SIZE_BYTES_MAX / 2);
533 core.set_block(MemoryBlockType::System, content).unwrap();
534
535 let util = core.utilization();
536 assert!(util > 0.49 && util < 0.51);
537 }
538
539 #[test]
540 fn test_render_empty() {
541 let core = CoreMemory::new();
542 let rendered = core.render();
543 assert_eq!(rendered, "<core_memory>\n</core_memory>");
544 }
545
546 #[test]
547 fn test_render_with_blocks() {
548 let mut core = CoreMemory::new();
549 core.set_block(MemoryBlockType::System, "Be helpful.")
550 .unwrap();
551 core.set_block(MemoryBlockType::Human, "User: Alice")
552 .unwrap();
553
554 let rendered = core.render();
555
556 assert!(rendered.starts_with("<core_memory>"));
557 assert!(rendered.ends_with("</core_memory>"));
558 assert!(rendered.contains("Be helpful."));
559 assert!(rendered.contains("User: Alice"));
560 let sys_pos = rendered.find("system").unwrap();
562 let human_pos = rendered.find("human").unwrap();
563 assert!(sys_pos < human_pos);
564 }
565
566 #[test]
567 fn test_render_order() {
568 let mut core = CoreMemory::new();
569 core.set_block(MemoryBlockType::Scratch, "5").unwrap();
571 core.set_block(MemoryBlockType::Goals, "4").unwrap();
572 core.set_block(MemoryBlockType::Facts, "3").unwrap();
573 core.set_block(MemoryBlockType::Human, "2").unwrap();
574 core.set_block(MemoryBlockType::Persona, "1").unwrap();
575 core.set_block(MemoryBlockType::System, "0").unwrap();
576
577 let rendered = core.render();
578
579 let positions: Vec<usize> = ["system", "persona", "human", "facts", "goals", "scratch"]
581 .iter()
582 .map(|s| rendered.find(s).unwrap())
583 .collect();
584
585 for i in 1..positions.len() {
587 assert!(
588 positions[i] > positions[i - 1],
589 "render order should be by priority"
590 );
591 }
592 }
593
594 #[test]
595 fn test_clock_ms() {
596 let mut core = CoreMemory::new();
597 assert_eq!(core.clock_ms(), 0);
598
599 core.set_clock_ms(5000);
600 assert_eq!(core.clock_ms(), 5000);
601
602 core.set_block(MemoryBlockType::System, "Test").unwrap();
603 let block = core.get_block(MemoryBlockType::System).unwrap();
604 assert_eq!(block.created_at_ms(), 5000);
605 }
606
607 #[test]
608 fn test_blocks_ordered_iterator() {
609 let mut core = CoreMemory::new();
610 core.set_block(MemoryBlockType::Scratch, "scratch").unwrap();
611 core.set_block(MemoryBlockType::System, "system").unwrap();
612
613 let blocks: Vec<_> = core.blocks_ordered().collect();
614 assert_eq!(blocks.len(), 2);
615 assert_eq!(blocks[0].block_type(), MemoryBlockType::System);
616 assert_eq!(blocks[1].block_type(), MemoryBlockType::Scratch);
617 }
618
619 #[test]
620 #[should_panic(expected = "max_bytes")]
621 fn test_config_below_minimum() {
622 let _ = CoreMemoryConfig::new(100);
623 }
624
625 #[test]
626 #[should_panic(expected = "max_bytes")]
627 fn test_config_above_maximum() {
628 let _ = CoreMemoryConfig::new(CORE_MEMORY_SIZE_BYTES_MAX + 1);
629 }
630}
631
632#[cfg(test)]
634mod dst_tests {
635 use super::*;
636 use crate::dst::{SimConfig, Simulation};
637
638 #[tokio::test]
640 async fn test_core_memory_with_sim_clock() {
641 let sim = Simulation::new(SimConfig::with_seed(42));
642
643 sim.run(|env| async move {
644 let mut core = CoreMemory::new();
645
646 core.set_clock_ms(env.clock.now_ms());
648
649 core.set_block(MemoryBlockType::System, "Initial").unwrap();
651 let block = core.get_block(MemoryBlockType::System).unwrap();
652 assert_eq!(block.created_at_ms(), 0);
653
654 env.clock.advance_ms(1000);
656 core.set_clock_ms(env.clock.now_ms());
657
658 core.set_block(MemoryBlockType::System, "Updated").unwrap();
660 let block = core.get_block(MemoryBlockType::System).unwrap();
661 assert_eq!(block.created_at_ms(), 1000);
662
663 Ok::<(), std::convert::Infallible>(())
664 })
665 .await
666 .unwrap();
667 }
668
669 #[tokio::test]
671 async fn test_core_memory_determinism() {
672 let mut results1 = Vec::new();
673 let mut results2 = Vec::new();
674
675 let sim1 = Simulation::new(SimConfig::with_seed(12345));
677 sim1.run(|mut env| async move {
678 let mut core = CoreMemory::new();
679
680 for _ in 0..5 {
681 env.clock.advance_ms(100);
682 core.set_clock_ms(env.clock.now_ms());
683
684 let content = format!("block_{}", env.rng.next_usize(0, 1000));
685 core.set_block(MemoryBlockType::Scratch, &content).unwrap();
686 results1.push(content);
687 }
688
689 Ok::<(), std::convert::Infallible>(())
690 })
691 .await
692 .unwrap();
693
694 let sim2 = Simulation::new(SimConfig::with_seed(12345));
696 sim2.run(|mut env| async move {
697 let mut core = CoreMemory::new();
698
699 for _ in 0..5 {
700 env.clock.advance_ms(100);
701 core.set_clock_ms(env.clock.now_ms());
702
703 let content = format!("block_{}", env.rng.next_usize(0, 1000));
704 core.set_block(MemoryBlockType::Scratch, &content).unwrap();
705 results2.push(content);
706 }
707
708 Ok::<(), std::convert::Infallible>(())
709 })
710 .await
711 .unwrap();
712
713 }
716
717 #[tokio::test]
719 async fn test_core_memory_time_tracking() {
720 let sim = Simulation::new(SimConfig::with_seed(42));
721
722 sim.run(|env| async move {
723 let mut core = CoreMemory::new();
724
725 let mut timestamps = Vec::new();
727
728 for i in 0..3 {
729 env.clock.advance_ms(500);
730 core.set_clock_ms(env.clock.now_ms());
731
732 let content = format!("Block {}", i);
733 let block_type = match i {
734 0 => MemoryBlockType::System,
735 1 => MemoryBlockType::Human,
736 _ => MemoryBlockType::Facts,
737 };
738
739 core.set_block(block_type, content).unwrap();
740 timestamps.push(env.clock.now_ms());
741 }
742
743 assert_eq!(timestamps, vec![500, 1000, 1500]);
745
746 assert_eq!(
748 core.get_block(MemoryBlockType::System)
749 .unwrap()
750 .created_at_ms(),
751 500
752 );
753 assert_eq!(
754 core.get_block(MemoryBlockType::Human)
755 .unwrap()
756 .created_at_ms(),
757 1000
758 );
759 assert_eq!(
760 core.get_block(MemoryBlockType::Facts)
761 .unwrap()
762 .created_at_ms(),
763 1500
764 );
765
766 Ok::<(), std::convert::Infallible>(())
767 })
768 .await
769 .unwrap();
770 }
771
772 #[tokio::test]
774 async fn test_core_memory_capacity_under_simulation() {
775 let sim = Simulation::new(SimConfig::with_seed(42));
776
777 sim.run(|env| async move {
778 let config = CoreMemoryConfig::new(CORE_MEMORY_SIZE_BYTES_MIN);
779 let mut core = CoreMemory::with_config(config);
780 core.set_clock_ms(env.clock.now_ms());
781
782 let mut total_added = 0;
784 let block_types = [
785 MemoryBlockType::System,
786 MemoryBlockType::Persona,
787 MemoryBlockType::Human,
788 ];
789
790 for block_type in &block_types {
791 let size = 1000; let content = "x".repeat(size);
793
794 match core.set_block(*block_type, content) {
795 Ok(_) => total_added += size,
796 Err(CoreMemoryError::Full { .. }) => break,
797 Err(e) => panic!("Unexpected error: {:?}", e),
798 }
799
800 env.clock.advance_ms(100);
801 core.set_clock_ms(env.clock.now_ms());
802 }
803
804 assert!(total_added >= 3000);
806 assert!(core.used_bytes() <= CORE_MEMORY_SIZE_BYTES_MIN);
807
808 Ok::<(), std::convert::Infallible>(())
809 })
810 .await
811 .unwrap();
812 }
813
814 #[tokio::test]
816 async fn test_render_deterministic() {
817 let sim = Simulation::new(SimConfig::with_seed(42));
818
819 sim.run(|env| async move {
820 let mut core = CoreMemory::new();
821 core.set_clock_ms(env.clock.now_ms());
822
823 core.set_block(MemoryBlockType::System, "System prompt")
824 .unwrap();
825 core.set_block(MemoryBlockType::Human, "User info").unwrap();
826 core.set_block(MemoryBlockType::Facts, "Key facts").unwrap();
827
828 let rendered = core.render();
829
830 assert!(rendered.starts_with("<core_memory>"));
832 assert!(rendered.ends_with("</core_memory>"));
833
834 let sys_pos = rendered.find("type=\"system\"").unwrap();
836 let human_pos = rendered.find("type=\"human\"").unwrap();
837 let facts_pos = rendered.find("type=\"facts\"").unwrap();
838
839 assert!(sys_pos < human_pos);
840 assert!(human_pos < facts_pos);
841
842 Ok::<(), std::convert::Infallible>(())
843 })
844 .await
845 .unwrap();
846 }
847}
848
849#[cfg(test)]
850mod edge_case_tests {
851 use super::*;
852
853 #[test]
854 fn test_unicode_content_size() {
855 let mut core = CoreMemory::new();
856 core.set_block(MemoryBlockType::System, "こんにちは")
858 .unwrap();
859 assert_eq!(core.used_bytes(), 15);
860 assert_eq!(
861 core.get_content(MemoryBlockType::System)
862 .unwrap()
863 .chars()
864 .count(),
865 5
866 );
867 }
868
869 #[test]
870 fn test_empty_string_content() {
871 let mut core = CoreMemory::new();
872 core.set_block(MemoryBlockType::System, "").unwrap();
873 assert_eq!(core.used_bytes(), 0);
874 assert_eq!(core.get_content(MemoryBlockType::System), Some(""));
875 assert!(core.has_block(MemoryBlockType::System));
876 }
877
878 #[test]
879 fn test_empty_label() {
880 let mut core = CoreMemory::new();
881 core.set_block_with_label(MemoryBlockType::Facts, "", "content")
882 .unwrap();
883 let block = core.get_block(MemoryBlockType::Facts).unwrap();
884 assert_eq!(block.label(), Some(""));
885 }
886
887 #[test]
888 fn test_max_length_label() {
889 use crate::constants::CORE_MEMORY_BLOCK_LABEL_BYTES_MAX;
890 let mut core = CoreMemory::new();
891 let max_label = "x".repeat(CORE_MEMORY_BLOCK_LABEL_BYTES_MAX);
892 core.set_block_with_label(MemoryBlockType::Facts, &max_label, "content")
893 .unwrap();
894 let block = core.get_block(MemoryBlockType::Facts).unwrap();
895 assert_eq!(
896 block.label().unwrap().len(),
897 CORE_MEMORY_BLOCK_LABEL_BYTES_MAX
898 );
899 }
900
901 #[test]
902 fn test_whitespace_content() {
903 let mut core = CoreMemory::new();
904 core.set_block(MemoryBlockType::Scratch, " \n\t ")
905 .unwrap();
906 assert_eq!(core.used_bytes(), 7);
907 }
908}