WeaveDocs
Gnosis

Usage with the SDK

Driving Gnosis through WeaveNode — the recommended entry point for most applications.

Why use the SDK

The weave-sdk crate exposes Gnosis via a namespaced API on WeaveNode. You open a graph by name, add triples, query with patterns. The SDK owns the Strand lifecycle.

Open, add, query

use weave_sdk::prelude::*;

#[tokio::main]
async fn main() -> WeaveResult<()> {
    let node = WeaveNode::builder()
        .namespace("l1fe")
        .identifier("graph-agent")
        .storage_dir("/tmp/weave-graph")
        .build()
        .await?;

    node.open_gnosis("kb").await?;

    node.gnosis_add("kb", "alice", "trusts",   "bob").await?;
    node.gnosis_add("kb", "alice", "trusts",   "carol").await?;
    node.gnosis_add("kb", "bob",   "has_role", "admin").await?;

    // Query: all facts about alice.
    let facts = node.gnosis_query("kb", Some("alice"), None, None).await?;
    for t in &facts {
        println!("{} {} {}", t.subject, t.predicate, t.object);
    }

    // Neighbors: walk from alice.
    let nbrs = node.gnosis_neighbors("kb", "alice").await?;
    for (pred, obj) in nbrs {
        println!("alice --[{pred}]--> {obj}");
    }

    Ok(())
}

SDK surface for Gnosis

CallReturnsNotes
node.open_gnosis(name)WeaveResult<()>Idempotent
node.gnosis_add(name, s, p, o)WeaveResult<()>Strand append + index update
node.gnosis_query(name, s, p, o)WeaveResult<Vec<Triple>>None for wildcards
node.gnosis_neighbors(name, node_id)WeaveResult<Vec<(predicate, object)>>Subject-side walk
node.gnosis(name)Option<&Gnosis> (via store)Direct access

A small reasoning loop

use weave_sdk::prelude::*;

async fn can_admin(node: &WeaveNode, who: &str) -> WeaveResult<bool> {
    let facts = node.gnosis_query("kb", Some(who), Some("has_role"), Some("admin")).await?;
    Ok(!facts.is_empty())
}

async fn add_trust(node: &WeaveNode, from: &str, to: &str) -> WeaveResult<()> {
    node.gnosis_add("kb", from, "trusts", to).await
}

A typical ingestion shape

use weave_sdk::prelude::*;

struct Doc {
    id: String,
    author: String,
    tags: Vec<String>,
}

async fn ingest(node: &WeaveNode, doc: Doc) -> WeaveResult<()> {
    node.gnosis_add("docs", &doc.id, "authored_by", &doc.author).await?;
    for tag in &doc.tags {
        node.gnosis_add("docs", &doc.id, "tagged_with", tag).await?;
    }
    Ok(())
}

async fn docs_by_tag(node: &WeaveNode, tag: &str) -> WeaveResult<Vec<String>> {
    let triples = node.gnosis_query("docs", None, Some("tagged_with"), Some(tag)).await?;
    Ok(triples.into_iter().map(|t| t.subject).collect())
}

Errors and recovery

SurfaceErrorWhat to do
open_gnosis on existing nameReturns existing handleNo action
gnosis_add with disk fullWeaveError::Storage from StrandFree disk, retry
gnosis_query after partial index rebuildReturns whatever the indices currently holdOn a known-cold start, allow a rebuild pass before serving production traffic

Practical patterns

Time-bound facts

Gnosis has no built-in expiry. Encode it in the predicate:

let now = chrono::Utc::now().timestamp();
node.gnosis_add("kb", "alice", &format!("trusts_until:{}", now + 86400), "bob").await?;

A periodic compaction job removes expired triples by writing tombstones (today: re-add with a "revoked" predicate and filter in the caller).

Composition with Basis

Use Gnosis for typed relationships and Basis for similarity over the same identifiers:

use uuid::Uuid;

async fn ingest_doc(node: &weave_sdk::WeaveNode, doc_id: Uuid, author: &str, embedding: &[f32]) -> weave_sdk::error::WeaveResult<()> {
    node.gnosis_add("kb", &doc_id.to_string(), "authored_by", author).await?;
    node.basis_add("kb-vec", doc_id, embedding).await?;
    Ok(())
}

The semantic search tutorial walks through the full pattern: Semantic Search.