palace-rs 0.20.0

Local memory palace for AI assistants — Rust edition
Documentation
use palace::db;
use palace::ranker::hybrid_search;
use palace::store::{add_drawer, DrawerFilter};

mod common;

use common::coding_eval::{DOCS, QUESTIONS};

#[test]
fn coding_agent_memory_eval_has_stable_recall() {
    let conn = db::open_in_memory().expect("db should open");
    for doc in DOCS {
        add_drawer(
            &conn,
            "palace_rs",
            doc.room,
            doc.text,
            None,
            doc.id,
            0,
            "eval",
            3.0,
        )
        .expect("eval drawer should insert");
    }

    let mut top1 = 0usize;
    let mut top5 = 0usize;
    let mut failures = Vec::new();
    let mut top1_misses = Vec::new();

    for question in QUESTIONS {
        let results = hybrid_search(&conn, question.query, None, &DrawerFilter::default(), 5)
            .expect("eval search should work");
        if results
            .first()
            .is_some_and(|result| result.drawer.source_file == question.gold_source)
        {
            top1 += 1;
        } else {
            top1_misses.push((
                question.category,
                question.query,
                question.gold_source,
                results
                    .first()
                    .map(|result| result.drawer.source_file.clone())
                    .unwrap_or_else(|| "<none>".to_string()),
            ));
        }
        if results
            .iter()
            .any(|result| result.drawer.source_file == question.gold_source)
        {
            top5 += 1;
        } else {
            failures.push((question.category, question.query, question.gold_source));
        }
    }

    let recall_at_1 = top1 as f64 / QUESTIONS.len() as f64;
    let recall_at_5 = top5 as f64 / QUESTIONS.len() as f64;
    eprintln!(
        "coding-agent eval: recall@1={recall_at_1:.3} recall@5={recall_at_5:.3} failures={failures:?}"
    );

    assert!(
        recall_at_1 >= 0.86,
        "recall@1 too low: {recall_at_1:.3}; first_top1_misses={:?}",
        top1_misses
            .iter()
            .take(1)
            .map(|(_, _, gold, got)| {
                format!(
                    "{}!={}",
                    got.rsplit('/').next().unwrap_or(got),
                    gold.rsplit('/').next().unwrap_or(gold)
                )
            })
            .collect::<Vec<_>>()
    );
    assert!(recall_at_5 >= 0.98, "recall@5 too low: {recall_at_5:.3}");
}