enki_runtime/config/memory_config.rs
1//! Memory backend configuration from TOML files.
2
3use serde::{Deserialize, Serialize};
4
5/// Memory backend type.
6///
7/// Specifies which memory backend implementation to use.
8#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
9#[serde(rename_all = "lowercase")]
10pub enum MemoryBackendType {
11 /// In-memory storage (fast, no persistence)
12 #[default]
13 InMemory,
14 /// SQLite-based storage (persistent, requires 'sqlite' feature)
15 Sqlite,
16 /// Redis-based storage (distributed, requires 'redis' feature)
17 Redis,
18 /// Qdrant vector database (requires 'qdrant' feature)
19 Qdrant,
20 /// ChromaDB vector database (requires 'chromadb' feature)
21 #[serde(alias = "chroma")]
22 ChromaDB,
23 /// Pinecone vector database (requires 'pinecone' feature)
24 Pinecone,
25}
26
27/// Configuration for memory backends.
28///
29/// This struct can be used at the mesh level (shared by all agents) or
30/// at the agent level (per-agent configuration).
31///
32/// # Examples
33///
34/// ## InMemory (default)
35/// ```toml
36/// [memory]
37/// backend = "inmemory"
38/// max_entries = 500
39/// ttl_seconds = 3600
40/// ```
41///
42/// ## SQLite (persistent)
43/// ```toml
44/// [memory]
45/// backend = "sqlite"
46/// path = "./data/knowledge.db"
47/// ```
48///
49/// ## Redis (distributed)
50/// ```toml
51/// [memory]
52/// backend = "redis"
53/// url = "redis://localhost:6379"
54/// prefix = "myapp"
55/// ttl_seconds = 7200
56/// ```
57///
58/// ## Qdrant (vector database)
59/// ```toml
60/// [memory]
61/// backend = "qdrant"
62/// grpc_url = "http://localhost:6334"
63/// collection = "agent_memory"
64/// dimensions = 384
65/// embedding_model = "ollama::mxbai-embed-large"
66/// ```
67///
68/// ## ChromaDB (vector database)
69/// ```toml
70/// [memory]
71/// backend = "chromadb"
72/// url = "http://localhost:8000"
73/// collection = "agent_memory"
74/// use_server_embeddings = true
75/// ```
76///
77/// ## Pinecone (cloud vector database)
78/// ```toml
79/// [memory]
80/// backend = "pinecone"
81/// api_key = "${PINECONE_API_KEY}"
82/// collection = "agent-memory"
83/// dimensions = 1536
84/// ```
85#[derive(Debug, Clone, Serialize, Deserialize, Default)]
86pub struct MemoryConfig {
87 /// Backend type: "inmemory", "sqlite", "redis", "qdrant", "chromadb", or "pinecone"
88 #[serde(default)]
89 pub backend: MemoryBackendType,
90
91 /// Path for SQLite database file.
92 /// Use ":memory:" for in-memory SQLite.
93 /// Only used when backend = "sqlite".
94 #[serde(default)]
95 pub path: Option<String>,
96
97 /// Connection URL for Redis or ChromaDB.
98 /// Example: "redis://localhost:6379" or "http://localhost:8000"
99 /// Used when backend = "redis" or "chromadb".
100 #[serde(default)]
101 pub url: Option<String>,
102
103 /// GRPC URL for Qdrant.
104 /// Example: "http://localhost:6334"
105 /// Only used when backend = "qdrant".
106 #[serde(default)]
107 pub grpc_url: Option<String>,
108
109 /// Collection/index name for vector databases.
110 /// Used by Qdrant, ChromaDB, and Pinecone.
111 #[serde(default)]
112 pub collection: Option<String>,
113
114 /// Vector dimensions for embeddings.
115 /// Required for Qdrant and Pinecone when creating new collections.
116 #[serde(default)]
117 pub dimensions: Option<usize>,
118
119 /// API key for cloud services (Pinecone, etc.).
120 /// Can use environment variable syntax: "${PINECONE_API_KEY}"
121 #[serde(default)]
122 pub api_key: Option<String>,
123
124 /// Embedding model identifier (e.g., "ollama::mxbai-embed-large").
125 /// Used for generating embeddings if not using server-side embeddings.
126 #[serde(default)]
127 pub embedding_model: Option<String>,
128
129 /// Whether to use the vector DB's server-side embedding feature.
130 /// ChromaDB supports this with configured embedding functions.
131 #[serde(default)]
132 pub use_server_embeddings: Option<bool>,
133
134 /// Key prefix for Redis namespace isolation.
135 /// Only used when backend = "redis".
136 #[serde(default)]
137 pub prefix: Option<String>,
138
139 /// Maximum number of entries for InMemory backend.
140 /// When exceeded, oldest entries are evicted.
141 /// Only used when backend = "inmemory".
142 #[serde(default)]
143 pub max_entries: Option<usize>,
144
145 /// Default TTL (time-to-live) in seconds for entries.
146 /// Entries will be automatically removed after this time.
147 /// Supported by all backends.
148 #[serde(default)]
149 pub ttl_seconds: Option<i64>,
150}
151
152impl MemoryConfig {
153 /// Create a new default memory config (InMemory backend).
154 pub fn new() -> Self {
155 Self::default()
156 }
157
158 /// Create an InMemory backend configuration.
159 pub fn in_memory() -> Self {
160 Self {
161 backend: MemoryBackendType::InMemory,
162 ..Default::default()
163 }
164 }
165
166 /// Create a SQLite backend configuration.
167 pub fn sqlite(path: impl Into<String>) -> Self {
168 Self {
169 backend: MemoryBackendType::Sqlite,
170 path: Some(path.into()),
171 ..Default::default()
172 }
173 }
174
175 /// Create a Redis backend configuration.
176 pub fn redis(url: impl Into<String>) -> Self {
177 Self {
178 backend: MemoryBackendType::Redis,
179 url: Some(url.into()),
180 ..Default::default()
181 }
182 }
183
184 /// Create a Qdrant backend configuration.
185 ///
186 /// # Arguments
187 /// * `grpc_url` - Qdrant gRPC URL (e.g., "http://localhost:6334")
188 /// * `collection` - Collection name
189 /// * `dimensions` - Vector dimensions
190 pub fn qdrant(
191 grpc_url: impl Into<String>,
192 collection: impl Into<String>,
193 dimensions: usize,
194 ) -> Self {
195 Self {
196 backend: MemoryBackendType::Qdrant,
197 grpc_url: Some(grpc_url.into()),
198 collection: Some(collection.into()),
199 dimensions: Some(dimensions),
200 ..Default::default()
201 }
202 }
203
204 /// Create a ChromaDB backend configuration.
205 ///
206 /// # Arguments
207 /// * `url` - ChromaDB URL (e.g., "http://localhost:8000")
208 /// * `collection` - Collection name
209 pub fn chromadb(url: impl Into<String>, collection: impl Into<String>) -> Self {
210 Self {
211 backend: MemoryBackendType::ChromaDB,
212 url: Some(url.into()),
213 collection: Some(collection.into()),
214 ..Default::default()
215 }
216 }
217
218 /// Create a Pinecone backend configuration.
219 ///
220 /// # Arguments
221 /// * `api_key` - Pinecone API key
222 /// * `collection` - Index name
223 /// * `dimensions` - Vector dimensions
224 pub fn pinecone(
225 api_key: impl Into<String>,
226 collection: impl Into<String>,
227 dimensions: usize,
228 ) -> Self {
229 Self {
230 backend: MemoryBackendType::Pinecone,
231 api_key: Some(api_key.into()),
232 collection: Some(collection.into()),
233 dimensions: Some(dimensions),
234 ..Default::default()
235 }
236 }
237
238 /// Set max entries limit.
239 pub fn with_max_entries(mut self, max: usize) -> Self {
240 self.max_entries = Some(max);
241 self
242 }
243
244 /// Set default TTL in seconds.
245 pub fn with_ttl_seconds(mut self, seconds: i64) -> Self {
246 self.ttl_seconds = Some(seconds);
247 self
248 }
249
250 /// Set Redis key prefix.
251 pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
252 self.prefix = Some(prefix.into());
253 self
254 }
255
256 /// Set collection/index name.
257 pub fn with_collection(mut self, collection: impl Into<String>) -> Self {
258 self.collection = Some(collection.into());
259 self
260 }
261
262 /// Set vector dimensions.
263 pub fn with_dimensions(mut self, dimensions: usize) -> Self {
264 self.dimensions = Some(dimensions);
265 self
266 }
267
268 /// Set embedding model.
269 pub fn with_embedding_model(mut self, model: impl Into<String>) -> Self {
270 self.embedding_model = Some(model.into());
271 self
272 }
273
274 /// Enable or disable server-side embeddings.
275 pub fn with_server_embeddings(mut self, enabled: bool) -> Self {
276 self.use_server_embeddings = Some(enabled);
277 self
278 }
279
280 /// Set API key for cloud services.
281 pub fn with_api_key(mut self, api_key: impl Into<String>) -> Self {
282 self.api_key = Some(api_key.into());
283 self
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn test_parse_inmemory_config() {
293 let toml = r#"
294 backend = "inmemory"
295 max_entries = 500
296 ttl_seconds = 3600
297 "#;
298
299 let config: MemoryConfig = toml::from_str(toml).unwrap();
300 assert_eq!(config.backend, MemoryBackendType::InMemory);
301 assert_eq!(config.max_entries, Some(500));
302 assert_eq!(config.ttl_seconds, Some(3600));
303 }
304
305 #[test]
306 fn test_parse_sqlite_config() {
307 let toml = r#"
308 backend = "sqlite"
309 path = "./data/knowledge.db"
310 "#;
311
312 let config: MemoryConfig = toml::from_str(toml).unwrap();
313 assert_eq!(config.backend, MemoryBackendType::Sqlite);
314 assert_eq!(config.path, Some("./data/knowledge.db".to_string()));
315 }
316
317 #[test]
318 fn test_parse_redis_config() {
319 let toml = r#"
320 backend = "redis"
321 url = "redis://localhost:6379"
322 prefix = "myapp"
323 ttl_seconds = 7200
324 "#;
325
326 let config: MemoryConfig = toml::from_str(toml).unwrap();
327 assert_eq!(config.backend, MemoryBackendType::Redis);
328 assert_eq!(config.url, Some("redis://localhost:6379".to_string()));
329 assert_eq!(config.prefix, Some("myapp".to_string()));
330 assert_eq!(config.ttl_seconds, Some(7200));
331 }
332
333 #[test]
334 fn test_default_is_inmemory() {
335 let toml = "";
336 let config: MemoryConfig = toml::from_str(toml).unwrap();
337 assert_eq!(config.backend, MemoryBackendType::InMemory);
338 }
339
340 #[test]
341 fn test_builder_methods() {
342 let config = MemoryConfig::in_memory()
343 .with_max_entries(1000)
344 .with_ttl_seconds(3600);
345
346 assert_eq!(config.backend, MemoryBackendType::InMemory);
347 assert_eq!(config.max_entries, Some(1000));
348 assert_eq!(config.ttl_seconds, Some(3600));
349
350 let config = MemoryConfig::redis("redis://localhost:6379")
351 .with_prefix("test")
352 .with_ttl_seconds(7200);
353
354 assert_eq!(config.backend, MemoryBackendType::Redis);
355 assert_eq!(config.url, Some("redis://localhost:6379".to_string()));
356 assert_eq!(config.prefix, Some("test".to_string()));
357 }
358}