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:
2026-03-21 15:51:31 +00:00
parent 4dc20bee23
commit 4949e70ecc
23 changed files with 4494 additions and 124 deletions

View File

@@ -24,6 +24,15 @@ pub struct ArchiveDocument {
pub edited: bool,
#[serde(default)]
pub redacted: bool,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub reactions: Vec<Reaction>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Reaction {
pub sender: String,
pub emoji: String,
pub timestamp: i64,
}
const INDEX_MAPPING: &str = r#"{
@@ -45,7 +54,15 @@ const INDEX_MAPPING: &str = r#"{
"media_urls": { "type": "keyword" },
"event_type": { "type": "keyword" },
"edited": { "type": "boolean" },
"redacted": { "type": "boolean" }
"redacted": { "type": "boolean" },
"reactions": {
"type": "nested",
"properties": {
"sender": { "type": "keyword" },
"emoji": { "type": "keyword" },
"timestamp": { "type": "date", "format": "epoch_millis" }
}
}
}
}
}"#;
@@ -63,6 +80,25 @@ pub async fn create_index_if_not_exists(client: &OpenSearch, index: &str) -> any
if exists.status_code().is_success() {
info!(index, "OpenSearch index already exists");
// Ensure reactions field exists (added after initial schema)
let reactions_mapping = serde_json::json!({
"properties": {
"reactions": {
"type": "nested",
"properties": {
"sender": { "type": "keyword" },
"emoji": { "type": "keyword" },
"timestamp": { "type": "date", "format": "epoch_millis" }
}
}
}
});
let _ = client
.indices()
.put_mapping(opensearch::indices::IndicesPutMappingParts::Index(&[index]))
.body(reactions_mapping)
.send()
.await;
return Ok(());
}
@@ -102,6 +138,7 @@ mod tests {
event_type: "m.room.message".to_string(),
edited: false,
redacted: false,
reactions: vec![],
}
}