Memory trait provides a simple async interface for storing and recalling agent memories.
Overview
Memory backends implement theMemory trait:
Step-by-Step Guide
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Mutex;
use crate::memory::traits::{Memory, MemoryEntry, MemoryCategory};
/// In-memory HashMap backend (great for testing or ephemeral sessions)
pub struct InMemoryBackend {
store: Mutex<HashMap<String, MemoryEntry>>,
}
impl Default for InMemoryBackend {
fn default() -> Self {
Self {
store: Mutex::new(HashMap::new()),
}
}
}
impl InMemoryBackend {
pub fn new() -> Self {
Self::default()
}
}
async fn store(
&self,
key: &str,
content: &str,
category: MemoryCategory,
) -> anyhow::Result<()> {
let entry = MemoryEntry {
id: uuid::Uuid::new_v4().to_string(),
key: key.to_string(),
content: content.to_string(),
category,
timestamp: chrono::Local::now().to_rfc3339(),
score: None,
};
self.store
.lock()
.map_err(|e| anyhow::anyhow!("{e}"))?
.insert(key.to_string(), entry);
Ok(())
}
async fn recall(&self, query: &str, limit: usize) -> anyhow::Result<Vec<MemoryEntry>> {
let store = self.store.lock().map_err(|e| anyhow::anyhow!("{e}"))?;
let query_lower = query.to_lowercase();
let mut results: Vec<MemoryEntry> = store
.values()
.filter(|e| e.content.to_lowercase().contains(&query_lower))
.cloned()
.collect();
results.truncate(limit);
Ok(results)
}
async fn get(&self, key: &str) -> anyhow::Result<Option<MemoryEntry>> {
let store = self.store.lock().map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(store.get(key).cloned())
}
async fn forget(&self, key: &str) -> anyhow::Result<bool> {
let mut store = self.store.lock().map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(store.remove(key).is_some())
}
async fn count(&self) -> anyhow::Result<usize> {
let store = self.store.lock().map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(store.len())
}
pub fn create_memory(backend: &str) -> Result<Box<dyn Memory>> {
match backend {
"markdown" => Ok(Box::new(MarkdownMemory::new())),
"sqlite" => Ok(Box::new(SqliteMemory::new())),
"in-memory" => Ok(Box::new(InMemoryBackend::new())),
_ => anyhow::bail!("Unknown memory backend: {}", backend),
}
}
Complete Example
Here’s the full implementation fromexamples/custom_memory.rs:
Advanced Backends
Redis Backend
PostgreSQL Backend with Vector Search
Best Practices
Use connection pooling
Use connection pooling
Avoid creating new connections for each operation:
Implement proper error handling
Implement proper error handling
Return descriptive errors:
Add indexes for search performance
Add indexes for search performance
For SQL backends, index searchable fields:
Support batch operations
Support batch operations
Optimize for bulk inserts/queries:
Test with realistic data volumes
Test with realistic data volumes
Benchmark your backend:
Next Steps
Gateway Setup
Expose your agent via HTTP gateway
Deployment
Deploy ZeroClaw to production