WeaveDocs
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.

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

CallReturnsNotes
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

SurfaceError you will seeWhat to do
open_basis on already-open nameReturns the existing handle (idempotent)No action required
basis_add after disk fullWeaveError::Storage bubbled from StrandFree disk, retry; the partial state is safe
basis_search with wrong dimensionalityResult is garbage, no error returned todayValidate dimensionality on the caller side
Process crash mid-addStrand WAL replay on next openResume 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 10

A 768-dim corpus of 100 000 vectors typically lands at ~320 MB resident memory with the default HNSW parameters. Plan for linear growth.