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 any required transport. The weave-sdk crate manages local stores 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<()> | Present-id removal can deadlock in the current implementation; see recovery limits |
node.basis() | &Arc<RwLock<BasisStore>> | Access the named store manager; this is not a Basis handle |
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 Ok(()) (idempotent) | No action required |
basis_add fails | An error from the store or underlying append | Inspect the durable log before retrying; do not assume rollback or retry idempotence |
| Mismatched vector dimensions | Comparison can panic or use only part of a vector | Validate equal dimensions and finite values before add/search |
| Process restart | Stored vector records may survive, but the new HNSW index is empty | No public automatic replay is provided; follow the recovery limitations |
Recovery and sizing limits
Basis::new creates an empty in-memory index. BasisStore::open does not replay persisted vectors. The private rebuild helper is not a public recovery API, and present-id removal can wait on a lock it already holds. See Snapshot and Recovery.
The checkout has no basis_load example or published memory benchmark supporting a fixed sizing figure. Benchmark representative vectors, dimensions and query loads in your own integration. This guide demonstrates in-process indexing; it does not establish restart-safe search.