Skip to main content

Crate aimem_core

Crate aimem_core 

Source
Expand description

§aimem-core

Give your AI a memory — reusable core library.

aimem-core is a pure-Rust library that stores, retrieves, and searches text memories using Turso (an in-process SQLite engine with native vector support) as the sole storage backend. No external vector database, no cloud service, no API key.

§Feature overview

FeatureImplementation
Vector storageTurso vector32() BLOB column
Semantic searchvector_distance_cos() — SimSIMD-accelerated cosine
Full-text searchSQL LIKE scan (Turso FTS index optional)
Embedding generationfastembed — all-MiniLM-L6-v2 locally
Knowledge graphTemporal entity-relationship triples in SQLite
Graph traversalBFS over wing/room adjacency — no graph DB needed
MCP protocolImplemented in aimem-mcp (separate crate)

§Architecture: 4-Layer Memory Stack

┌─────────────────────────────────────────────────────────┐
│  L0  Identity text           ~100 tokens   always loaded │
│  L1  Essential story         ~500-800 tok  always loaded │
│  L2  On-demand recall        ~200-500 tok  loaded on ask │
│  L3  Deep semantic search    unlimited     full palace   │
└─────────────────────────────────────────────────────────┘

§Storage model

Everything lives in a single Turso DB file (default: ~/.aimem/aimem.db):

aimem.db
├── drawers   — verbatim text chunks + vector32 embeddings
├── entities  — knowledge graph nodes
└── triples   — temporal (subject → predicate → object) edges

§Concepts

TermMeaning
PalaceThe entire memory store (one DB file)
WingA named project or domain (e.g. "my_app", "journal")
RoomA topic within a wing (e.g. "backend", "decisions")
DrawerOne verbatim text chunk (800 chars) with its embedding
TunnelA room that spans multiple wings — a conceptual bridge

§Quick start

# Cargo.toml
[dependencies]
aimem-core = "0.1.0"
tokio = { version = "1", features = ["full"] }
use aimem_core::{Config, Embedder, MemoryStack, PalaceDb};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // 1. Load config (env > ~/.aimem/config.json > defaults)
    let cfg = Config::load()?;

    // 2. Open the palace (creates DB + schema on first run)
    let db = PalaceDb::open(&cfg.db_path).await?;

    // 3. Load local embedding model (downloads ~23 MB on first use)
    let embedder = Embedder::new()?;

    // 4. Build the memory stack
    let stack = MemoryStack::new(db, embedder, &cfg);

    // 5. Wake-up prompt: L0 (identity) + L1 (top memories) — inject into LLM context
    println!("{}", stack.wake_up(None).await?);

    // 6. Semantic search — uses Turso vector_distance_cos() natively
    println!("{}", stack.search("why did we choose Rust?", None, None).await?);

    Ok(())
}

§Mining a project

use aimem_core::{Config, Embedder, Miner, PalaceDb};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let cfg = Config::load()?;
    let db = PalaceDb::open(&cfg.db_path).await?;
    let embedder = Embedder::new()?;

    let miner = Miner::new(db, Some(embedder));

    // Reads aimem.yaml in the project directory for wing/room config
    let stats = miner.mine("~/projects/my_app", None, "claude", 0, false).await?;

    println!(
        "Mined {} drawers from {} files",
        stats.drawers_added,
        stats.files_scanned - stats.files_skipped,
    );
    Ok(())
}

§Keyword-only search without embeddings

If you mine with Miner::new(db, None) or ConvoMiner::new(db, None), drawers still participate in SQL keyword search even though they have no embeddings.

use aimem_core::{Drawer, PalaceDb, Searcher};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let db = PalaceDb::memory().await?;
    let drawer = Drawer {
        id: "drawer_kw_001".into(),
        wing: "demo".into(),
        room: "backend".into(),
        content: "Turso keeps the backend local and simple.".into(),
        source_file: None,
        chunk_index: 0,
        added_by: "example".into(),
        filed_at: chrono::Utc::now().to_rfc3339(),
    };
    db.insert_drawer(&drawer, None).await?;

    let searcher = Searcher::keyword_only(db);
    let hits = searcher.keyword_search("Turso", Some("demo"), None, 5).await?;
    assert_eq!(hits.len(), 1);
    Ok(())
}

§Knowledge graph

use aimem_core::{KnowledgeGraph, PalaceDb};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let db = PalaceDb::memory().await?; // in-memory for demo
    let kg = KnowledgeGraph::new(db);

    // Store a temporal fact
    kg.add_triple("Max", "does", "swimming", Some("2025-01-01"), None).await?;
    kg.add_triple("Max", "loves", "chess", Some("2025-10-01"), None).await?;

    // Query all outgoing facts for "Max"
    let facts = kg.query_entity("Max", None, "outgoing").await?;
    for f in &facts {
        println!("{} → {} → {}", f.subject, f.predicate, f.object);
    }

    // Invalidate a fact (set valid_to = today)
    kg.invalidate("Max", "does", "swimming", None).await?;

    Ok(())
}

§Low-level storage operations

use aimem_core::{Drawer, Embedder, PalaceDb};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let db = PalaceDb::memory().await?;
    let embedder = Embedder::new()?;

    let content = "We decided to use Rust for performance and memory safety.";
    let embedding = embedder.embed_one(content)?;

    let drawer = Drawer {
        id: "drawer_test_decision_001".to_string(),
        wing: "my_project".to_string(),
        room: "decisions".to_string(),
        content: content.to_string(),
        source_file: Some("DECISIONS.md".to_string()),
        chunk_index: 0,
        added_by: "claude".to_string(),
        filed_at: chrono::Utc::now().to_rfc3339(),
    };

    let inserted = db.insert_drawer(&drawer, Some(&embedding)).await?;
    println!("Inserted: {inserted}");
    println!("Total drawers: {}", db.drawer_count().await?);

    Ok(())
}

§Crate feature flags

aimem-core has no optional features itself — the embedding model is always available. The hf-hub feature of fastembed controls whether Embedder::new() can download models automatically.

§Reusing in other projects

Because this is a library crate with no binary, any project can depend on it:

[dependencies]
aimem-core = { git = "https://github.com/v1cc0/aimem" }
# or local path:
aimem-core = { path = "/path/to/aimem/crates/aimem-core" }

The public re-exports in this file form the stable API surface. Everything else is implementation detail.

Re-exports§

pub use config::Config;
pub use convo::ConvoMineStats;
pub use convo::ConvoMiner;
pub use db::PalaceDb;
pub use embedder::Embedder;
pub use graph::PalaceGraph;
pub use graph::TraversalNode;
pub use knowledge::KnowledgeGraph;
pub use layers::MemoryStack;
pub use miner::Miner;
pub use search::Searcher;
pub use types::Drawer;
pub use types::DrawerMeta;
pub use types::Entity;
pub use types::RoomNode;
pub use types::SearchResult;
pub use types::Triple;
pub use types::Tunnel;

Modules§

config
Configuration — mirrors Python’s config.py
convo
Conversation miner — mine chat exports into the palace.
db
PalaceDb — Turso connection wrapper + schema bootstrap.
embedder
Embedder — local text-to-vector using fastembed-rs.
extractor
Memory extractor — heuristic keyword extraction, no LLM required.
graph
Palace graph — traverse rooms and wings in the palace.
knowledge
Knowledge graph — temporal entity-relationship graph.
layers
4-layer memory stack — mirrors Python’s layers.py.
miner
File miner — scan a project directory and file chunks into the palace.
search
Semantic search and FTS over the palace.
types
Shared data types.