pub trait LoopMemory: Send + Sync {
// Required methods
fn store(
&self,
entry: MemoryEntry,
) -> impl Future<Output = Result<(), LoopError>> + Send;
fn retrieve(
&self,
query: &str,
limit: usize,
) -> impl Future<Output = Result<Vec<MemoryEntry>, LoopError>> + Send;
fn consolidate(
&self,
) -> impl Future<Output = Result<ConsolidationStats, LoopError>> + Send;
fn len(&self) -> usize;
// Provided method
fn is_empty(&self) -> bool { ... }
}Expand description
A memory system for loops.
Implementations can store and retrieve entries using different strategies (vector similarity, keyword matching, recency, etc.).
§Implementing
At a minimum you must provide store,
retrieve, consolidate,
and len. The trait supplies a default
is_empty implementation that delegates to len.
§Example
use loopctl::memory::{LoopMemory, MemoryEntry, MemoryCategory, ConsolidationStats};
use loopctl::error::LoopError;
use std::sync::RwLock;
struct MyStore {
entries: RwLock<Vec<MemoryEntry>>,
}
impl LoopMemory for MyStore {
fn store(&self, entry: MemoryEntry)
-> impl Future<Output = Result<(), LoopError>> + Send
{
async move {
self.entries.write().unwrap().push(entry);
Ok(())
}
}
fn retrieve(&self, query: &str, limit: usize)
-> impl Future<Output = Result<Vec<MemoryEntry>, LoopError>> + Send
{
let query = query.to_string();
async move {
let entries = self.entries.read().unwrap();
Ok(entries.iter()
.filter(|e| e.memory.contains(&query))
.take(limit)
.cloned()
.collect())
}
}
fn consolidate(&self)
-> impl Future<Output = Result<ConsolidationStats, LoopError>> + Send
{
async move {
let mut entries = self.entries.write().unwrap();
let before = entries.len();
entries.retain(|e| e.relevance > 0.1);
let after = entries.len();
Ok(ConsolidationStats {
entries_before: before,
entries_after: after,
pruned: before - after,
..Default::default()
})
}
}
fn len(&self) -> usize {
self.entries.read().unwrap().len()
}
}Required Methods§
Sourcefn store(
&self,
entry: MemoryEntry,
) -> impl Future<Output = Result<(), LoopError>> + Send
fn store( &self, entry: MemoryEntry, ) -> impl Future<Output = Result<(), LoopError>> + Send
Store a new memory entry.
Called whenever the agent encounters information worth remembering — for example after a successful tool invocation, a resolved error, or an insight drawn from conversation. Implementations should persist the entry in whatever backing store they use.
Takes &self so that memory stores can be shared via Arc<impl LoopMemory>.
Implementations that need interior mutability (e.g. an in-memory Vec)
should use Mutex, RwLock, or lock-free structures internally.
Sourcefn retrieve(
&self,
query: &str,
limit: usize,
) -> impl Future<Output = Result<Vec<MemoryEntry>, LoopError>> + Send
fn retrieve( &self, query: &str, limit: usize, ) -> impl Future<Output = Result<Vec<MemoryEntry>, LoopError>> + Send
Retrieve memory entries relevant to the given query.
Called before each turn (or on demand) to surface context the agent
can use. Returns up to limit entries ordered by relevance. The
definition of “relevance” is left to the implementation — common
strategies include vector embedding similarity, keyword overlap,
recency weighting, or a hybrid approach.
Implementations that track MemoryEntry::access_count must use
interior mutability (e.g. AtomicUsize, Mutex) since this method
takes &self.
Sourcefn consolidate(
&self,
) -> impl Future<Output = Result<ConsolidationStats, LoopError>> + Send
fn consolidate( &self, ) -> impl Future<Output = Result<ConsolidationStats, LoopError>> + Send
Consolidate memory (e.g. prune, summarize, compress).
Called periodically to keep the memory store healthy. Implementations
may remove low-relevance entries, merge duplicates, or produce
compressed summaries. Returns ConsolidationStats describing what
was done.
Takes &self so that memory stores can be shared via Arc<impl LoopMemory>.
Implementations should use interior mutability as needed.
Provided Methods§
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".