feat(memory): add session_id isolation to Memory trait (#530)
* 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>
This commit is contained in:
parent
f30f87662e
commit
ebb78afda4
16 changed files with 556 additions and 221 deletions
|
|
@ -389,7 +389,7 @@ impl Agent {
|
|||
if self.auto_save {
|
||||
let _ = self
|
||||
.memory
|
||||
.store("user_msg", user_message, MemoryCategory::Conversation)
|
||||
.store("user_msg", user_message, MemoryCategory::Conversation, None)
|
||||
.await;
|
||||
}
|
||||
|
||||
|
|
@ -448,7 +448,7 @@ impl Agent {
|
|||
let summary = truncate_with_ellipsis(&final_text, 100);
|
||||
let _ = self
|
||||
.memory
|
||||
.store("assistant_resp", &summary, MemoryCategory::Daily)
|
||||
.store("assistant_resp", &summary, MemoryCategory::Daily, None)
|
||||
.await;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ async fn build_context(mem: &dyn Memory, user_msg: &str) -> String {
|
|||
let mut context = String::new();
|
||||
|
||||
// Pull relevant memories for this message
|
||||
if let Ok(entries) = mem.recall(user_msg, 5).await {
|
||||
if let Ok(entries) = mem.recall(user_msg, 5, None).await {
|
||||
if !entries.is_empty() {
|
||||
context.push_str("[Memory context]\n");
|
||||
for entry in &entries {
|
||||
|
|
@ -913,7 +913,7 @@ pub async fn run(
|
|||
if config.memory.auto_save {
|
||||
let user_key = autosave_memory_key("user_msg");
|
||||
let _ = mem
|
||||
.store(&user_key, &msg, MemoryCategory::Conversation)
|
||||
.store(&user_key, &msg, MemoryCategory::Conversation, None)
|
||||
.await;
|
||||
}
|
||||
|
||||
|
|
@ -956,7 +956,7 @@ pub async fn run(
|
|||
let summary = truncate_with_ellipsis(&response, 100);
|
||||
let response_key = autosave_memory_key("assistant_resp");
|
||||
let _ = mem
|
||||
.store(&response_key, &summary, MemoryCategory::Daily)
|
||||
.store(&response_key, &summary, MemoryCategory::Daily, None)
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -979,7 +979,7 @@ pub async fn run(
|
|||
if config.memory.auto_save {
|
||||
let user_key = autosave_memory_key("user_msg");
|
||||
let _ = mem
|
||||
.store(&user_key, &msg.content, MemoryCategory::Conversation)
|
||||
.store(&user_key, &msg.content, MemoryCategory::Conversation, None)
|
||||
.await;
|
||||
}
|
||||
|
||||
|
|
@ -1037,7 +1037,7 @@ pub async fn run(
|
|||
let summary = truncate_with_ellipsis(&response, 100);
|
||||
let response_key = autosave_memory_key("assistant_resp");
|
||||
let _ = mem
|
||||
.store(&response_key, &summary, MemoryCategory::Daily)
|
||||
.store(&response_key, &summary, MemoryCategory::Daily, None)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
|
@ -1499,16 +1499,16 @@ I will now call the tool with this payload:
|
|||
let key1 = autosave_memory_key("user_msg");
|
||||
let key2 = autosave_memory_key("user_msg");
|
||||
|
||||
mem.store(&key1, "I'm Paul", MemoryCategory::Conversation)
|
||||
mem.store(&key1, "I'm Paul", MemoryCategory::Conversation, None)
|
||||
.await
|
||||
.unwrap();
|
||||
mem.store(&key2, "I'm 45", MemoryCategory::Conversation)
|
||||
mem.store(&key2, "I'm 45", MemoryCategory::Conversation, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(mem.count().await.unwrap(), 2);
|
||||
|
||||
let recalled = mem.recall("45", 5).await.unwrap();
|
||||
let recalled = mem.recall("45", 5, None).await.unwrap();
|
||||
assert!(recalled.iter().any(|entry| entry.content.contains("45")));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ impl MemoryLoader for DefaultMemoryLoader {
|
|||
memory: &dyn Memory,
|
||||
user_message: &str,
|
||||
) -> anyhow::Result<String> {
|
||||
let entries = memory.recall(user_message, self.limit).await?;
|
||||
let entries = memory.recall(user_message, self.limit, None).await?;
|
||||
if entries.is_empty() {
|
||||
return Ok(String::new());
|
||||
}
|
||||
|
|
@ -61,11 +61,17 @@ mod tests {
|
|||
_key: &str,
|
||||
_content: &str,
|
||||
_category: MemoryCategory,
|
||||
_session_id: Option<&str>,
|
||||
) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn recall(&self, _query: &str, limit: usize) -> anyhow::Result<Vec<MemoryEntry>> {
|
||||
async fn recall(
|
||||
&self,
|
||||
_query: &str,
|
||||
limit: usize,
|
||||
_session_id: Option<&str>,
|
||||
) -> anyhow::Result<Vec<MemoryEntry>> {
|
||||
if limit == 0 {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
|
@ -87,6 +93,7 @@ mod tests {
|
|||
async fn list(
|
||||
&self,
|
||||
_category: Option<&MemoryCategory>,
|
||||
_session_id: Option<&str>,
|
||||
) -> anyhow::Result<Vec<MemoryEntry>> {
|
||||
Ok(vec![])
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue