AndaDB Technical Documentation
June 4, 2026 · View on GitHub
Version: 0.7.26
Overview
AndaDB is the core embedded database crate in the AndaDB workspace. It is designed for AI-agent memory and knowledge workloads where application code needs to store structured documents, index them in multiple retrieval modes, and keep persistence logic local to the process instead of delegating it to an external database service.
At the crate level, AndaDB provides:
- A database object that manages collections and shared persistence
- Schema-aware collections for document CRUD
- Exact-match and range retrieval through B-Tree indexes
- Full-text retrieval through BM25 indexes
- Vector similarity retrieval through HNSW indexes
- Hybrid retrieval with reciprocal-rank-fusion reranking
- An object-store-backed persistence layer with optional compression and caching
The crate is intentionally small in surface area. Most user-facing work happens through these modules:
database: database lifecycle and collection managementcollection: document operations, index management, search, and metadataquery: hybrid search, filters, and reranking typesindex: index types and index hooksschema: re-exported type system fromanda_db_schemastorage: persistence, compression, object versions, and I/O statserror: the unifiedDBErrortype
Design Goals
AndaDB is optimized for a specific class of systems rather than for general-purpose relational workloads.
Its main goals are:
- Durable long-term memory for AI agents
- Unified structured, lexical, and semantic retrieval
- Explicit schema validation at write time
- Embeddable deployment with no mandatory external database server
- Storage abstraction through
object_store - Predictable operational control for flushing, checkpoints, and read-only mode
In practice, the library behaves like a document database with specialized retrieval primitives for agent memory.
Architecture
The anda_db crate sits on top of several lower-level workspace crates.
Application
-> anda_db::database::AndaDB
-> anda_db::collection::Collection
-> anda_db::index::{BTree, BM25, Hnsw}
-> anda_db::storage::Storage
-> object_store::ObjectStore
Supporting crates:
- anda_db_schema: schema and field-value system
- anda_db_btree: exact/range index engine
- anda_db_tfs: BM25 text index engine
- anda_db_hnsw: vector index engine
- anda_db_utils: supporting utilities such as UniqueVec
The core execution model is:
- Validate incoming documents against a schema.
- Derive index values for the document.
- Update in-memory index state.
- Persist the document to object storage.
- Flush metadata and index files to make state durable.
The library also contains recovery-oriented logic for reopening collections, loading persisted indexes, and repairing some partially flushed states.
Core Concepts
Database
AndaDB is the top-level handle. A database owns:
- A database name
- A shared
object_storebackend - A storage namespace rooted at the database name
- Database metadata
- A set of open collections
Important methods:
AndaDB::create: create a new database and fail if metadata already existsAndaDB::connect: open an existing database or create a new oneAndaDB::open: open an existing database and fail if it does not existcreate_collection: create a new collectionopen_collection: open an existing collectionopen_or_create_collection: open if present, otherwise createdelete_collection: remove a collection and its persisted dataflush: flush open collections and database metadataclose: switch the database to read-only mode and flush pending state
The database also exposes extensions, which are lightweight user-defined metadata entries persisted alongside database metadata.
Collection
A collection is the main unit of application work. All documents in a collection share one schema.
A collection owns:
- The collection name
- The active schema
- The collection storage namespace
- Zero or more B-Tree, BM25, and HNSW indexes
- Collection statistics and metadata
- In-memory document-id tracking structures
- Tokenizer configuration for text indexing
- Optional custom
IndexHooks
Important methods:
addandadd_fromgetandget_asupdateremovesearchandsearch_assearch_idsandquery_idscreate_btree_index,create_bm25_index,create_hnsw_indexcompact_btree_index,compact_bm25_indexflushandclose
Collections also expose their own extensions map for storing small application-specific metadata.
Schema
The schema system comes from anda_db_schema and is re-exported through anda_db::schema.
There are two common ways to define schemas:
- Derive them from Rust types with
AndaDBSchema - Build them programmatically with schema builders and field entries
Typical field categories include:
- scalar values such as integers, floats, booleans, and text
- byte arrays
- vectors
- arrays and maps
- JSON-like values
- optional values
The collection validates documents against its schema before they are accepted.
Document Identity
Document ids are unsigned 64-bit integers. The collection manages document-id assignment internally when using add and add_from.
Each collection also maintains:
max_document_id- an ordered
BTreeSetof ids for range traversal - a bitmap (
croaring::Treemap) for efficient membership and persistence
This combination supports fast containment checks, ordered scans, and durable recovery.
Indexing Model
AndaDB supports three complementary index families. Creating a new index on an existing collection backfills the index from the collection's current document ids before the index is registered in collection metadata. If the backfill fails, for example because a unique B-Tree index would conflict, the index is not made visible and the temporary index metadata is cleaned up best-effort.
B-Tree Indexes
B-Tree indexes are used for:
- exact match
- range queries
- unique constraints
- compound virtual-field indexes
They are the backbone of the Filter::Field query model and of direct id-range filtering.
Examples:
- filter by thread id
- filter by created_at range
- enforce uniqueness on external keys
- query a synthetic compound key built from multiple fields
For multi-field B-Tree indexes, the collection combines the indexed field values into a deterministic binary representation and stores it as a virtual field.
BM25 Indexes
BM25 indexes support full-text retrieval over one or more fields.
Important properties:
- Collections can customize tokenization via
set_tokenizer - A BM25 index may span multiple fields
- Queries can run in standard or logical-search mode
- Results can be fused with vector results through RRF
The default BM25 path is collection-local. You create the index once and then use Query.search.text to retrieve documents.
HNSW Indexes
HNSW indexes support approximate nearest-neighbor retrieval over vector fields.
Important properties:
- The indexed field must be a vector field
- Index construction is parameterized by
HnswConfig - Query vectors are supplied as
Vec<f32> - Search returns ranked document ids which can be fused with BM25 results
- Persisted vectors may be read back as the schema-compatible
Array(U64 bits)representation; the default index hooks normalize this form before inserting into HNSW.
This index family is the semantic-retrieval path for embeddings or representation vectors.
Hybrid Retrieval and RRF
If both text and vector search are present in a Query, AndaDB executes both and combines their ranked id lists with RRFReranker.
The reranker:
- assigns each result list a reciprocal-rank score
- sums scores across lists
- sorts by descending combined score
This is a pragmatic hybrid-search strategy that keeps query semantics simple while still allowing multiple retrieval signals.
Query Model
The query surface is intentionally compact.
Query
Query contains:
search: Option<Search>filter: Option<Filter>limit: Option<usize>
The practical execution order is:
- Produce ranked candidates from text and or vector search
- Apply filters
- Enforce the final limit
Search
Search contains:
text: optional BM25 text queryvector: optional HNSW query vectorbm25_params: optional tuning for BM25 scoringreranker: optional custom RRF configurationlogical_search: whether to enable logical search operators in BM25
Filter
Filter supports recursive logic:
Field((index_name, range_query))Or(Vec<Box<Filter>>)And(Vec<Box<Filter>>)Not(Box<Filter>)
This makes it possible to express compound constraints such as:
- vector search constrained to a thread id
- full-text retrieval constrained to a time window
- id-range scans excluding known records
Limits and Candidate Expansion
Internally, hybrid search may fetch more than the final limit before filtering so that ranking and filtering still produce useful final results. The public limit remains the final output contract.
Storage Layer
The storage module implements persistence on top of object_store.
Storage Backend Portability
One of AndaDB's most important design choices is that it does not bind persistence to a single local disk implementation. Instead, it builds on the object_store::ObjectStore trait, which provides a uniform async API for object storage services and local environments.
This means the same AndaDB application can be wired to different storage backends with minimal changes in database code. Depending on which object_store feature flags are enabled by the embedding application, the storage layer can target:
- in-memory storage for tests and ephemeral runs
- local filesystem storage for embedded deployments
- Amazon S3
- Google Cloud Storage
- Azure Blob Storage
- HTTP/WebDAV-compatible object storage
This portability matters for AI memory systems because it lets the same collection, indexing, and flush logic move across local development, self-hosted environments, and cloud object storage without redesigning the database layer.
It is also important that object_store models object-store semantics rather than POSIX filesystem semantics. In practice, this gives AndaDB a better foundation for durable metadata and index persistence, because the underlying abstraction supports capabilities such as conditional reads and writes, multipart upload, bulk deletion, and buffered adapters that map directly onto modern cloud storage systems.
In other words, AndaDB is not merely using object_store as a convenience wrapper. It is using it as the portability and durability boundary for the whole persistence layer. The exact backend is a deployment choice; the database logic above it remains the same.
Storage Namespacing
The database and each collection use distinct storage prefixes.
Within a collection, persisted objects include:
- collection metadata
- document-id bitmap
- document bodies
- B-Tree metadata and buckets
- BM25 metadata and buckets
- HNSW metadata, ids, and node files
- storage metadata for checkpointing and I/O statistics
Small Objects vs Streaming Writes
The storage layer distinguishes between:
- small objects written by
putorput_bytes - streamed objects written by
stream_writer
StorageConfig.max_small_object_size protects the small-object path from oversized payloads.
Compression
Storage can compress payloads with zstd.
Important details:
- compression is optional and controlled by
StorageConfig.compress_level - compression is skipped if it does not reduce size
- decompression is guarded by a maximum-size policy to reduce decompression-bomb risk
Caching
Small objects may be cached in-memory using moka.
The cache is intended for:
- frequently read metadata objects
- small document reads
- repeated access patterns in agent loops
Cache size is configured with cache_max_capacity.
Versioned Updates
The storage layer tracks ObjectVersion values built from object-store metadata such as ETag and version id.
These versions are used for conditional updates so the library can:
- avoid silent clobbering of newer state
- detect precondition failures
- coordinate metadata and index flushes safely
Flush, Durability, and Recovery
Persistence in AndaDB is incremental rather than transactional in the relational-database sense.
What flush Does
At collection level, flush persists:
- updated collection metadata
- document-id bitmap when needed
- dirty index state
- storage checkpoint metadata
At database level, flush:
- flushes all open collections
- persists database metadata
close Semantics
close places the database or collection into read-only mode and then flushes pending changes. This is the expected end-of-life operation for a process that owns mutable state.
Auto Flush
The database provides auto_flush(cancel_token, interval) for background periodic flushing. This is useful in agent runtimes that want bounded persistence lag without flushing after every single write.
Recovery Strategy
On reopening a collection, the library:
- loads collection metadata
- loads the persisted document-id bitmap
- loads persisted indexes
- may auto-repair index state for documents that were written but not fully reflected in index persistence
This recovery path is one of the key reasons the crate is well suited to long-running agent memory processes.
Read-Only Mode and Safety Controls
Both the database and collections can be switched into read-only mode.
This is useful for:
- controlled shutdown
- maintenance windows
- serving queries from a stable snapshot in-process
Database-level configuration also supports an optional opaque lock value. This lets applications enforce that only processes with the expected lock material may open a database for mutation.
Metadata and Extensions
Both databases and collections support user-defined lightweight extensions.
These are appropriate for:
- application version markers
- ingestion cursors
- sync checkpoints
- small runtime hints
- policy flags
They are not appropriate for large payloads because they live in frequently read metadata objects.
Error Model
The crate exposes a unified DBError enum.
Major categories include:
- generic errors
- schema errors
- storage errors
- index errors
- not found and already exists conditions
- precondition failures
- serialization failures
- payload-too-large errors
The error model intentionally preserves enough structure for callers to distinguish:
- logical application problems such as missing collections
- concurrency and conditional-write problems
- persistence failures from the underlying object store
- validation failures from schemas and indexes
Example Workflow
This is the common end-to-end flow for an application using anda_db.
use anda_db::{
database::{AndaDB, DBConfig},
collection::CollectionConfig,
index::HnswConfig,
query::{Filter, Query, RangeQuery, Search},
schema::{AndaDBSchema, Fv, vector_from_f32},
storage::StorageConfig,
};
use object_store::local::LocalFileSystem;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, AndaDBSchema)]
struct Memory {
_id: u64,
created_at: u64,
topic: String,
body: String,
embedding: anda_db::schema::Vector,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let store = Arc::new(LocalFileSystem::new_with_prefix("./data")?);
let db = AndaDB::connect(
store,
DBConfig {
name: "agent_memory".into(),
description: "Long-term agent memory".into(),
storage: StorageConfig::default(),
lock: None,
},
)
.await?;
let schema = Memory::schema()?;
let memories = db
.open_or_create_collection(
schema,
CollectionConfig {
name: "memories".into(),
description: "Semantic and lexical memory store".into(),
},
async |c| {
c.create_btree_index_nx(&["created_at"]).await?;
c.create_bm25_index_nx(&["topic", "body"]).await?;
c.create_hnsw_index_nx(
"embedding",
HnswConfig {
dimension: 4,
..Default::default()
},
)
.await?;
Ok(())
},
)
.await?;
let memory = Memory {
_id: 0,
created_at: 1_713_000_000,
topic: "rust".into(),
body: "Rust makes long-running agent services safer.".into(),
embedding: vector_from_f32(vec![0.1, 0.2, 0.3, 0.4]),
};
let id = memories.add_from(&memory).await?;
let results: Vec<Memory> = memories
.search_as(Query {
search: Some(Search {
text: Some("rust agent safety".into()),
vector: Some(vec![0.1, 0.2, 0.3, 0.4]),
..Default::default()
}),
filter: Some(Filter::Field((
"created_at".into(),
RangeQuery::Ge(Fv::U64(1_700_000_000)),
))),
limit: Some(10),
})
.await?;
assert!(!results.is_empty());
let _ = id;
db.close().await?;
Ok(())
}
Operational Guidance
Choose Indexes Deliberately
Use B-Tree indexes for fields you filter on frequently. Use BM25 only for fields whose textual content should participate in retrieval. Use HNSW only for vector fields with stable dimensionality.
Keep Extensions Small
Database and collection extensions should stay lightweight because they are persisted in hot metadata objects.
Flush on a Policy, Not by Accident
For write-heavy agent systems, choose either:
- periodic
auto_flush - explicit flushes after ingestion batches
- explicit close on shutdown
Relying only on process exit is not a good persistence policy.
Prefer Schema-Derived Models
When the application already has Rust structs for memories or knowledge objects, deriving AndaDBSchema reduces schema drift and keeps serialization aligned with storage.
Tune Storage for Payload Shape
If documents and index objects are mostly small, caching and small-object writes are effective defaults. If payloads are large, revisit compression and chunk sizing in StorageConfig.
Module Reference
database
Defines:
AndaDBDBConfigDBMetadata
Responsibilities:
- open, create, connect, and close the database
- own shared storage and open collections
- coordinate collection creation and deletion
- expose database metadata and extensions
collection
Defines:
CollectionCollectionConfigCollectionMetadataCollectionStats
Responsibilities:
- validate, insert, update, remove, and read documents
- create and manage indexes
- execute filtering and hybrid retrieval
- expose collection stats, metadata, and extensions
query
Defines:
QuerySearchFilterRRFReranker- re-exported
RangeQuery
Responsibilities:
- describe retrieval intent independently of storage mechanics
- support hybrid retrieval and recursive filter composition
index
Defines the index façade over the underlying workspace engines and includes IndexHooks for custom index-value extraction.
storage
Defines:
StorageStorageConfigStorageMetadataStorageStatsObjectVersion
Responsibilities:
- encode and decode persisted objects
- manage object versions and conditional writes
- expose cached reads and write helpers
- track storage-level metrics and checkpoints
schema
Re-exports the type system from anda_db_schema, including:
- field types and field values
- schema builders and validation
- derived schema support
- document conversion helpers
Relationship to Other Workspace Crates
The anda_db crate is the embedded core. Other crates in the workspace build on top of it:
anda_db_serverexposes HTTP RPCanda_db_shard_proxyadds sharded and multi-tenant routinganda_cognitive_nexusbuilds higher-level knowledge workflowsanda_kipdefines the protocol layer used by adjacent components
If you only need an in-process memory database in Rust, anda_db is the direct entry point.
Summary
AndaDB is best understood as a schema-aware, embeddable, multi-index document store specialized for AI memory systems. Its strength is not only that it stores data, but that it keeps lexical, structural, and semantic retrieval close to the write path while remaining deployable as a normal Rust library.
For agent builders, that combination is the core value proposition: one collection model, one persistence layer, and multiple retrieval paths that can be fused into a single memory access pattern.