garudust_memory/
file_store.rs1use std::path::{Path, PathBuf};
2
3use async_trait::async_trait;
4use garudust_core::{
5 config::MemoryExpiryConfig,
6 error::AgentError,
7 memory::{MemoryContent, MemoryStore},
8};
9
10pub struct FileMemoryStore {
11 memory_path: PathBuf,
12 profile_path: PathBuf,
13}
14
15impl FileMemoryStore {
16 pub fn new(home_dir: &Path) -> Self {
17 let memories = home_dir.join("memories");
18 Self {
19 memory_path: memories.join("MEMORY.md"),
20 profile_path: memories.join("USER.md"),
21 }
22 }
23
24 async fn read_file(&self, path: &PathBuf) -> Result<String, AgentError> {
25 match tokio::fs::read_to_string(path).await {
26 Ok(s) => Ok(s),
27 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(String::new()),
28 Err(e) => Err(AgentError::Other(anyhow::anyhow!("{e}"))),
29 }
30 }
31
32 async fn write_file(&self, path: &PathBuf, content: &str) -> Result<(), AgentError> {
33 if let Some(parent) = path.parent() {
34 tokio::fs::create_dir_all(parent)
35 .await
36 .map_err(|e| AgentError::Other(anyhow::anyhow!("{e}")))?;
37 }
38 tokio::fs::write(path, content)
39 .await
40 .map_err(|e| AgentError::Other(anyhow::anyhow!("{e}")))
41 }
42
43 pub async fn expire_entries(&self, config: &MemoryExpiryConfig) -> Result<usize, AgentError> {
46 let mut mem = self.read_memory().await?;
47 let removed = mem.expire(config);
48 if removed > 0 {
49 self.write_memory(&mem).await?;
50 }
51 Ok(removed)
52 }
53}
54
55#[async_trait]
56impl MemoryStore for FileMemoryStore {
57 async fn read_memory(&self) -> Result<MemoryContent, AgentError> {
58 let raw = self.read_file(&self.memory_path).await?;
59 Ok(MemoryContent::parse(&raw))
60 }
61
62 async fn write_memory(&self, content: &MemoryContent) -> Result<(), AgentError> {
63 self.write_file(&self.memory_path, &content.serialize())
64 .await
65 }
66
67 async fn read_user_profile(&self) -> Result<String, AgentError> {
68 self.read_file(&self.profile_path).await
69 }
70
71 async fn write_user_profile(&self, content: &str) -> Result<(), AgentError> {
72 self.write_file(&self.profile_path, content).await
73 }
74}