aimem-core
Language / 语言 / 言語: English | 简体中文 | 日本語
aimem-core is AiMem's Rust core library.
It provides:
- Turso-backed storage
- project mining and conversation import
- keyword and semantic retrieval
- memory layers / wake-up context generation
- knowledge graph support
- embedding profile guards
- multimodal content primitives
CLI lives in aimem-cli. MCP server lives in aimem-mcp.
0.3.x capabilities
Current aimem-core includes:
- async embedding flow
LocalEmbedder
- opt-in
Gemini2Embedder
- multimodal
ContentPart
- embedding profile metadata:
- profile guards against mixed stores
Drawer helper API
MemoryStack::file_text(...)
MemoryStack::file_drawer_with_id(...)
MemoryStack::file_drawers_with_ids(...)
- tighter extractor heuristics with multilingual regression tests
Install
[dependencies]
tokio = { version = "1", features = ["full"] }
cargo add aimem-core
Recommended API surface
Prefer either:
use aimem_core::prelude::*;
or explicit root imports like:
use aimem_core::{AimemDb, Drawer, LocalEmbedder, MemoryStack, Miner, Searcher};
Core concepts
- Store — one AiMem DB
- Wing — project or domain
- Room — topic inside a wing
- Drawer — one stored chunk
- ContentPart — multimodal part inside a drawer
- L0/L1/L2/L3 — identity, essential story, recall, deep search
Embedding modes
Local
LocalEmbedder
- default recommendation
- embedding stays local
Remote
Gemini2Embedder
- explicit opt-in
- only explicit text / data URI / raw bytes are sent
URI-only multimedia parts are not automatically read from disk and uploaded.
Store compatibility guard
aimem-core records embedding profile metadata and validates semantic writes and queries against:
This prevents silent mixing of incompatible embedding stores.
Drawer helpers
use aimem_core::prelude::*;
let drawer = Drawer::new("id", "wing", "room", "content", "agent")
.with_source_file("README.md")
.with_chunk_index(3);
Stable-ID filing through MemoryStack:
use std::sync::Arc;
use aimem_core::prelude::*;
# #[tokio::main]
# async fn main() -> anyhow::Result<()> {
let cfg = Config::load()?;
let db = AimemDb::open(&cfg.db_path).await?;
let embedder = Arc::new(LocalEmbedder::new()?);
let stack = MemoryStack::new(db, embedder, &cfg);
let inserted = stack
.file_drawer_with_id(
"attachment.chunk.001",
"attachments",
"user-123",
"Attachment chunk body".to_string(),
vec![ContentPart::text("Attachment chunk body")],
Some("report.pdf"),
1,
"example",
)
.await?;
println!("inserted={inserted}");
# Ok(())
# }
When you already have a related set of stable-ID drawers (for example one
attachment summary plus chunk drawers), batch them in one call:
use std::sync::Arc;
use aimem_core::prelude::*;
# #[tokio::main]
# async fn main() -> anyhow::Result<()> {
let cfg = Config::load()?;
let db = AimemDb::open(&cfg.db_path).await?;
let embedder = Arc::new(LocalEmbedder::new()?);
let stack = MemoryStack::new(db, embedder, &cfg);
let inserted = stack
.file_drawers_with_ids(&[
DrawerFilingRequest::new(
"attachment.summary.001",
"attachments",
"user-123",
"Attachment summary".to_string(),
"example",
)
.with_parts(vec![ContentPart::text("Attachment summary")])
.with_source_file("report.pdf"),
DrawerFilingRequest::new(
"attachment.chunk.001",
"attachments",
"user-123",
"Attachment chunk 1".to_string(),
"example",
)
.with_parts(vec![ContentPart::text("Attachment chunk 1")])
.with_source_file("report.pdf")
.with_chunk_index(1),
])
.await?;
println!("inserted={inserted:?}");
# Ok(())
# }
AiMem checks existing stable IDs before embedding, so retrying the same batch
does not spend another remote embedding call on drawers that are already stored.
For multimodal drawers:
use aimem_core::prelude::*;
let drawer = Drawer::multimodal(
"id",
"wing",
"room",
"Look at this image",
vec![ContentPart::text("Look at this image")],
"agent",
);
Semantic search example
use std::sync::Arc;
use aimem_core::prelude::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let db = AimemDb::memory().await?;
let embedder = Arc::new(LocalEmbedder::new()?);
let content = "We moved the memory backend to Turso for local vector search.";
let embedding = embedder.embed_one(content).await?;
let drawer = Drawer::new("drawer_demo_001", "demo_app", "backend", content, "example");
db.insert_drawer_with_profile(
&drawer,
Some(&embedding),
embedder.provider_name(),
embedder.model_name(),
).await?;
let searcher = Searcher::new(db, embedder);
let hits = searcher.vector_search("local vector database", Some("demo_app"), None, 5).await?;
assert!(!hits.is_empty());
Ok(())
}
Filing text directly
use std::sync::Arc;
use aimem_core::prelude::*;
# #[tokio::main]
# async fn main() -> anyhow::Result<()> {
let cfg = Config::load()?;
let db = AimemDb::open(&cfg.db_path).await?;
let embedder = Arc::new(LocalEmbedder::new()?);
let stack = MemoryStack::new(db, embedder, &cfg);
let id = stack.file_text("demo_app", "notes", "remember this", "example").await?;
println!("{id}");
# Ok(())
# }
Notes
AimemDb::open() bootstraps schema automatically.
Miner::new(db, None) and ConvoMiner::new(db, None) work fine for text-only filing.
- drawers without embeddings still appear in keyword search.
AimemDb::embedding_profile() reports the current store profile.