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
18const MEMORY_PROMPT: &str = include_str!("../../prompts/memory.md");
19
20/// Structured knowledge memory for LLM agents.
21///
22/// Implementations store named key-value pairs that get compiled
23/// into the system prompt via [`compile()`](Memory::compile).
24///
25/// Uses `&self` for all methods — implementations must handle
26/// interior mutability (e.g. via `Mutex`).
27pub trait Memory: Send + Sync {
28 /// Get the value for a key (owned).
29 fn get(&self, key: &str) -> Option<String>;
30
31 /// Get all key-value pairs (owned).
32 fn entries(&self) -> Vec<(String, String)>;
33
34 /// Set (upsert) a key-value pair. Returns the previous value if the key existed.
35 fn set(&self, key: impl Into<String>, value: impl Into<String>) -> Option<String>;
36
37 /// Remove a key. Returns the removed value if it existed.
38 fn remove(&self, key: &str) -> Option<String>;
39
40 /// Compile memory into a string for system prompt injection.
41 ///
42 /// Always returns the memory usage instructions. Appends a `## Your profile`
43 /// section containing only `user.*` and `soul.*` entries — stable identity
44 /// facts safe to embed unconditionally. All other entries are retrieval-only
45 /// and surface exclusively via the `recall` tool.
46 fn compile(&self) -> String {
47 let mut out = MEMORY_PROMPT.to_string();
48 let profile: Vec<_> = self
49 .entries()
50 .into_iter()
51 .filter(|(k, _)| k.starts_with("user.") || k.starts_with("soul."))
52 .collect();
53 if !profile.is_empty() {
54 out.push_str("\n\n## Your profile\n\n");
55 for (key, value) in &profile {
56 out.push_str(&format!("**{key}**: {value}\n"));
57 }
58 }
59 out
60 }
61
62 /// Store a key-value pair (async). Default delegates to `set`.
63 fn store(
64 &self,
65 key: impl Into<String> + Send,
66 value: impl Into<String> + Send,
67 ) -> impl Future<Output = Result<()>> + Send {
68 self.set(key, value);
69 async { Ok(()) }
70 }
71
72 /// Search for relevant entries (async). Default returns empty.
73 fn recall(
74 &self,
75 _query: &str,
76 _options: RecallOptions,
77 ) -> impl Future<Output = Result<Vec<MemoryEntry>>> + Send {
78 async { Ok(Vec::new()) }
79 }
80
81 /// Compile relevant entries for a query (async). Default delegates to `compile`.
82 fn compile_relevant(&self, _query: &str) -> impl Future<Output = String> + Send {
83 let compiled = self.compile();
84 async move { compiled }
85 }
86}
87
88/// A structured memory entry with metadata and optional embedding.
89#[derive(Debug, Clone, Default)]
90pub struct MemoryEntry {
91 /// Entry key (identity string).
92 pub key: CompactString,
93 /// Entry value (unbounded content).
94 pub value: String,
95 /// Optional structured metadata (JSON).
96 pub metadata: Option<Value>,
97 /// Unix timestamp when the entry was created.
98 pub created_at: u64,
99 /// Unix timestamp when the entry was last accessed.
100 pub accessed_at: u64,
101 /// Number of times the entry has been accessed.
102 pub access_count: u32,
103 /// Optional embedding vector for semantic search.
104 pub embedding: Option<Vec<f32>>,
105}
106
107/// Options controlling memory recall behavior.
108#[derive(Debug, Clone, Default)]
109pub struct RecallOptions {
110 /// Maximum number of results (0 = implementation default).
111 pub limit: usize,
112 /// Filter by creation time range (start, end) in unix seconds.
113 pub time_range: Option<(u64, u64)>,
114 /// Minimum relevance score threshold (0.0–1.0).
115 pub relevance_threshold: Option<f32>,
116}