feat: per-user auto-memory with ResponseContext
Three memory channels: hidden tool (sol.memory.set/get in scripts), pre-response injection (relevant memories loaded into system prompt), and post-response extraction (ministral-3b extracts facts after each response). User isolation enforced at Rust level — user_id derived from Matrix sender, never from script arguments. New modules: context (ResponseContext), memory (schema, store, extractor). ResponseContext threaded through responder → tools → script runtime. OpenSearch index sol_user_memory created on startup alongside archive.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use opensearch::OpenSearch;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use tracing::debug;
|
||||
use tracing::{debug, info};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SearchArgs {
|
||||
@@ -24,19 +24,24 @@ fn default_limit() -> usize { 10 }
|
||||
|
||||
/// Build the OpenSearch query body from parsed SearchArgs. Extracted for testability.
|
||||
pub fn build_search_query(args: &SearchArgs) -> serde_json::Value {
|
||||
let must = vec![json!({
|
||||
"match": { "content": args.query }
|
||||
})];
|
||||
// Handle empty/wildcard queries as match_all
|
||||
let must = if args.query.is_empty() || args.query == "*" {
|
||||
vec![json!({ "match_all": {} })]
|
||||
} else {
|
||||
vec![json!({
|
||||
"match": { "content": args.query }
|
||||
})]
|
||||
};
|
||||
|
||||
let mut filter = vec![json!({
|
||||
"term": { "redacted": false }
|
||||
})];
|
||||
|
||||
if let Some(ref room) = args.room {
|
||||
filter.push(json!({ "term": { "room_name": room } }));
|
||||
filter.push(json!({ "term": { "room_name.keyword": room } }));
|
||||
}
|
||||
if let Some(ref sender) = args.sender {
|
||||
filter.push(json!({ "term": { "sender_name": sender } }));
|
||||
filter.push(json!({ "term": { "sender_name.keyword": sender } }));
|
||||
}
|
||||
|
||||
let mut range = serde_json::Map::new();
|
||||
@@ -73,10 +78,19 @@ pub async fn search_archive(
|
||||
args_json: &str,
|
||||
) -> anyhow::Result<String> {
|
||||
let args: SearchArgs = serde_json::from_str(args_json)?;
|
||||
debug!(query = args.query.as_str(), "Searching archive");
|
||||
|
||||
let query_body = build_search_query(&args);
|
||||
|
||||
info!(
|
||||
query = args.query.as_str(),
|
||||
room = args.room.as_deref().unwrap_or("*"),
|
||||
sender = args.sender.as_deref().unwrap_or("*"),
|
||||
after = args.after.as_deref().unwrap_or("*"),
|
||||
before = args.before.as_deref().unwrap_or("*"),
|
||||
limit = args.limit,
|
||||
query_json = %query_body,
|
||||
"Executing search"
|
||||
);
|
||||
|
||||
let response = client
|
||||
.search(opensearch::SearchParts::Index(&[index]))
|
||||
.body(query_body)
|
||||
@@ -84,6 +98,8 @@ pub async fn search_archive(
|
||||
.await?;
|
||||
|
||||
let body: serde_json::Value = response.json().await?;
|
||||
let hit_count = body["hits"]["total"]["value"].as_i64().unwrap_or(0);
|
||||
info!(hit_count, "Search results");
|
||||
let hits = &body["hits"]["hits"];
|
||||
|
||||
let Some(hits_arr) = hits.as_array() else {
|
||||
@@ -173,7 +189,7 @@ mod tests {
|
||||
|
||||
let filters = q["query"]["bool"]["filter"].as_array().unwrap();
|
||||
assert_eq!(filters.len(), 2);
|
||||
assert_eq!(filters[1]["term"]["room_name"], "design");
|
||||
assert_eq!(filters[1]["term"]["room_name.keyword"], "design");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -183,7 +199,7 @@ mod tests {
|
||||
|
||||
let filters = q["query"]["bool"]["filter"].as_array().unwrap();
|
||||
assert_eq!(filters.len(), 2);
|
||||
assert_eq!(filters[1]["term"]["sender_name"], "Bob");
|
||||
assert_eq!(filters[1]["term"]["sender_name.keyword"], "Bob");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -193,8 +209,8 @@ mod tests {
|
||||
|
||||
let filters = q["query"]["bool"]["filter"].as_array().unwrap();
|
||||
assert_eq!(filters.len(), 3);
|
||||
assert_eq!(filters[1]["term"]["room_name"], "dev");
|
||||
assert_eq!(filters[2]["term"]["sender_name"], "Carol");
|
||||
assert_eq!(filters[1]["term"]["room_name.keyword"], "dev");
|
||||
assert_eq!(filters[2]["term"]["sender_name.keyword"], "Carol");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user