Skip to main content

apollo/memory/
traits.rs

1//! Core MemoryBackend trait.
2
3use async_trait::async_trait;
4use serde_json::Value;
5
6/// Memory entry
7#[derive(Debug, Clone)]
8pub struct MemoryEntry {
9    pub key: String,
10    pub value: String,
11    pub metadata: Option<Value>,
12    pub created_at: chrono::DateTime<chrono::Utc>,
13}
14
15/// Embedding entry (vector + source text)
16#[derive(Debug, Clone)]
17pub struct EmbeddingEntry {
18    pub namespace: String,
19    pub key: String,
20    pub vector: Vec<f32>,
21    pub text: String,
22    pub created_at: chrono::DateTime<chrono::Utc>,
23}
24
25/// Indexed file record
26#[derive(Debug, Clone)]
27pub struct FileIndex {
28    pub path: String,
29    pub hash: String,
30    pub last_indexed: chrono::DateTime<chrono::Utc>,
31}
32
33/// Code chunk with optional embedding
34#[derive(Debug, Clone)]
35pub struct Chunk {
36    pub file_path: String,
37    pub start_line: u32,
38    pub end_line: u32,
39    pub content: String,
40    pub embedding: Option<Vec<f32>>,
41    pub created_at: chrono::DateTime<chrono::Utc>,
42}
43
44#[derive(Debug, Clone)]
45pub struct ConversationSearchHit {
46    pub chat_id: String,
47    pub role: String,
48    pub content: String,
49    pub created_at: chrono::DateTime<chrono::Utc>,
50}
51
52/// The core MemoryBackend trait.
53#[async_trait]
54pub trait MemoryBackend: Send + Sync {
55    /// For downcasting to concrete types
56    fn as_any(&self) -> &dyn std::any::Any;
57
58    /// Store a key-value memory
59    async fn store(
60        &self,
61        namespace: &str,
62        key: &str,
63        value: &str,
64        metadata: Option<Value>,
65    ) -> anyhow::Result<()>;
66
67    /// Recall a specific memory by key
68    async fn recall(&self, namespace: &str, key: &str) -> anyhow::Result<Option<MemoryEntry>>;
69
70    /// Search memories by query (semantic or keyword)
71    async fn search(
72        &self,
73        namespace: &str,
74        query: &str,
75        limit: usize,
76    ) -> anyhow::Result<Vec<MemoryEntry>>;
77
78    /// Delete a memory
79    async fn forget(&self, namespace: &str, key: &str) -> anyhow::Result<()>;
80
81    /// List all memories in a namespace
82    async fn list(&self, namespace: &str) -> anyhow::Result<Vec<MemoryEntry>>;
83
84    /// Store a conversation message
85    async fn store_conversation(
86        &self,
87        chat_id: &str,
88        sender_id: &str,
89        role: &str,
90        content: &str,
91    ) -> anyhow::Result<()>;
92
93    /// Store multiple conversation messages in one batch.
94    async fn store_conversation_batch(
95        &self,
96        entries: &[(&str, &str, &str, &str)],
97    ) -> anyhow::Result<()> {
98        for (chat_id, sender_id, role, content) in entries {
99            self.store_conversation(chat_id, sender_id, role, content)
100                .await?;
101        }
102        Ok(())
103    }
104
105    /// Get recent conversation history (returns role, content pairs)
106    async fn get_conversation_history(
107        &self,
108        chat_id: &str,
109        limit: usize,
110    ) -> anyhow::Result<Vec<(String, String)>>;
111
112    /// Delete every stored message for one chat.
113    ///
114    /// Backends opt in: the default errors rather than returning `Ok`, so a
115    /// caller is never told a conversation was cleared when nothing was.
116    async fn clear_conversation(&self, chat_id: &str) -> anyhow::Result<()> {
117        let _ = chat_id;
118        anyhow::bail!("clearing a conversation is unsupported by this memory backend")
119    }
120
121    async fn search_conversations(
122        &self,
123        query: &str,
124        limit: usize,
125        chat_id: Option<&str>,
126    ) -> anyhow::Result<Vec<ConversationSearchHit>> {
127        let _ = (query, limit, chat_id);
128        Ok(Vec::new())
129    }
130
131    /// Get cached sticker description by ID
132    async fn get_sticker_cache(&self, sticker_id: &str) -> anyhow::Result<Option<String>>;
133
134    /// Store sticker cache (sticker_id → description)
135    async fn store_sticker_cache(
136        &self,
137        sticker_id: &str,
138        file_id: &str,
139        description: &str,
140    ) -> anyhow::Result<()>;
141
142    // ── Embeddings ──
143
144    /// Store a vector embedding for a memory key
145    ///
146    /// `model` identifies the embedding model that produced `vector`. It is
147    /// persisted alongside the vector's dimension so that later searches can
148    /// exclude rows that are not comparable with the current query vector.
149    async fn store_embedding(
150        &self,
151        namespace: &str,
152        key: &str,
153        vector: &[f32],
154        text: &str,
155        model: &str,
156    ) -> anyhow::Result<()> {
157        let _ = (namespace, key, vector, text, model);
158        Ok(())
159    }
160
161    /// Search embeddings by cosine similarity (returns nearest matches)
162    ///
163    /// Only rows whose recorded dimension matches `query_vector.len()` are
164    /// considered; vectors of a different dimension are not comparable and are
165    /// never returned.
166    async fn search_embeddings(
167        &self,
168        namespace: &str,
169        query_vector: &[f32],
170        limit: usize,
171    ) -> anyhow::Result<Vec<EmbeddingEntry>> {
172        let _ = (namespace, query_vector, limit);
173        Ok(Vec::new())
174    }
175
176    // ── File indexing ──
177
178    /// Record that a file has been indexed
179    async fn store_file_index(&self, path: &str, hash: &str) -> anyhow::Result<()> {
180        let _ = (path, hash);
181        Ok(())
182    }
183
184    /// Get file index entry (to check if re-indexing is needed)
185    async fn get_file_index(&self, path: &str) -> anyhow::Result<Option<FileIndex>> {
186        let _ = path;
187        Ok(None)
188    }
189
190    // ── Code chunks ──
191
192    /// Store a code chunk with optional embedding
193    async fn store_chunk(
194        &self,
195        file_path: &str,
196        start_line: u32,
197        end_line: u32,
198        content: &str,
199        embedding: Option<&[f32]>,
200    ) -> anyhow::Result<()> {
201        let _ = (file_path, start_line, end_line, content, embedding);
202        Ok(())
203    }
204
205    /// Search chunks by file path
206    async fn get_chunks_for_file(&self, file_path: &str) -> anyhow::Result<Vec<Chunk>> {
207        let _ = file_path;
208        Ok(Vec::new())
209    }
210
211    /// Delete all chunks for a file (before re-indexing)
212    async fn delete_chunks_for_file(&self, file_path: &str) -> anyhow::Result<()> {
213        let _ = file_path;
214        Ok(())
215    }
216}