Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Records API

The Records API manages structured Memory objects with metadata, timestamps, and embeddings.

Memory Type

#![allow(unused)]
fn main() {
pub struct Memory {
    pub id: String,
    pub content: String,
    pub embedding: Vec<f32>,
    pub importance: f64,
    pub timestamp: i64,
    pub metadata: HashMap<String, String>,
}
}

Creating Memories

New Memory

#![allow(unused)]
fn main() {
use opendb::{OpenDB, Memory};

let memory = Memory::new(
    "mem_001".to_string(),
    "User asked about Rust ownership".to_string(),
);
}

With Metadata

#![allow(unused)]
fn main() {
let memory = Memory::new("mem_002".to_string(), "Content".to_string())
    .with_metadata("category", "conversation")
    .with_metadata("user_id", "123");
}

Custom Builder

#![allow(unused)]
fn main() {
use std::collections::HashMap;

let mut metadata = HashMap::new();
metadata.insert("priority".to_string(), "high".to_string());

let memory = Memory {
    id: "mem_003".to_string(),
    content: "Important note".to_string(),
    embedding: vec![0.1, 0.2, 0.3], // 3D for demo
    importance: 0.95,
    timestamp: chrono::Utc::now().timestamp(),
    metadata,
};
}

CRUD Operations

Insert

#![allow(unused)]
fn main() {
let db = OpenDB::open("./db")?;
let memory = Memory::new("mem_001".to_string(), "Hello world".to_string());
db.insert_memory(&memory)?;
}

Signature:

#![allow(unused)]
fn main() {
pub fn insert_memory(&self, memory: &Memory) -> Result<()>
}

Behavior:

  • Serializes with rkyv (zero-copy)
  • Writes to records column family
  • Updates cache
  • If embedding is non-empty, stores in vector index (requires rebuild for search)

Get

#![allow(unused)]
fn main() {
let memory = db.get_memory("mem_001")?;
match memory {
    Some(mem) => println!("Content: {}", mem.content),
    None => println!("Not found"),
}
}

Signature:

#![allow(unused)]
fn main() {
pub fn get_memory(&self, id: &str) -> Result<Option<Memory>>
}

Behavior:

  • Checks cache first
  • Deserializes from storage on cache miss
  • Returns None if not found

Update

#![allow(unused)]
fn main() {
let mut memory = db.get_memory("mem_001")?.unwrap();
memory.content = "Updated content".to_string();
memory.importance = 0.9;
memory.touch(); // Update timestamp
db.insert_memory(&memory)?; // Upsert
}

Note: insert_memory() acts as upsert (update if exists, insert if not).

Delete

#![allow(unused)]
fn main() {
db.delete_memory("mem_001")?;
}

Signature:

#![allow(unused)]
fn main() {
pub fn delete_memory(&self, id: &str) -> Result<()>
}

Behavior:

  • Removes from storage
  • Invalidates cache
  • Does not remove from vector index (requires rebuild)
  • Does not remove graph edges (handle separately)

Listing Operations

List All IDs

#![allow(unused)]
fn main() {
let ids = db.list_memory_ids()?;
for id in ids {
    println!("Memory ID: {}", id);
}
}

Signature:

#![allow(unused)]
fn main() {
pub fn list_memory_ids(&self) -> Result<Vec<String>>
}

List All Memories

#![allow(unused)]
fn main() {
let memories = db.list_memories()?;
for memory in memories {
    println!("{}: {}", memory.id, memory.content);
}
}

Signature:

#![allow(unused)]
fn main() {
pub fn list_memories(&self) -> Result<Vec<Memory>>
}

Warning: Loads all memories into memory. For large datasets, use pagination (not yet implemented) or filter by prefix.

Advanced Usage

Importance Filtering

#![allow(unused)]
fn main() {
let memories = db.list_memories()?;
let important: Vec<_> = memories.into_iter()
    .filter(|m| m.importance > 0.8)
    .collect();
}

Metadata Queries

#![allow(unused)]
fn main() {
let memories = db.list_memories()?;
let category_matches: Vec<_> = memories.into_iter()
    .filter(|m| {
        m.metadata.get("category")
            .map(|v| v == "conversation")
            .unwrap_or(false)
    })
    .collect();
}

Time Range Queries

#![allow(unused)]
fn main() {
use chrono::{Utc, Duration};

let one_hour_ago = (Utc::now() - Duration::hours(1)).timestamp();
let recent: Vec<_> = db.list_memories()?.into_iter()
    .filter(|m| m.timestamp > one_hour_ago)
    .collect();
}

Embeddings

Setting Embeddings

Embeddings enable semantic search:

#![allow(unused)]
fn main() {
let embedding = generate_embedding("Hello world"); // Your embedding model
let memory = Memory {
    id: "mem_001".to_string(),
    content: "Hello world".to_string(),
    embedding, // Vec<f32>
    ..Default::default()
};
db.insert_memory(&memory)?;
}

Dimension Requirements

All embeddings must have the same dimension (default 384):

#![allow(unused)]
fn main() {
use opendb::OpenDBOptions;

let mut options = OpenDBOptions::default();
options.vector_dimension = 768; // For larger models
let db = OpenDB::open_with_options("./db", options)?;
}

Searching Embeddings

See Vector API for semantic search.

Touch Timestamp

Update access time without modifying content:

#![allow(unused)]
fn main() {
let mut memory = db.get_memory("mem_001")?.unwrap();
memory.touch(); // Sets timestamp to now
db.insert_memory(&memory)?;
}

Default Values

#![allow(unused)]
fn main() {
impl Default for Memory {
    fn default() -> Self {
        Self {
            id: String::new(),
            content: String::new(),
            embedding: Vec::new(),
            importance: 0.5,
            timestamp: chrono::Utc::now().timestamp(),
            metadata: HashMap::new(),
        }
    }
}
}

Performance Tips

  1. Batch Inserts: Use transactions for multiple inserts:
#![allow(unused)]
fn main() {
let mut txn = db.begin_transaction()?;
for memory in memories {
    // Insert via transaction (lower-level API needed)
}
txn.commit()?;
}
  1. Cache Warm-Up: Preload frequently accessed memories:
#![allow(unused)]
fn main() {
for id in important_ids {
    db.get_memory(id)?; // Populate cache
}
}
  1. Lazy Embedding Generation: Only generate embeddings when needed for search:
#![allow(unused)]
fn main() {
let memory = Memory::new(id, content);
// Don't set embedding unless search is required
db.insert_memory(&memory)?;
}

Error Handling

#![allow(unused)]
fn main() {
use opendb::Error;

match db.get_memory("mem_001") {
    Ok(Some(memory)) => { /* use memory */ },
    Ok(None) => { /* not found */ },
    Err(Error::Codec(_)) => { /* deserialization error */ },
    Err(Error::Storage(_)) => { /* storage error */ },
    Err(e) => { /* other error */ },
}
}

Next