traitclaw_core/traits/memory.rs
1//! Memory trait for agent state persistence.
2//!
3//! The [`Memory`] trait provides a 3-layer memory system:
4//! - **Conversation**: Short-term message history
5//! - **Working**: Task-specific key-value context
6//! - **Long-term**: Semantic recall across sessions
7
8use async_trait::async_trait;
9use serde_json::Value;
10
11use crate::types::message::Message;
12use crate::Result;
13
14/// A stored memory entry for long-term recall.
15///
16/// This struct is `#[non_exhaustive]` — new fields (e.g., `embedding`, `score`)
17/// may be added in future releases without breaking changes.
18#[derive(Debug, Clone)]
19#[non_exhaustive]
20pub struct MemoryEntry {
21 /// Unique identifier for this entry.
22 pub id: String,
23 /// The content of the memory.
24 pub content: String,
25 /// Optional metadata associated with this entry.
26 pub metadata: Option<Value>,
27 /// Unix timestamp (seconds) when this entry was created.
28 pub created_at: u64,
29}
30
31impl MemoryEntry {
32 /// Create a new `MemoryEntry` with `created_at` set to the current system time.
33 ///
34 /// Falls back to `0` if the system clock is not available (e.g., in WASM).
35 #[must_use]
36 pub fn now(id: impl Into<String>, content: impl Into<String>) -> Self {
37 let created_at = std::time::SystemTime::now()
38 .duration_since(std::time::UNIX_EPOCH)
39 .map(|d| d.as_secs())
40 .unwrap_or(0);
41 Self {
42 id: id.into(),
43 content: content.into(),
44 metadata: None,
45 created_at,
46 }
47 }
48}
49
50/// Trait for the 3-layer memory system.
51///
52/// Provides conversation history, working memory, and long-term recall.
53/// Implement this trait for custom storage backends (`SQLite`, `PostgreSQL`, Redis, etc.).
54#[async_trait]
55pub trait Memory: Send + Sync + 'static {
56 // === Conversation Memory (short-term) ===
57
58 /// Get conversation messages for a session.
59 async fn messages(&self, session_id: &str) -> Result<Vec<Message>>;
60
61 /// Append a message to the conversation history.
62 async fn append(&self, session_id: &str, message: Message) -> Result<()>;
63
64 // === Working Memory (task-specific) ===
65
66 /// Get a value from working memory.
67 async fn get_context(&self, session_id: &str, key: &str) -> Result<Option<Value>>;
68
69 /// Set a value in working memory.
70 async fn set_context(&self, session_id: &str, key: &str, value: Value) -> Result<()>;
71
72 // === Long-term Memory (semantic recall) ===
73
74 /// Search for relevant memories.
75 async fn recall(&self, query: &str, limit: usize) -> Result<Vec<MemoryEntry>>;
76
77 /// Store a new memory entry.
78 async fn store(&self, entry: MemoryEntry) -> Result<()>;
79
80 // === Session Lifecycle (default impls — override for persistent backends) ===
81
82 /// Create a new session and return its ID.
83 ///
84 /// Default impl generates a random UUID v4. Override to use custom ID schemes.
85 async fn create_session(&self) -> Result<String> {
86 Ok(uuid::Uuid::new_v4().to_string())
87 }
88
89 /// List all known session IDs.
90 ///
91 /// Default impl always returns an empty vec. Override for persistent backends.
92 async fn list_sessions(&self) -> Result<Vec<String>> {
93 Ok(vec![])
94 }
95
96 /// Delete a session and all associated conversation history and working memory.
97 ///
98 /// Default impl is a no-op. Override for persistent backends.
99 ///
100 /// # Note on long-term memory
101 ///
102 /// Long-term memory (`store`/`recall`) is **global** across all sessions and
103 /// is intentionally NOT cleared by this method. Use a separate cleanup
104 /// strategy if per-session long-term memory isolation is required.
105 async fn delete_session(&self, _session_id: &str) -> Result<()> {
106 Ok(())
107 }
108}