remem-ai 0.6.70

Local-first coding agent memory for Claude Code and OpenAI Codex
Documentation
use std::collections::BTreeSet;

use anyhow::{bail, Result};
use rusqlite::{params, Connection, OptionalExtension};

use super::{ActiveTopicMemory, CandidateRoute, ParsedMemoryCandidate};

pub(super) fn load_required_memories(
    conn: &Connection,
    candidate: &ParsedMemoryCandidate,
    route: &CandidateRoute,
    memory_project: &str,
    required_ids: &BTreeSet<i64>,
    now_epoch: i64,
    candidate_has_ttl: bool,
) -> Result<Vec<ActiveTopicMemory>> {
    if required_ids.is_empty() {
        bail!("Dream promotion requires at least one reviewed supersede target");
    }

    let mut memories = Vec::with_capacity(required_ids.len());
    for id in required_ids {
        let row = conn
            .query_row(
                "SELECT project, memory_type, content, status,
                        COALESCE(
                            owner_scope,
                            CASE WHEN COALESCE(scope, 'project') = 'global'
                                 THEN 'user' ELSE 'repo' END
                        ),
                        COALESCE(
                            owner_key,
                            CASE WHEN COALESCE(scope, 'project') = 'global'
                                 THEN 'user:default' ELSE project END
                        ),
                        expires_at_epoch
                 FROM memories WHERE id = ?1",
                params![id],
                |row| {
                    Ok((
                        row.get::<_, String>(0)?,
                        row.get::<_, String>(1)?,
                        row.get::<_, String>(2)?,
                        row.get::<_, String>(3)?,
                        row.get::<_, String>(4)?,
                        row.get::<_, String>(5)?,
                        row.get::<_, Option<i64>>(6)?,
                    ))
                },
            )
            .optional()?;
        let Some((project, memory_type, content, status, owner_scope, owner_key, expires_at)) = row
        else {
            bail!("reviewed Dream supersede target is missing: id={id}");
        };
        if project != memory_project
            || memory_type != candidate.memory_type
            || owner_scope != route.owner_scope
            || owner_key != route.owner_key
        {
            bail!("reviewed Dream supersede target left its project/type/owner scope: id={id}");
        }
        if status != "active" {
            bail!("reviewed Dream supersede target is no longer active: id={id}");
        }
        if !memory_is_canonically_current(conn, *id)? {
            bail!("reviewed Dream supersede target is no longer current: id={id}");
        }
        let is_current = if candidate_has_ttl {
            expires_at.is_some_and(|epoch| epoch > now_epoch)
        } else {
            expires_at.is_none_or(|epoch| epoch > now_epoch)
        };
        if !is_current {
            bail!("reviewed Dream supersede target is no longer current: id={id}");
        }
        memories.push(ActiveTopicMemory {
            id: *id,
            content,
            is_current: true,
        });
    }
    Ok(memories)
}

fn memory_is_canonically_current(conn: &Connection, memory_id: i64) -> Result<bool> {
    let current_filter =
        crate::memory::memory_current_filter_sql("m.status", "m.expires_at_epoch", false);
    let state_filter = crate::memory::memory_state_key_current_filter_sql("m");
    let policy_filter = crate::memory::suppression::memory_policy_filter_sql("m");
    let sql = format!(
        "SELECT EXISTS(
             SELECT 1 FROM memories m
             WHERE m.id = ?1
               AND {current_filter}
               AND {state_filter}
               AND {policy_filter}
         )"
    );
    conn.query_row(&sql, params![memory_id], |row| row.get::<_, i64>(0))
        .map(|exists| exists != 0)
        .map_err(Into::into)
}