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

@@ -3,7 +3,8 @@ use matrix_sdk::RoomMemberships;
use ruma::events::room::message::{
MessageType, OriginalSyncRoomMessageEvent, Relation, RoomMessageEventContent,
};
use ruma::events::relation::InReplyTo;
use ruma::events::relation::{Annotation, InReplyTo};
use ruma::events::reaction::ReactionEventContent;
use ruma::OwnedEventId;
/// Extract the plain-text body from a message event.
@@ -45,15 +46,27 @@ pub fn extract_thread_id(event: &OriginalSyncRoomMessageEvent) -> Option<OwnedEv
None
}
/// Build a reply message content with m.in_reply_to relation.
/// Build a reply message content with m.in_reply_to relation and markdown rendering.
pub fn make_reply_content(body: &str, reply_to_event_id: OwnedEventId) -> RoomMessageEventContent {
let mut content = RoomMessageEventContent::text_plain(body);
let mut content = RoomMessageEventContent::text_markdown(body);
content.relates_to = Some(Relation::Reply {
in_reply_to: InReplyTo::new(reply_to_event_id),
});
content
}
/// Send an emoji reaction to a message.
pub async fn send_reaction(
room: &Room,
event_id: OwnedEventId,
emoji: &str,
) -> anyhow::Result<()> {
let annotation = Annotation::new(event_id, emoji.to_string());
let content = ReactionEventContent::new(annotation);
room.send(content).await?;
Ok(())
}
/// Get the display name for a room.
pub fn room_display_name(room: &Room) -> String {
room.cached_display_name()