rig_memvid/memory_graph.rs
1//! Backend-agnostic structured-memory abstraction.
2//!
3//! [`MemoryGraph`] is the read-side trait that
4//! [`crate::MemoryCardContext`] (and any future graph-aware adapters) is
5//! generic over. It models a memory store as a typed entity-relationship
6//! graph: nodes are *entities* (subjects), edges are *slots* (predicates)
7//! pointing at *values* (objects), each carrying polarity, timestamps,
8//! and provenance.
9//!
10//! # Why a trait
11//!
12//! Memvid is the most complete implementation today (it ships frames +
13//! Logic-Mesh + memory cards in a single `.mv2` file), but the same
14//! abstraction fits Neo4j (graph-native), Postgres / SQLite (cards
15//! table), and the in-memory test fake here. Keeping the trait local
16//! to `rig-memvid` for now is deliberate — the next backend that
17//! implements it will tell us where the upstream home should be (most
18//! likely `rig-core::memory` next to
19//! [`rig::vector_store::VectorStoreIndex`]).
20//!
21//! # Semantics
22//!
23//! - All methods are **read-only** and **synchronous**: implementations
24//! are expected to be backed by an in-memory snapshot, a memory-mapped
25//! file, or a thread-pool-bound database connection. Async backends
26//! should `block_in_place` or expose a separate async trait above
27//! this one.
28//! - The error type is implementation-defined but must convert into
29//! [`rig::vector_store::VectorStoreError`] so adapters can surface
30//! failures uniformly.
31//! - `entity_memories` returning an empty `Vec` is **not** an error;
32//! unknown entities are valid.
33//!
34//! # Implementing for a new backend
35//!
36//! ```rust,no_run
37//! use memvid_core::MemoryCard;
38//! use rig::vector_store::VectorStoreError;
39//! use rig_memvid::MemoryGraph;
40//!
41//! struct PgGraph; // imagine a postgres-backed cards table
42//!
43//! impl MemoryGraph for PgGraph {
44//! type Error = VectorStoreError;
45//! fn memory_card_count(&self) -> Result<usize, Self::Error> { Ok(0) }
46//! fn all_memory_cards(&self) -> Result<Vec<MemoryCard>, Self::Error> { Ok(vec![]) }
47//! fn entity_memories(&self, _: &str) -> Result<Vec<MemoryCard>, Self::Error> { Ok(vec![]) }
48//! fn current_memory(&self, _: &str, _: &str) -> Result<Option<MemoryCard>, Self::Error> { Ok(None) }
49//! fn entity_preferences(&self, _: &str) -> Result<Vec<MemoryCard>, Self::Error> { Ok(vec![]) }
50//! fn memory_timeline(&self, _: &str) -> Result<Vec<MemoryCard>, Self::Error> { Ok(vec![]) }
51//! }
52//! ```
53
54use memvid_core::MemoryCard;
55
56/// Read-side abstraction over a structured-memory store.
57///
58/// Implementations expose entity / slot / value cards with versioning,
59/// polarity, and provenance. The default
60/// [`crate::MemvidStore`] implementation backs all six methods with the
61/// memvid `.mv2` memories track.
62pub trait MemoryGraph {
63 /// Error returned by graph queries. Must be convertible to
64 /// [`rig::vector_store::VectorStoreError`] when used with an
65 /// adapter that exposes a [`rig::vector_store::VectorStoreIndex`]
66 /// (such as [`crate::MemoryCardContext`]).
67 type Error: Into<rig::vector_store::VectorStoreError>;
68
69 /// Total number of cards currently stored.
70 fn memory_card_count(&self) -> Result<usize, Self::Error>;
71
72 /// Snapshot of every card. Used by selection strategies that need
73 /// to filter / sort across the whole set.
74 fn all_memory_cards(&self) -> Result<Vec<MemoryCard>, Self::Error>;
75
76 /// All cards for `entity`. Empty `Vec` for unknown entities.
77 fn entity_memories(&self, entity: &str) -> Result<Vec<MemoryCard>, Self::Error>;
78
79 /// Most recent non-retracted value for `entity`/`slot`, if any.
80 fn current_memory(&self, entity: &str, slot: &str) -> Result<Option<MemoryCard>, Self::Error>;
81
82 /// Preference-kind cards for `entity`.
83 fn entity_preferences(&self, entity: &str) -> Result<Vec<MemoryCard>, Self::Error>;
84
85 /// Event-kind cards for `entity` in chronological order.
86 fn memory_timeline(&self, entity: &str) -> Result<Vec<MemoryCard>, Self::Error>;
87
88 /// Cards whose `entity` mentions appear in `query` (case-insensitive
89 /// whole-word match). The default implementation snapshots the entire
90 /// archive via [`MemoryGraph::all_memory_cards`] and filters in pure
91 /// Rust, which is correct but clones every card; backends backed by a
92 /// graph store should override this to filter behind their own
93 /// locking / indexing and avoid the intermediate full-archive
94 /// allocation.
95 fn cards_for_query(&self, query: &str) -> Result<Vec<MemoryCard>, Self::Error> {
96 let needle = query.to_lowercase();
97 let all = self.all_memory_cards()?;
98 Ok(all
99 .into_iter()
100 .filter(|card| {
101 let entity = card.entity.to_lowercase();
102 !entity.is_empty() && crate::cards_context::contains_word(&needle, &entity)
103 })
104 .collect())
105 }
106}
107
108impl MemoryGraph for crate::MemvidStore {
109 type Error = crate::MemvidError;
110
111 fn memory_card_count(&self) -> Result<usize, Self::Error> {
112 Self::memory_card_count(self)
113 }
114
115 fn all_memory_cards(&self) -> Result<Vec<MemoryCard>, Self::Error> {
116 Self::all_memory_cards(self)
117 }
118
119 fn entity_memories(&self, entity: &str) -> Result<Vec<MemoryCard>, Self::Error> {
120 Self::entity_memories(self, entity)
121 }
122
123 fn current_memory(&self, entity: &str, slot: &str) -> Result<Option<MemoryCard>, Self::Error> {
124 Self::current_memory(self, entity, slot)
125 }
126
127 fn entity_preferences(&self, entity: &str) -> Result<Vec<MemoryCard>, Self::Error> {
128 Self::entity_preferences(self, entity)
129 }
130
131 fn memory_timeline(&self, entity: &str) -> Result<Vec<MemoryCard>, Self::Error> {
132 Self::memory_timeline(self, entity)
133 }
134
135 fn cards_for_query(&self, query: &str) -> Result<Vec<MemoryCard>, Self::Error> {
136 Self::cards_for_query(self, query)
137 }
138}