lantern 0.3.0

Local-first, provenance-aware semantic search for agent activity
Documentation
use lantern::memory::{
    AddMemoryOptions, ListMemoryOptions, MemoryKind, MemoryStatus, add_memory, archive_memory,
    list_memories,
};
use lantern::store::Store;
use tempfile::tempdir;

fn new_store() -> (tempfile::TempDir, Store) {
    let root = tempdir().unwrap();
    let store = Store::initialize(&root.path().join("store")).unwrap();
    (root, store)
}

#[test]
fn add_memory_persists_defaults_and_returns_record() {
    let (_root, store) = new_store();

    let record = add_memory(
        &store,
        AddMemoryOptions {
            kind: MemoryKind::Preference,
            scope: "user:raphael".into(),
            content: "Raphael prefers separate branches on his repos.".into(),
            priority: None,
            urgency: None,
            confidence: None,
            source_refs: Vec::new(),
        },
    )
    .unwrap();

    assert_eq!(record.kind, MemoryKind::Preference);
    assert_eq!(record.scope, "user:raphael");
    assert_eq!(
        record.content,
        "Raphael prefers separate branches on his repos."
    );
    assert_eq!(record.priority, 50);
    assert_eq!(record.urgency, 0);
    assert_eq!(record.confidence, 1.0);
    assert_eq!(record.status, MemoryStatus::Active);
    assert!(record.created_at > 0);
    assert_eq!(record.updated_at, record.created_at);

    let listed = list_memories(&store, ListMemoryOptions::default()).unwrap();
    assert_eq!(listed.records.len(), 1);
    assert_eq!(listed.records[0].id, record.id);
}

#[test]
fn list_memories_filters_and_orders_active_records_by_policy_fields() {
    let (_root, store) = new_store();

    let low = add_memory(
        &store,
        AddMemoryOptions {
            kind: MemoryKind::Fact,
            scope: "project:lantern".into(),
            content: "Low priority fact.".into(),
            priority: Some(10),
            urgency: Some(0),
            confidence: Some(0.7),
            source_refs: Vec::new(),
        },
    )
    .unwrap();
    let urgent = add_memory(
        &store,
        AddMemoryOptions {
            kind: MemoryKind::Goal,
            scope: "project:lantern".into(),
            content: "Urgent goal.".into(),
            priority: Some(90),
            urgency: Some(80),
            confidence: Some(1.0),
            source_refs: Vec::new(),
        },
    )
    .unwrap();
    let high = add_memory(
        &store,
        AddMemoryOptions {
            kind: MemoryKind::Constraint,
            scope: "project:lantern".into(),
            content: "High priority constraint.".into(),
            priority: Some(90),
            urgency: Some(20),
            confidence: Some(1.0),
            source_refs: Vec::new(),
        },
    )
    .unwrap();
    let other_scope = add_memory(
        &store,
        AddMemoryOptions {
            kind: MemoryKind::Fact,
            scope: "user:raphael".into(),
            content: "Other scope.".into(),
            priority: Some(100),
            urgency: Some(100),
            confidence: Some(1.0),
            source_refs: Vec::new(),
        },
    )
    .unwrap();

    archive_memory(&store, &low.id).unwrap();

    let listed = list_memories(
        &store,
        ListMemoryOptions {
            scope: Some("project:lantern".into()),
            status: Some(MemoryStatus::Active),
            kind: None,
            limit: Some(10),
        },
    )
    .unwrap();

    let ids: Vec<_> = listed.records.iter().map(|r| r.id.as_str()).collect();
    assert_eq!(ids, vec![urgent.id.as_str(), high.id.as_str()]);
    assert!(!ids.contains(&low.id.as_str()));
    assert!(!ids.contains(&other_scope.id.as_str()));
}

#[test]
fn archive_memory_marks_record_without_deleting_it() {
    let (_root, store) = new_store();
    let record = add_memory(
        &store,
        AddMemoryOptions {
            kind: MemoryKind::Observation,
            scope: "global".into(),
            content: "Temporary observation.".into(),
            priority: Some(40),
            urgency: Some(5),
            confidence: Some(0.6),
            source_refs: vec!["session://abc#turn-1".into()],
        },
    )
    .unwrap();

    let archived = archive_memory(&store, &record.id).unwrap();
    assert_eq!(archived.id, record.id);
    assert_eq!(archived.status, MemoryStatus::Archived);
    assert!(archived.updated_at >= archived.created_at);
    assert_eq!(
        archived.source_refs,
        vec!["session://abc#turn-1".to_string()]
    );

    let active = list_memories(
        &store,
        ListMemoryOptions {
            status: Some(MemoryStatus::Active),
            ..Default::default()
        },
    )
    .unwrap();
    assert!(active.records.is_empty());

    let archived_records = list_memories(
        &store,
        ListMemoryOptions {
            status: Some(MemoryStatus::Archived),
            ..Default::default()
        },
    )
    .unwrap();
    assert_eq!(archived_records.records.len(), 1);
    assert_eq!(archived_records.records[0].id, record.id);
}