topodb 0.0.6

Embedded, local-first memory engine for AI agents: temporal property graph + scoped recall.
Documentation

topodb

The memory terrain for AI agents — embedded, temporal, graph-native.

TopoDB is an embedded, local-first memory engine for AI agents, written in pure Rust: a scoped temporal property graph on redb, with an op-log write path, disk-resident MVCC reads, and k-hop temporal traversal — running in-process, no server.

Status: early development (0.0.x) — the engine core and the recall layer are implemented: op log, single-applier concurrency, scoped temporal traversal, BM25 full-text search, graph-scoped vector search, access stats, change feed, and replay-determinism property tests. The API is not yet stable; pin exact versions.

use topodb::{
    Db, Direction, IndexSpec, NodeId, Op, PropIndex, PropValue, Scope, ScopeId,
    ScopeSet, TraversalQuery, VectorQuery,
};

fn main() -> Result<(), topodb::TopoError> {
    // Declare what gets indexed up front (Db::open defaults to NO indexes;
    // the CLI/MCP layers declare this same spec for you automatically).
    let spec = IndexSpec {
        equality: vec![PropIndex { label: "Entity".into(), prop: "name".into() }],
        text: vec![PropIndex { label: "Memory".into(), prop: "content".into() }],
    };
    let db = Db::open_with("memory.topodb", spec)?;
    let scope = ScopeId::new();
    let (a, b) = (NodeId::new(), NodeId::new());

    // Every mutation is a batch of ops, applied atomically.
    db.submit(vec![
        Op::CreateNode {
            id: a,
            scope: Scope::Id(scope),
            label: "Memory".into(),
            props: [(
                "content".to_string(),
                PropValue::Str("ada wrote the first program".into()),
            )]
            .into(),
        },
        Op::CreateNode {
            id: b,
            scope: Scope::Shared,
            label: "Entity".into(),
            props: Default::default(),
        },
        Op::CreateEdge {
            id: Default::default(),
            scope: Scope::Id(scope),
            ty: "ABOUT".into(),
            from: a,
            to: b,
            props: Default::default(),
            valid_from: None,
        },
        // Embeddings are host-computed and submitted as ops (engine, not policy).
        Op::SetEmbedding { id: a, model: "my-embedder".into(), vector: vec![0.1, 0.2, 0.3] },
    ])?;

    // Every read is scoped — there is no unscoped read path.
    let scopes = ScopeSet::of(&[scope]).with_shared();

    // BM25 full-text recall.
    let _hits = db.search_text(&scopes, "first program", 10)?;

    // Vector recall, scoped, under the same model namespace.
    let _near = db.search_vector(&VectorQuery {
        scopes: scopes.clone(),
        model: "my-embedder".into(),
        vector: vec![0.1, 0.2, 0.3],
        k: 10,
        candidates: None,
    })?;

    // Scoped k-hop temporal traversal.
    let _sub = db.traverse(&TraversalQuery {
        scopes,
        seeds: vec![a],
        max_hops: 2,
        edge_types: None,
        direction: Direction::Out,
        as_of: None,
    })?;
    Ok(())
}

Design principles

  1. Narrow and deep — an agent-memory engine, not a general graph database
  2. Format stability is a feature — versioned on-disk format, migrations always
  3. Honest benchmarks from day one
  4. Engine, not policy — no LLM calls inside the database, ever
  5. Embedded-first — servers and sync are future layers, never prerequisites

Core properties

  • Temporal edges — facts supersede, never overwrite; as_of reads see history
  • Structural scoping — every read takes a ScopeSet; cross-scope edges require a Shared endpoint
  • Deterministic replay — the op log stores fully-resolved ops; replaying it reproduces state exactly (property-tested)
  • Single-applier concurrency — writers from any thread serialize through one applier; reads run in redb MVCC read transactions, never block each other, and never block redb's storage commits, though a long-running read can briefly delay the applier's next batch while it holds the dicts/scope-registry read guards

License: MIT OR Apache-2.0.