Skip to main content

mnemo_core/storage/
mod.rs

1pub mod cold;
2pub mod duckdb;
3pub mod migrations;
4
5use crate::error::Result;
6use crate::model::acl::{Acl, Permission};
7use crate::model::agent_profile::AgentProfile;
8use crate::model::checkpoint::Checkpoint;
9use crate::model::delegation::Delegation;
10use crate::model::embedding_baseline::EmbeddingBaseline;
11use crate::model::event::AgentEvent;
12use crate::model::memory::MemoryRecord;
13use crate::model::relation::Relation;
14use crate::model::write_provenance::WriteProvenance;
15use uuid::Uuid;
16
17#[derive(Debug, Clone, Default)]
18pub struct MemoryFilter {
19    pub agent_id: Option<String>,
20    pub memory_type: Option<crate::model::memory::MemoryType>,
21    pub scope: Option<crate::model::memory::Scope>,
22    pub tags: Option<Vec<String>>,
23    pub min_importance: Option<f32>,
24    pub org_id: Option<String>,
25    pub thread_id: Option<String>,
26    pub include_deleted: bool,
27}
28
29#[async_trait::async_trait]
30pub trait StorageBackend: Send + Sync {
31    // Memory CRUD
32    async fn insert_memory(&self, record: &MemoryRecord) -> Result<()>;
33    async fn get_memory(&self, id: Uuid) -> Result<Option<MemoryRecord>>;
34    async fn update_memory(&self, record: &MemoryRecord) -> Result<()>;
35    async fn soft_delete_memory(&self, id: Uuid) -> Result<()>;
36    async fn hard_delete_memory(&self, id: Uuid) -> Result<()>;
37    async fn list_memories(
38        &self,
39        filter: &MemoryFilter,
40        limit: usize,
41        offset: usize,
42    ) -> Result<Vec<MemoryRecord>>;
43    async fn touch_memory(&self, id: Uuid) -> Result<()>;
44
45    // ACL
46    async fn insert_acl(&self, acl: &Acl) -> Result<()>;
47    async fn check_permission(
48        &self,
49        memory_id: Uuid,
50        principal_id: &str,
51        required: Permission,
52    ) -> Result<bool>;
53
54    // Relations
55    async fn insert_relation(&self, relation: &Relation) -> Result<()>;
56    async fn get_relations_from(&self, source_id: Uuid) -> Result<Vec<Relation>>;
57    async fn get_relations_to(&self, target_id: Uuid) -> Result<Vec<Relation>>;
58    async fn delete_relation(&self, id: Uuid) -> Result<()>;
59
60    // Chain linking
61    async fn get_latest_memory_hash(
62        &self,
63        agent_id: &str,
64        thread_id: Option<&str>,
65    ) -> Result<Option<Vec<u8>>>;
66    async fn get_latest_event_hash(
67        &self,
68        agent_id: &str,
69        thread_id: Option<&str>,
70    ) -> Result<Option<Vec<u8>>>;
71
72    // Sync watermarks
73    async fn get_sync_watermark(&self, key: &str) -> Result<Option<String>>;
74    async fn set_sync_watermark(&self, key: &str, value: &str) -> Result<()>;
75
76    // Permission-safe ANN
77    async fn list_accessible_memory_ids(&self, agent_id: &str, limit: usize) -> Result<Vec<Uuid>>;
78
79    // Events
80    async fn insert_event(&self, event: &AgentEvent) -> Result<()>;
81    async fn list_events(
82        &self,
83        agent_id: &str,
84        limit: usize,
85        offset: usize,
86    ) -> Result<Vec<AgentEvent>>;
87    async fn get_events_by_thread(&self, thread_id: &str, limit: usize) -> Result<Vec<AgentEvent>>;
88    async fn get_event(&self, id: Uuid) -> Result<Option<AgentEvent>>;
89    async fn list_child_events(
90        &self,
91        parent_event_id: Uuid,
92        limit: usize,
93    ) -> Result<Vec<AgentEvent>>;
94
95    // Ordered listing for chain verification
96    async fn list_memories_by_agent_ordered(
97        &self,
98        agent_id: &str,
99        thread_id: Option<&str>,
100        limit: usize,
101    ) -> Result<Vec<MemoryRecord>>;
102
103    // Sync support
104    async fn list_memories_since(
105        &self,
106        updated_after: &str,
107        limit: usize,
108    ) -> Result<Vec<MemoryRecord>>;
109    async fn upsert_memory(&self, record: &MemoryRecord) -> Result<()>;
110
111    // Expired memory cleanup
112    async fn cleanup_expired(&self) -> Result<usize>;
113
114    // Delegations
115    async fn insert_delegation(&self, d: &Delegation) -> Result<()>;
116    async fn list_delegations_for(&self, delegate_id: &str) -> Result<Vec<Delegation>>;
117    async fn revoke_delegation(&self, id: Uuid) -> Result<()>;
118    async fn check_delegation(
119        &self,
120        delegate_id: &str,
121        memory_id: Uuid,
122        required: Permission,
123    ) -> Result<bool>;
124
125    // Agent Profiles
126    async fn insert_or_update_agent_profile(&self, profile: &AgentProfile) -> Result<()>;
127    async fn get_agent_profile(&self, agent_id: &str) -> Result<Option<AgentProfile>>;
128
129    // Embedding baselines (v0.3.3, z-score outlier detector)
130    async fn insert_or_update_embedding_baseline(&self, baseline: &EmbeddingBaseline)
131    -> Result<()>;
132    async fn get_embedding_baseline(&self, agent_id: &str) -> Result<Option<EmbeddingBaseline>>;
133
134    // Checkpoints
135    async fn insert_checkpoint(&self, cp: &Checkpoint) -> Result<()>;
136    async fn get_checkpoint(&self, id: Uuid) -> Result<Option<Checkpoint>>;
137    async fn list_checkpoints(
138        &self,
139        thread_id: &str,
140        branch: Option<&str>,
141        limit: usize,
142    ) -> Result<Vec<Checkpoint>>;
143    async fn get_latest_checkpoint(
144        &self,
145        thread_id: &str,
146        branch: &str,
147    ) -> Result<Option<Checkpoint>>;
148
149    // --- Write provenance (who wrote each memory, under what authority) -------
150    // A tamper-evident, hash-chained record per memory write. Default impls are
151    // graceful (no-op / empty) so a backend opts in by overriding them —
152    // provenance is recorded only where a backend implements it. DuckDB
153    // implements the full set; PostgreSQL support is tracked alongside.
154
155    /// Append a tamper-evident write-provenance record. Default: no-op.
156    async fn insert_write_provenance(&self, _prov: &WriteProvenance) -> Result<()> {
157        Ok(())
158    }
159    /// The provenance for one memory id, if recorded. Default: `None`.
160    async fn get_write_provenance(&self, _memory_id: Uuid) -> Result<Option<WriteProvenance>> {
161        Ok(None)
162    }
163    /// `content_hash` of the most recent provenance record (the chain head), or
164    /// `None` when the chain is empty. Used to link the next record. Default: `None`.
165    async fn get_latest_provenance_hash(&self) -> Result<Option<Vec<u8>>> {
166        Ok(None)
167    }
168    /// All provenance written by `principal`, newest first. Default: empty.
169    async fn list_provenance_by_principal(
170        &self,
171        _principal: &str,
172        _limit: usize,
173    ) -> Result<Vec<WriteProvenance>> {
174        Ok(Vec::new())
175    }
176    /// All provenance written under `session_id`, newest first. Default: empty.
177    async fn list_provenance_by_session(
178        &self,
179        _session_id: &str,
180        _limit: usize,
181    ) -> Result<Vec<WriteProvenance>> {
182        Ok(Vec::new())
183    }
184    /// Memory ids written by `principal` — the target set for FORGET BY
185    /// PROVENANCE. Default: empty.
186    async fn list_memory_ids_by_principal(&self, _principal: &str) -> Result<Vec<Uuid>> {
187        Ok(Vec::new())
188    }
189    /// Memory ids written under `session_id`. Default: empty.
190    async fn list_memory_ids_by_session(&self, _session_id: &str) -> Result<Vec<Uuid>> {
191        Ok(Vec::new())
192    }
193    /// The whole provenance chain, oldest-first (append order), for
194    /// tamper-evidence verification. Default: empty.
195    async fn list_all_provenance(&self, _limit: usize) -> Result<Vec<WriteProvenance>> {
196        Ok(Vec::new())
197    }
198
199    /// Whether this backend records write provenance (overridden to `true` by
200    /// backends that implement the methods above). Lets the write path skip the
201    /// provenance write on a backend that would no-op it.
202    fn records_write_provenance(&self) -> bool {
203        false
204    }
205
206    /// Short, stable label for the backend implementation (e.g. `"duckdb"`,
207    /// `"postgres"`). Used in diagnostics such as
208    /// [`crate::error::Error::EmbedderNotConfigured`] so an error names the
209    /// backend in play. Defaults to `"unknown"`; real backends override it.
210    fn backend_name(&self) -> &'static str {
211        "unknown"
212    }
213
214    /// Whether this backend guarantees the `agent_events` log is **append-only**
215    /// — no code path (and, where enforceable, no schema path) can delete or
216    /// rewrite an event row. Both shipped backends guarantee this: DuckDB has no
217    /// `DELETE`/`UPDATE` on `agent_events`, and PostgreSQL additionally enforces
218    /// it with a `prevent_event_modification` trigger. A retention-conformance
219    /// profile relies on this to promise a retention floor; a backend that
220    /// cannot honour it should override this to `false` so the profile fails
221    /// loud (see `mnemo-compliance`'s `RetentionProfile`). Defaults to `true`.
222    fn events_are_append_only(&self) -> bool {
223        true
224    }
225}