Basis
Usage with the SDK
Driving Basis through WeaveNode — the recommended entry point for most applications.
Why use the SDK
Constructing a Basis directly requires you to manage two Strand instances, their storage paths, and their replication. The weave-sdk crate does all of that for you and exposes a small set of namespaced calls on a single WeaveNode.
Open, add, search
use weave_sdk::prelude::*;
use uuid::Uuid;
#[tokio::main]
async fn main() -> WeaveResult<()> {
let node = WeaveNode::builder()
.namespace("l1fe")
.identifier("rag-agent")
.storage_dir("/tmp/weave-rag")
.build()
.await?;
// One Basis per logical corpus. Multiple corpora are fine — they share the node.
node.open_basis("docs").await?;
// Insert. The id is whatever you use to look up the source text later.
let doc_id = Uuid::new_v4();
let embedding: Vec<f32> = vec![0.01; 768]; // your real model goes here
node.basis_add("docs", doc_id, &embedding).await?;
// Search.
let query: Vec<f32> = vec![0.01; 768];
let hits: Vec<(Uuid, f32)> = node.basis_search("docs", &query, 10).await?;
for (id, distance) in hits {
println!("{id} @ L2 = {distance:.4}");
}
Ok(())
}SDK surface for Basis
| Call | Returns | Notes |
|---|---|---|
node.open_basis(name) | WeaveResult<()> | Idempotent; first call creates the strand pair |
node.basis_add(name, id, vector) | WeaveResult<()> | One Strand append + one HNSW insert |
node.basis_search(name, query, k) | WeaveResult<Vec<(Uuid, f32)>> | Returns up to k L2-nearest entries |
node.basis_remove(name, id) | WeaveResult<()> | Tombstones the entry and forces an index rebuild |
node.basis(name) | Option<&Basis> (via store) | Escape hatch to the underlying type |
A complete RAG ingestion loop
use weave_sdk::prelude::*;
use uuid::Uuid;
struct Chunk {
id: Uuid,
text: String,
embedding: Vec<f32>,
}
async fn ingest(node: &WeaveNode, chunks: &[Chunk]) -> WeaveResult<()> {
for c in chunks {
node.basis_add("docs", c.id, &c.embedding).await?;
// Persist the source text under a parallel Lens so search results can be
// resolved back to readable content.
node.lens_put("docs-text", c.id.as_bytes(), c.text.as_bytes()).await?;
}
Ok(())
}
async fn search(node: &WeaveNode, query_emb: &[f32], k: usize) -> WeaveResult<Vec<String>> {
let hits = node.basis_search("docs", query_emb, k).await?;
let mut out = Vec::with_capacity(hits.len());
for (id, _dist) in hits {
if let Some(bytes) = node.lens_get("docs-text", id.as_bytes()).await? {
out.push(String::from_utf8_lossy(&bytes).into_owned());
}
}
Ok(out)
}This pattern — Basis for the index, Lens for the resolved text — is the standard RAG layout in Weave. See the Semantic Search tutorial for the full end-to-end build.
Errors and recovery
| Surface | Error you will see | What to do |
|---|---|---|
open_basis on already-open name | Returns the existing handle (idempotent) | No action required |
basis_add after disk full | WeaveError::Storage bubbled from Strand | Free disk, retry; the partial state is safe |
basis_search with wrong dimensionality | Result is garbage, no error returned today | Validate dimensionality on the caller side |
| Process crash mid-add | Strand WAL replay on next open | Resume normally; rebuild is automatic |
Performance notes
Run a load test before sizing memory:
cargo run --example basis_load -p weave-sdk -- \
--corpus-size 100000 --dim 768 --k 10A 768-dim corpus of 100 000 vectors typically lands at ~320 MB resident memory with the default HNSW parameters. Plan for linear growth.