walrus_core/memory/mod.rs
1//! Memory abstractions for Walrus agents.
2//!
3//! Defines the [`Memory`] trait, [`Embedder`] trait, and associated types.
4//! Concrete implementations (`InMemory`, `SqliteMemory`) live in `walrus-memory`.
5//!
6//! Memory is **not chat history**. It is structured knowledge — extracted facts,
7//! user preferences, agent persona — that gets compiled into the system prompt.
8
9use anyhow::Result;
10use compact_str::CompactString;
11pub use embedder::{Embedder, NoEmbedder};
12use serde_json::Value;
13use std::future::Future;
14
15pub mod embedder;
16pub mod tools;
17
18/// Structured knowledge memory for LLM agents.
19///
20/// Implementations store named key-value pairs that get compiled
21/// into the system prompt via [`compile()`](Memory::compile).
22///
23/// Uses `&self` for all methods — implementations must handle
24/// interior mutability (e.g. via `Mutex`).
25pub trait Memory: Send + Sync {
26 /// Get the value for a key (owned).
27 fn get(&self, key: &str) -> Option<String>;
28
29 /// Get all key-value pairs (owned).
30 fn entries(&self) -> Vec<(String, String)>;
31
32 /// Set (upsert) a key-value pair. Returns the previous value if the key existed.
33 fn set(&self, key: impl Into<String>, value: impl Into<String>) -> Option<String>;
34
35 /// Remove a key. Returns the removed value if it existed.
36 fn remove(&self, key: &str) -> Option<String>;
37
38 /// Compile all entries into a string for system prompt injection.
39 fn compile(&self) -> String {
40 let entries = self.entries();
41 if entries.is_empty() {
42 return String::new();
43 }
44
45 let mut out = String::from("<memory>\n");
46 for (key, value) in &entries {
47 out.push_str(&format!("<{key}>\n"));
48 out.push_str(value);
49 if !value.ends_with('\n') {
50 out.push('\n');
51 }
52 out.push_str(&format!("</{key}>\n"));
53 }
54 out.push_str("</memory>");
55 out
56 }
57
58 /// Store a key-value pair (async). Default delegates to `set`.
59 fn store(
60 &self,
61 key: impl Into<String> + Send,
62 value: impl Into<String> + Send,
63 ) -> impl Future<Output = Result<()>> + Send {
64 self.set(key, value);
65 async { Ok(()) }
66 }
67
68 /// Search for relevant entries (async). Default returns empty.
69 fn recall(
70 &self,
71 _query: &str,
72 _options: RecallOptions,
73 ) -> impl Future<Output = Result<Vec<MemoryEntry>>> + Send {
74 async { Ok(Vec::new()) }
75 }
76
77 /// Compile relevant entries for a query (async). Default delegates to `compile`.
78 fn compile_relevant(&self, _query: &str) -> impl Future<Output = String> + Send {
79 let compiled = self.compile();
80 async move { compiled }
81 }
82}
83
84/// A structured memory entry with metadata and optional embedding.
85#[derive(Debug, Clone, Default)]
86pub struct MemoryEntry {
87 /// Entry key (identity string).
88 pub key: CompactString,
89 /// Entry value (unbounded content).
90 pub value: String,
91 /// Optional structured metadata (JSON).
92 pub metadata: Option<Value>,
93 /// Unix timestamp when the entry was created.
94 pub created_at: u64,
95 /// Unix timestamp when the entry was last accessed.
96 pub accessed_at: u64,
97 /// Number of times the entry has been accessed.
98 pub access_count: u32,
99 /// Optional embedding vector for semantic search.
100 pub embedding: Option<Vec<f32>>,
101}
102
103/// Options controlling memory recall behavior.
104#[derive(Debug, Clone, Default)]
105pub struct RecallOptions {
106 /// Maximum number of results (0 = implementation default).
107 pub limit: usize,
108 /// Filter by creation time range (start, end) in unix seconds.
109 pub time_range: Option<(u64, u64)>,
110 /// Minimum relevance score threshold (0.0–1.0).
111 pub relevance_threshold: Option<f32>,
112}