Skip to main content

tycode_core/modules/memory/
log.rs

1//! Log-based memory system with JSON-backed storage.
2//!
3//! Memories are stored as a JSON log at ~/.tycode/memory/memories_log.json.
4//! Each memory has a monotonic sequence number, content, timestamp, and optional source.
5
6use std::fs;
7use std::path::{Path, PathBuf};
8
9use anyhow::{Context, Result};
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct Memory {
15    pub seq: u64,
16    pub content: String,
17    pub created_at: DateTime<Utc>,
18    pub source: Option<String>,
19}
20
21#[derive(Debug, Serialize, Deserialize)]
22struct MemoryLogInner {
23    memories: Vec<Memory>,
24    next_seq: u64,
25}
26
27/// Memory log that loads from disk on every operation.
28#[derive(Debug)]
29pub struct MemoryLog {
30    path: PathBuf,
31}
32
33impl MemoryLog {
34    pub fn new(path: PathBuf) -> Self {
35        Self { path }
36    }
37
38    /// Load current state from disk. Returns empty if file doesn't exist.
39    fn load_inner(&self) -> Result<MemoryLogInner> {
40        if !self.path.exists() {
41            return Ok(MemoryLogInner {
42                memories: Vec::new(),
43                next_seq: 1,
44            });
45        }
46
47        let content = fs::read_to_string(&self.path)
48            .with_context(|| format!("Failed to read memory log: {}", self.path.display()))?;
49
50        serde_json::from_str(&content)
51            .with_context(|| format!("Failed to parse memory log: {}", self.path.display()))
52    }
53
54    /// Save state to disk, creating directories as needed.
55    fn save_inner(&self, inner: &MemoryLogInner) -> Result<()> {
56        if let Some(parent) = self.path.parent() {
57            fs::create_dir_all(parent).with_context(|| {
58                format!("Failed to create memory directory: {}", parent.display())
59            })?;
60        }
61
62        let content =
63            serde_json::to_string_pretty(inner).context("Failed to serialize memory log")?;
64
65        fs::write(&self.path, content)
66            .with_context(|| format!("Failed to write memory log: {}", self.path.display()))
67    }
68
69    /// Append a new memory. Loads from disk, adds memory, saves back.
70    /// Race condition: if two processes append simultaneously, one may lose.
71    /// This is acceptable - we lose a few memories, not the entire log.
72    pub fn append(&self, content: String, source: Option<String>) -> Result<u64> {
73        let mut inner = self.load_inner()?;
74
75        let seq = inner.next_seq;
76        inner.next_seq += 1;
77
78        inner.memories.push(Memory {
79            seq,
80            content,
81            created_at: Utc::now(),
82            source,
83        });
84
85        self.save_inner(&inner)?;
86        Ok(seq)
87    }
88
89    /// Read all memories from disk.
90    pub fn read_all(&self) -> Result<Vec<Memory>> {
91        self.load_inner().map(|inner| inner.memories)
92    }
93
94    pub fn path(&self) -> &Path {
95        &self.path
96    }
97}