Skip to main content

roma_core/
memory.rs

1//! Memory store trait and types.
2//!
3//! The trait and its supporting types live in `roma-core` so that
4//! crates like `roma-tools` can depend on the trait without pulling
5//! in the full file-backed implementation from `roma-memory`.
6
7use async_trait::async_trait;
8
9use crate::PathError;
10
11/// Memory level.
12///
13/// | Level | Purpose | Persistence |
14/// |-------|---------|-------------|
15/// | L0 | Meta-rules, system directives | Permanent |
16/// | L1 | Short-term working memory | Per-session |
17/// | L2 | Session summaries *(reserved)* | Planned |
18/// | L3 | Long-term distilled knowledge | Permanent |
19/// | L4 | Archived sessions | Permanent |
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub enum MemoryLevel {
22    L0,
23    L1,
24    /// Reserved for session-level summaries. Infrastructure (directory layout,
25    /// validators) exists but no agent logic writes to L2 yet.
26    L2,
27    L3,
28    L4,
29}
30
31impl MemoryLevel {
32    /// Directory name for this level.
33    pub fn dir_name(self) -> &'static str {
34        match self {
35            Self::L0 => "L0",
36            Self::L1 => "L1",
37            Self::L2 => "L2",
38            Self::L3 => "L3",
39            Self::L4 => "L4",
40        }
41    }
42}
43
44/// Memory store errors.
45#[derive(Debug, thiserror::Error)]
46pub enum MemoryError {
47    #[error("patch failed: {0}")]
48    Patch(#[from] PatchError),
49    #[error("validation failed at level {level:?}: {reason}")]
50    ValidationFailed { level: MemoryLevel, reason: String },
51    #[error("not found: {0}")]
52    NotFound(String),
53    #[error("path denied: {0}")]
54    PathDenied(String),
55    #[error("io: {0}")]
56    Io(#[from] std::io::Error),
57}
58
59/// Uniqueness-checked patch errors.
60///
61/// Shared by `FilePatchTool` and [`MemoryStore::patch`].
62#[derive(Debug, Clone, thiserror::Error)]
63pub enum PatchError {
64    #[error("patch not found: old_content absent")]
65    NotFound,
66    #[error("patch not unique: found {count} matches, expected 1")]
67    NotUnique { count: usize },
68}
69
70impl From<PathError> for MemoryError {
71    fn from(err: PathError) -> Self {
72        match err {
73            PathError::Io(e) => Self::Io(e),
74            other => Self::PathDenied(other.to_string()),
75        }
76    }
77}
78
79/// Pluggable memory backend.
80#[async_trait]
81pub trait MemoryStore: Send + Sync {
82    /// Read a file from the given path within the memory store.
83    async fn read(&self, path: &str) -> Result<String, MemoryError>;
84    /// Write content to the given path within the memory store.
85    async fn write(&self, path: &str, content: &str) -> Result<(), MemoryError>;
86    /// Patch a file by replacing `old` with `new`.
87    async fn patch(&self, path: &str, old: &str, new: &str) -> Result<(), MemoryError>;
88    /// Delete a file from the memory store.
89    async fn delete(&self, path: &str) -> Result<(), MemoryError>;
90    /// List all files at the given memory level.
91    async fn list(&self, level: MemoryLevel) -> Result<Vec<String>, MemoryError>;
92    /// Validate content for the given memory level.
93    fn validate(&self, level: MemoryLevel, content: &str) -> Result<(), MemoryError>;
94    /// Read a file from the memory store root directory.
95    async fn read_root(&self, name: &str) -> Result<String, MemoryError>;
96}
97
98/// No-op implementation used in tests that don't need real persistence.
99#[derive(Debug, Default, Clone)]
100pub struct NullMemoryStore;
101
102#[async_trait]
103impl MemoryStore for NullMemoryStore {
104    async fn read(&self, path: &str) -> Result<String, MemoryError> {
105        Err(MemoryError::NotFound(path.to_string()))
106    }
107
108    async fn write(&self, _path: &str, _content: &str) -> Result<(), MemoryError> {
109        Ok(())
110    }
111
112    async fn patch(&self, path: &str, _old: &str, _new: &str) -> Result<(), MemoryError> {
113        Err(MemoryError::NotFound(path.to_string()))
114    }
115
116    async fn delete(&self, _path: &str) -> Result<(), MemoryError> {
117        Ok(())
118    }
119
120    async fn list(&self, _level: MemoryLevel) -> Result<Vec<String>, MemoryError> {
121        Ok(Vec::new())
122    }
123
124    fn validate(&self, _level: MemoryLevel, _content: &str) -> Result<(), MemoryError> {
125        Ok(())
126    }
127
128    async fn read_root(&self, name: &str) -> Result<String, MemoryError> {
129        Err(MemoryError::NotFound(name.to_string()))
130    }
131}