remem-ai 0.4.4

Persistent memory for Claude Code — single binary, zero subprocesses
Documentation
use anyhow::Result;
use rusqlite::Connection;

/// Load sqlite-vec extension into the connection.
/// For now, we use a placeholder implementation.
/// TODO: Integrate actual sqlite-vec when ready for production.
pub fn load_vec_extension(_conn: &Connection) -> Result<()> {
    // Placeholder: sqlite-vec integration requires either:
    // 1. Compiling the extension and loading it dynamically
    // 2. Using a bundled version
    // For Phase 1, we skip this and focus on the LLM extraction pipeline.
    Ok(())
}

/// Create vector table for observations embeddings.
/// Using 768 dimensions (typical for sentence transformers like all-MiniLM-L6-v2).
pub fn ensure_vec_table(_conn: &Connection) -> Result<()> {
    // Placeholder: will be implemented when sqlite-vec is properly integrated
    Ok(())
}

/// Insert or update observation embedding.
pub fn upsert_embedding(_conn: &Connection, _obs_id: i64, embedding: &[f32]) -> Result<()> {
    if embedding.len() != 768 {
        anyhow::bail!("embedding must be 768 dimensions, got {}", embedding.len());
    }
    // Placeholder: will be implemented when sqlite-vec is properly integrated
    Ok(())
}

/// Vector similarity search: find top K most similar observations.
/// Returns (observation_id, distance) pairs sorted by distance (lower = more similar).
pub fn vector_search(
    _conn: &Connection,
    query_embedding: &[f32],
    _limit: usize,
) -> Result<Vec<(i64, f32)>> {
    if query_embedding.len() != 768 {
        anyhow::bail!(
            "query embedding must be 768 dimensions, got {}",
            query_embedding.len()
        );
    }
    // Placeholder: will be implemented when sqlite-vec is properly integrated
    Ok(vec![])
}

/// Find observations with cosine similarity > threshold.
/// Used for deduplication (threshold typically 0.95).
pub fn find_similar_observations(
    conn: &Connection,
    query_embedding: &[f32],
    threshold: f32,
    limit: usize,
) -> Result<Vec<i64>> {
    let candidates = vector_search(conn, query_embedding, limit)?;

    // Filter by threshold (distance < 1 - threshold for cosine similarity)
    let distance_threshold = 1.0 - threshold;
    let similar: Vec<i64> = candidates
        .into_iter()
        .filter(|(_, dist)| *dist < distance_threshold)
        .map(|(id, _)| id)
        .collect();

    Ok(similar)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_vec_extension_loads() -> Result<()> {
        let conn = rusqlite::Connection::open_in_memory()?;
        load_vec_extension(&conn)?;
        ensure_vec_table(&conn)?;
        Ok(())
    }
}