grit-core 0.2.2

Embedded, bi-temporal property graph for agent memory: one SQLite file, in-process, deterministic
Documentation

grit

CI crates.io docs.rs License: MIT OR Apache-2.0

An embedded, bi-temporal property graph for agent memory. One SQLite file, in-process, written in Rust. No server, no daemon, no network — compiles for macOS, Windows, Linux, iOS, and Android.

grit is the piece of grit at the center of the pearl stack — Layer 1, doing deterministic graph storage and hybrid retrieval only. The LLM extraction pipeline that deposits memory around it is nacre (Layer 2); the agent app is Layer 3. The full design contract — invariants, scope limits, testing bar — is AGENTS.md, and it is binding for anyone (human or agent) working here.

use grit_core::{Budget, GraphOp, Grit, Options, Query, Traversal};

let g = Grit::open("memory.db", Options::new("laptop"))?;
g.apply(GraphOp::AddEpisode { /* provenance: a chat turn, a doc chunk … */ })?;
g.apply(GraphOp::AddEdge { /* a fact, with event-time validity … */ })?;

// Hybrid recall: BM25 + vectors + graph expansion, RRF-fused, budgeted.
let hits = g.search(Query::text("exactness").group("algebra").budget(Budget::items(20)))?;

// Time travel is a query, not archaeology.
let then = g.traverse(&[node_id], &Traversal::default().as_of(march).as_at(march))?;
let past = g.node_history(node_id)?;             // every edge ever believed

Why on SQLite

The embedded-graph-database niche is a graveyard: the ideal engine is archived, others are dormant, server-based, or license-encumbered — and none of it runs inside an iOS app. Meanwhile the graph semantics agent memory needs are a recipe, not a product. So grit is a deliberately thin layer (~a few kLOC) on the one storage engine that is in-process, iOS-native, public domain, and older than most databases. SQLite carries the dangerous parts — durability, crash recovery, decades of hardening — plus FTS5 and sqlite-vec, statically linked.

The model

Every mutation is a GraphOp, appended to an op-log; graph tables are derived state in the same transaction. The vocabulary is closed and small — it is also the future sync unit:

Op Semantics
AddNode create an entity (id-collision: lowest HLC wins, deterministically)
AddEdge create a fat edge — the fact sentence lives on it, with event-time valid_at/invalid_at
AddEpisode record raw provenance and its mentions — every fact traces to sources
UpdateNode revise entity metadata (name/summary/kind/attrs) — per-field last-writer-wins registers; history stays in the op-log
InvalidateEdge close a fact's event-time interval; concurrent invalidations converge to the earliest
MergeNodes execute a dedup decision (Layer 2 decides whether; cycle-safe canonical resolution)
Purge the only destructive op — exact-id right-to-forget, tombstoned, audited

Bi-temporal throughout. Edges carry event time (when a fact was true in the world) and system time (when it was believed). Invalidations are belief-versioned, so "back in March, did we think this job still held?" is as_of/as_at on an ordinary traversal.

Sync-ready before sync exists. UUIDv7 keys, hybrid logical clocks, and op application that is idempotent and commutative for concurrent ops — property-tested across adversarial interleavings (merge cycles, purges racing adds, updates arriving before their nodes, out-of-order invalidations). Embeddings stay out of the op-log: they're recomputable local state tagged with a model identity, stored and served back through typed getters/setters.

Data outlives the library. Lossless JSONL export/import of the full graph + op-log is a permanent compatibility surface; schema migrations are forward-only and tested against frozen fixture databases from every released version.

Proven by an oracle

grit's API is exercised by more than unit tests: nacre's golden-trace conformance replays recorded LLM/embedder responses through the full Rust stack and diffs the resulting grit graph against pinned Python Graphiti's frozen output — byte-equal facts, timestamps, attributions. Several grit 0.2 capabilities (UpdateNode, AddEdge.invalid_at, embedding getters, group scans) exist because that oracle demanded them.

Performance envelope

Scale target: ≤100k nodes / ≤1M edges — years of a person's learned knowledge, not a fleet's raw perception (extract first; that's the whole architecture). Measured at v0.1: traversal (3 hops, validity-filtered, 256-node budget) holds its ≤10 ms p95 target with ≥2× margin at 100k nodes / 300k edges; on a deliberately adversarial 1M-edge uniform-random fixture it measures ~17 ms warm p95, enforced by a CI latency tripwire. Criterion benches fail CI on >20 % regression.

Non-goals

No server mode, wire protocol, or networking. No general Cypher (a parked, fail-loud translator stub exists in grit-compat). No graph algorithms (PageRank, community detection) without a proven need. No multi-process access — one process, one writer. No chasing scale beyond the envelope. Each of these is an explicit decision in AGENTS.md, reversible only as a new decision.

Workspace

Crate Purpose
grit-core schema, op-log writer actor, traversal, hybrid search — the product
grit-compat parked, fail-loud Graphiti/FalkorDB-dialect translator stub
grit-cli dev tool: import/export, stats, ad-hoc search/traverse

Commands

cargo build --workspace
cargo test  --workspace          # unit + property + crash + migration + plan-pinning tests
cargo test -p grit-core --release --test envelope -- --ignored   # latency tripwire
cargo bench -p grit-core --bench hot_paths                       # criterion
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt   --all

The suite includes a kill -9 crash harness (derived tables must equal a full op-log replay after any crash point), op-log merge-law property tests, frozen schema-version fixtures, and EXPLAIN QUERY PLAN pinning — an index regression is a correctness bug at this latency budget. Everything runs offline.

License

MIT OR Apache-2.0, at your option. See NOTICE for attributions (Graphiti schema concepts, simple-graph traversal templates, Cortex scoring ideas).