Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ forgetting, and encryption are part of the product contract. See
- [x] Episode-to-fact consolidation via injected LLM (any local runner, no hard dependency)
- [x] Hardware-aware provisioning — no silent model downloads, explicit setup command
- [x] Encryption at rest via native envelope (ADR-030); centralized passphrase resolution (ADR-034)
- [x] Per-agent isolation enforced structurally by key layout — cross-agent leakage is a security invariant
- [x] Per-agent result isolation across retrieval surfaces — record, FTS and graph namespaces are structurally keyed by agent; the vector (ANN) index is currently searched globally and post-filtered by agent, full physical search-space isolation is targeted by V2-08 ([ADR-069](docs/adr/ADR-069-structural-project-agent-memory-scope.md))
- [x] MCP server (stdio + HTTP), CLI (`basemyai`), REST sidecar (axum), Python SDK (PyO3), Node SDK (NAPI-RS), native Rust crate

<h2><img height="20" src="./basemyai-branding/icons/tick.svg">&nbsp;&nbsp;P1 Public Proofs</h2>

- [Benchmark harness: BaseMyAI local vs Mem0 + Qdrant local](docs/benchmarks/local-memory-vs-mem0-qdrant.md)
- [Adversarial isolation test](crates/basemyai/tests/p1_isolation_adversarial.rs)
- [Adversarial isolation test](crates/basemyai/tests/p1_isolation_adversarial.rs) — proves no cross-agent leakage in returned *results* (record/FTS/vector/hybrid recall); does not prove ANN search-space isolation (see [Security](#security))
- [Temporal replacement demo](crates/basemyai/examples/temporal_replacement.rs)
- [Zero network after setup](docs/zero-network-after-setup.md)
- [BaseMyAI is not a vector DB](docs/not-a-vector-db.md)
Expand Down Expand Up @@ -167,7 +167,7 @@ BaseMyAI is a **Cargo workspace** with two publishable crates (`basemyai-core`,

**`basemyai-engine`** is the durable storage layer: crash-consistent LSM, LM-DiskANN vector index, inverted FTS index, graph traversal, and at-rest encryption — all in pure Rust, no libSQL/SQLite dependency.

**`basemyai`** is the memory product built on top: the four layers, temporal RAG, per-agent isolation enforced structurally in the key layout, and all language binding surfaces.
**`basemyai`** is the memory product built on top: the four layers, temporal RAG, per-agent result isolation (structural for records/FTS/graph, post-filtered today for the vector index — see [Security](#security)), and all language binding surfaces.

Since **[ADR-032](docs/adr/ADR-032-native-only.md)** (2026-07-08), the native engine is the **only** active backend. libSQL/V1 compatibility paths have been removed from the workspace.

Expand Down Expand Up @@ -609,7 +609,7 @@ runs is the failure mode this guard exists to make impossible.
For security issues, kindly email us at [security@basemyai.com](mailto:security@basemyai.com) instead of posting a public issue on GitHub.

- **100 % local** — no data leaves your machine, no telemetry by default
- **Per-agent isolation** — every access is scoped structurally by `agent_id`; cross-agent leakage is a security invariant, not a config option
- **Per-agent result isolation** — record, FTS and graph namespaces are scoped structurally by `agent_id`; cross-agent leakage of *results* is a security invariant, not a config option. The ANN vector index is currently searched globally and post-filtered by agent — the query's candidate set, beam and expansions are not yet scope-isolated. Full physical search-space isolation is targeted by V2-08 ([ADR-069](docs/adr/ADR-069-structural-project-agent-memory-scope.md)); see also the adversarial isolation test note below, which proves result-level non-leakage, not search-space isolation.
- **Encrypted at rest** — native envelope (ADR-030); passphrase resolved per ADR-034, never stored in config
- **No silent network** — the embedder receives a local model path and never auto-downloads

Expand Down
4 changes: 4 additions & 0 deletions crates/basemyai-engine/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,10 @@ pub(crate) fn publish_staged_meta_tracked(
if let Err(error) = crate::failpoint_result!("after_crypto_meta_write") {
return DurablePublish::Unknown(error);
}
tracker.directory_sync_started();
if let Err(error) = crate::failpoint_result!("during_crypto_meta_directory_sync") {
return DurablePublish::Unknown(error);
}
// ENG-DUR-003: see `crate::fs_util`.
if let Err(error) = crate::fs_util::sync_dir(dir) {
return DurablePublish::Unknown(error);
Expand Down
18 changes: 18 additions & 0 deletions crates/basemyai-engine/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ pub enum EngineError {
#[error("sequence space exhausted after {last_allocated}: cannot reserve {requested} contiguous mutation(s)")]
SequenceSpaceExhausted { last_allocated: u64, requested: usize },

/// A WAL write was attempted but its complete absence or durability
/// could not be proved. The writer is terminal until the store is
/// reopened; callers must not retry the logical mutation blindly.
#[error("WAL append outcome is unknown and the writer must be reopened: {cause}")]
WalAppendOutcomeUnknown { cause: String },

/// The WAL bytes were appended but their `sync_all` outcome could not be
/// proved. The writer is terminal until the store is reopened.
#[error("WAL sync outcome is unknown and the writer must be reopened: {cause}")]
WalSyncOutcomeUnknown { cause: String },

/// A point-in-time read snapshot is process-local and cannot be used
/// after the engine instance that created it has closed or dropped.
#[error("read snapshot belongs to a closed store")]
Expand Down Expand Up @@ -237,6 +248,13 @@ pub enum EngineError {
#[error("memory record already exists for agent {agent:?}, id {id:?}")]
DuplicateMemoryId { agent: String, id: String },

/// The monotonic memory/vector-id allocator cannot represent the cursor
/// immediately after the requested contiguous range. Rejected before
/// staging reaches the WAL or mutates index RAM; wraparound and id reuse
/// are never permitted.
#[error("vector id space exhausted at next id {next}: cannot reserve {requested} contiguous id(s)")]
VectorIdSpaceExhausted { next: u64, requested: usize },

/// A string handed to an FTS-index key encoder (`key::fts_index`) would
/// overflow that field's `u32` length prefix. Sibling of
/// [`EngineError::GraphKeyTooLong`]/[`EngineError::MemoryKeyTooLong`],
Expand Down
13 changes: 13 additions & 0 deletions crates/basemyai-engine/src/failpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
//!
//! ```text
//! after_wal_append after the WAL record's write_all, before fsync
//! before_wal_append_attempt immediately before ADR-070's phase-aware batch
//! write; errors here are provably `Aborted`
//! during_wal_sync after append succeeds, immediately before
//! `sync_all`; errors are `OutcomeUnknown(Sync)`
//! after_wal_fsync after the WAL record's sync_all
//! after_sst_tmp_write after the SST tmp file's write_all, before fsync
//! after_sst_tmp_fsync after the SST tmp file's sync_all, before rename
Expand All @@ -25,10 +29,16 @@
//! during_wal_segment_removal after catalog.meta publishes a new active
//! WAL (old-segment unlink retried + counted)
//! after_crypto_meta_write after crypto.meta's atomic replace (rotation)
//! during_crypto_meta_directory_sync after crypto.meta replacement, at the
//! directory-durability boundary (ADR-070)
//! after_full_rotation_new_dek after the next generation's fresh DEK wrap
//! after_full_rotation_sst_write after the merged SST is durable
//! before_full_rotation_publish after all content fsyncs, before pointer rename
//! after_full_rotation_publish after pointer rename, before old-generation GC
//! before_full_rotation_install after durable generation publication, before
//! the prebuilt RAM image is installed (ADR-070)
//! after_light_rotation_publish after crypto.meta publication is complete;
//! finalisation panic cannot demote Committed
//! during_full_rotation_gc immediately before best-effort old-generation GC
//! during_generation_gc_removal one simulated failed removal attempt per hit
//! (retried up to 3x, then counted via
Expand Down Expand Up @@ -59,6 +69,9 @@
//! root directory fsync (ADR-066) — same class as
//! `after_catalog_rename`, on the pointer that selects
//! which generation directory a reopen adopts
//! during_generation_directory_sync after generation.meta replacement, at
//! the root directory-durability boundary
//! (ADR-070)
//! before_seal_candidate_wal_fsync immediately before the replacement WAL's
//! initial fsync, with no reader-facing or
//! durable-publication lock held (V2-01B)
Expand Down
Loading
Loading