* feat(memory): add session_id isolation to Memory trait Add optional session_id parameter to store(), recall(), and list() methods across the Memory trait and all four backends (sqlite, markdown, lucid, none). This enables per-session memory isolation so different agent sessions cannot cross-read each other's stored memories. Changes: - traits.rs: Add session_id: Option<&str> to store/recall/list - sqlite.rs: Schema migration (ALTER TABLE ADD COLUMN session_id), index, persist/filter by session_id in all query paths - markdown.rs, lucid.rs, none.rs: Updated signatures - All callers pass None for backward compatibility - 5 new tests: session-filtered recall, cross-session isolation, session-filtered list, no-filter returns all, migration idempotency Closes #518 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(channels): fix discord _channel_id typo and lark missing reply_to Pre-existing compilation errors on main after reply_to was added to ChannelMessage: discord.rs used _channel_id (underscore prefix) but referenced channel_id, and lark.rs was missing the reply_to field. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
87 lines
2 KiB
Rust
87 lines
2 KiB
Rust
use super::traits::{Memory, MemoryCategory, MemoryEntry};
|
|
use async_trait::async_trait;
|
|
|
|
/// Explicit no-op memory backend.
|
|
///
|
|
/// This backend is used when `memory.backend = "none"` to disable persistence
|
|
/// while keeping the runtime wiring stable.
|
|
#[derive(Debug, Default, Clone, Copy)]
|
|
pub struct NoneMemory;
|
|
|
|
impl NoneMemory {
|
|
pub fn new() -> Self {
|
|
Self
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl Memory for NoneMemory {
|
|
fn name(&self) -> &str {
|
|
"none"
|
|
}
|
|
|
|
async fn store(
|
|
&self,
|
|
_key: &str,
|
|
_content: &str,
|
|
_category: MemoryCategory,
|
|
_session_id: Option<&str>,
|
|
) -> anyhow::Result<()> {
|
|
Ok(())
|
|
}
|
|
|
|
async fn recall(
|
|
&self,
|
|
_query: &str,
|
|
_limit: usize,
|
|
_session_id: Option<&str>,
|
|
) -> anyhow::Result<Vec<MemoryEntry>> {
|
|
Ok(Vec::new())
|
|
}
|
|
|
|
async fn get(&self, _key: &str) -> anyhow::Result<Option<MemoryEntry>> {
|
|
Ok(None)
|
|
}
|
|
|
|
async fn list(
|
|
&self,
|
|
_category: Option<&MemoryCategory>,
|
|
_session_id: Option<&str>,
|
|
) -> anyhow::Result<Vec<MemoryEntry>> {
|
|
Ok(Vec::new())
|
|
}
|
|
|
|
async fn forget(&self, _key: &str) -> anyhow::Result<bool> {
|
|
Ok(false)
|
|
}
|
|
|
|
async fn count(&self) -> anyhow::Result<usize> {
|
|
Ok(0)
|
|
}
|
|
|
|
async fn health_check(&self) -> bool {
|
|
true
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn none_memory_is_noop() {
|
|
let memory = NoneMemory::new();
|
|
|
|
memory
|
|
.store("k", "v", MemoryCategory::Core, None)
|
|
.await
|
|
.unwrap();
|
|
|
|
assert!(memory.get("k").await.unwrap().is_none());
|
|
assert!(memory.recall("k", 10, None).await.unwrap().is_empty());
|
|
assert!(memory.list(None, None).await.unwrap().is_empty());
|
|
assert!(!memory.forget("k").await.unwrap());
|
|
assert_eq!(memory.count().await.unwrap(), 0);
|
|
assert!(memory.health_check().await);
|
|
}
|
|
}
|