OSpipe

February 13, 2026 ยท View on GitHub

RuVector-enhanced personal AI memory for Screenpipe

Crates.io docs.rs License: MIT Rust WASM


What is OSpipe?

Screenpipe is an open-source desktop application that continuously records your screen, audio, and UI interactions locally. It builds a searchable timeline of everything you see, hear, and do on your computer. Out of the box, Screenpipe stores its data in SQLite with FTS5 full-text indexing -- effective for keyword lookups, but limited to literal string matching. If you search for "auth discussion," you will not find a frame that says "we talked about login security."

OSpipe replaces Screenpipe's storage and search backend with the RuVector ecosystem -- a collection of 70+ Rust crates providing HNSW vector search, graph neural networks, attention mechanisms, delta-change tracking, and more. Instead of keyword matching, OSpipe embeds every captured frame into a high-dimensional vector space and performs approximate nearest neighbor search, delivering true semantic recall. A query like "what was that API we discussed in standup?" will surface the relevant audio transcription even if those exact words never appeared.

Everything stays local and private. OSpipe processes all data on-device with no cloud dependency. The safety gate automatically detects and redacts PII -- credit card numbers, Social Security numbers, and email addresses -- before content ever reaches the vector store. A cosine-similarity deduplication window prevents consecutive identical frames (like a static desktop) from bloating storage. Age-based quantization progressively compresses older embeddings from 32-bit floats down to 1-bit binary, cutting long-term memory usage by 97%.

OSpipe ships as a Rust crate, a TypeScript SDK, and a WASM library. It runs natively on Windows, macOS, and Linux, and can run entirely in the browser via WebAssembly at bundles as small as 11.8KB.

Ask your computer what you saw, heard, and did -- with semantic understanding.


Features

  • Semantic Vector Search -- HNSW index via ruvector-core with 61us p50 query latency
  • PII Safety Gate -- automatic redaction of credit card numbers, SSNs, and email addresses before storage
  • Frame Deduplication -- cosine similarity sliding window eliminates near-duplicate captures
  • Hybrid Search -- weighted combination of semantic vector similarity and keyword term overlap
  • Query Router -- automatically routes queries to the optimal backend (Semantic, Keyword, Graph, Temporal, or Hybrid)
  • WASM Support -- runs entirely in the browser with bundles from 11.8KB (micro) to 350KB (full)
  • TypeScript SDK -- @ruvector/ospipe for Node.js and browser integration
  • Configurable Quantization -- 4-tier age-based compression: f32 -> int8 -> product -> binary
  • Cross-Platform -- native builds for Windows, macOS, Linux; WASM for browsers

Architecture

                         OSpipe Ingestion Pipeline
                         =========================

  Screenpipe -----> Capture -----> Safety Gate -----> Dedup -----> Embed -----> VectorStore
  (Screen/Audio/UI)  (CapturedFrame)  (PII Redaction)   (Cosine Window)  (HNSW)      |
                                                                                      |
                                                           Search Router <------------+
                                                           |    |    |    |    |
                                                        Semantic Keyword Graph Temporal Hybrid

Frames flow left to right through the ingestion pipeline. Each captured frame passes through:

  1. Safety Gate -- PII detection and redaction; content may be allowed, redacted, or denied
  2. Deduplication -- cosine similarity check against a sliding window of recent embeddings
  3. Embedding -- text content is encoded into a normalized vector
  4. Vector Store -- the embedding is indexed for approximate nearest neighbor retrieval

Queries enter through the Search Router, which analyzes the query string and dispatches to the optimal backend.


Quick Start

Rust

Add OSpipe to your Cargo.toml:

[dependencies]
ospipe = { path = "examples/OSpipe" }

Create a pipeline, ingest frames, and search:

use ospipe::config::OsPipeConfig;
use ospipe::pipeline::ingestion::IngestionPipeline;
use ospipe::capture::{CapturedFrame, CaptureSource, FrameContent, FrameMetadata};

fn main() -> ospipe::error::Result<()> {
    // Initialize with default configuration
    let config = OsPipeConfig::default();
    let mut pipeline = IngestionPipeline::new(config)?;

    // Ingest a screen capture
    let frame = CapturedFrame::new_screen(
        "Firefox",
        "Meeting Notes - Google Docs",
        "Discussion about authentication: we decided to use JWT with refresh tokens",
        0,
    );
    let result = pipeline.ingest(frame)?;
    println!("Ingest result: {:?}", result);

    // Ingest an audio transcription
    let audio = CapturedFrame::new_audio(
        "Built-in Microphone",
        "Let's revisit the login flow next sprint",
        Some("Alice"),
    );
    pipeline.ingest(audio)?;

    // Search semantically
    let query_embedding = pipeline.embedding_engine().embed("auth token discussion");
    let results = pipeline.vector_store().search(&query_embedding, 5)?;

    for hit in &results {
        println!("Score: {:.4} | {:?}", hit.score, hit.metadata);
    }

    // Print pipeline statistics
    let stats = pipeline.stats();
    println!(
        "Ingested: {} | Deduped: {} | Denied: {} | Redacted: {}",
        stats.total_ingested, stats.total_deduplicated,
        stats.total_denied, stats.total_redacted
    );

    Ok(())
}

TypeScript

import { OsPipe } from "@ruvector/ospipe";

const client = new OsPipe({ baseUrl: "http://localhost:3030" });

// Ingest a captured frame
await client.ingest({
  source: "screen",
  app: "Chrome",
  window: "Jira Board",
  content: "Sprint 14 planning: migrate auth to OAuth2",
});

// Semantic search
const results = await client.queryRuVector(
  "what did I discuss in the meeting about authentication?"
);

for (const hit of results) {
  console.log(`[${hit.score.toFixed(3)}] ${hit.metadata.text}`);
}

WASM (Browser)

import { OsPipeWasm } from "@ruvector/ospipe-wasm";

// Initialize with 384-dimensional embeddings
const pipe = new OsPipeWasm(384);

// Embed and insert content
const embedding = pipe.embed_text("meeting notes about auth migration to OAuth2");
pipe.insert("frame-001", embedding, '{"app":"Chrome","window":"Jira"}', Date.now());

// Embed a query and search
const queryEmbedding = pipe.embed_text("what was the auth discussion about?");
const results = pipe.search(queryEmbedding, 5);
console.log("Results:", results);

// Safety check before storage
const safety = pipe.safety_check("my card is 4111-1111-1111-1111");
console.log("Safety:", safety); // "deny"

// Query routing
const route = pipe.route_query("what happened yesterday?");
console.log("Route:", route); // "Temporal"

// Pipeline statistics
console.log("Stats:", pipe.stats());

Comparison: Screenpipe vs OSpipe

FeatureScreenpipe (FTS5)OSpipe (RuVector)
Search TypeKeyword (FTS5)Semantic + Keyword + Graph + Temporal
Search Latency~1ms (FTS5)61us (HNSW p50)
Content RelationsNoneKnowledge Graph (Cypher)
Temporal AnalysisBasic SQLDelta-behavior tracking
PII ProtectionBasicCredit card, SSN, email redaction
DeduplicationNoneCosine similarity sliding window
Browser SupportNoneWASM (11.8KB - 350KB)
QuantizationNone4-tier age-based (f32 -> binary)
PrivacyLocal-firstLocal-first + PII redaction
Query RoutingNoneAuto-routes to optimal backend
Hybrid SearchNoneWeighted semantic + keyword fusion
Metadata FilteringSQL WHEREApp, time range, content type, monitor

RuVector Crate Integration

RuVector CrateOSpipe UsageStatus
ruvector-coreHNSW vector storage and nearest neighbor searchIntegrated
ruvector-filterMetadata filtering (app, time, content type)Integrated
ruvector-clusterFrame deduplication via cosine similarityIntegrated
ruvector-delta-coreChange tracking and delta-behavior analysisIntegrated
ruvector-router-coreQuery routing to optimal search backendIntegrated
cognitum-gate-kernelAI safety gate decisions (allow/redact/deny)Integrated
ruvector-graphKnowledge graph for entity relationshipsPhase 2
ruvector-attentionContent prioritization and relevance weightingPhase 3
ruvector-gnnLearned search improvement via graph neural netsPhase 3
ruqu-algorithmsQuantum-inspired search accelerationPhase 4

Configuration

Full Configuration Reference

OsPipeConfig

Top-level configuration with nested subsystem configs. All fields have sensible defaults.

use ospipe::config::OsPipeConfig;

let config = OsPipeConfig::default();
// config.data_dir        = "~/.ospipe"
// config.capture         = CaptureConfig { ... }
// config.storage         = StorageConfig { ... }
// config.search          = SearchConfig { ... }
// config.safety          = SafetyConfig { ... }

CaptureConfig

FieldTypeDefaultDescription
fpsf321.0Frames per second for screen capture
audio_chunk_secsu3230Duration of audio chunks in seconds
excluded_appsVec<String>["1Password", "Keychain Access"]Applications excluded from capture
skip_private_windowsbooltrueSkip windows marked as private/incognito

StorageConfig

FieldTypeDefaultDescription
embedding_dimusize384Dimensionality of embedding vectors
hnsw_musize32HNSW M parameter (max connections per layer)
hnsw_ef_constructionusize200HNSW ef_construction (index build quality)
hnsw_ef_searchusize100HNSW ef_search (query-time accuracy)
dedup_thresholdf320.95Cosine similarity threshold for deduplication
quantization_tiersVec<QuantizationTier>4 tiers (see below)Age-based quantization schedule

SearchConfig

FieldTypeDefaultDescription
default_kusize10Default number of results to return
hybrid_weightf320.7Semantic vs keyword weight (1.0 = pure semantic, 0.0 = pure keyword)
mmr_lambdaf320.5MMR diversity vs relevance tradeoff
rerank_enabledboolfalseWhether to enable result reranking

SafetyConfig

FieldTypeDefaultDescription
pii_detectionbooltrueEnable PII detection (emails)
credit_card_redactionbooltrueEnable credit card number redaction
ssn_redactionbooltrueEnable SSN redaction
custom_patternsVec<String>[]Custom substring patterns that trigger denial

Example: Custom Configuration

use ospipe::config::*;
use std::path::PathBuf;

let config = OsPipeConfig {
    data_dir: PathBuf::from("/var/lib/ospipe"),
    capture: CaptureConfig {
        fps: 0.5,
        audio_chunk_secs: 60,
        excluded_apps: vec![
            "1Password".into(),
            "Signal".into(),
            "Bitwarden".into(),
        ],
        skip_private_windows: true,
    },
    storage: StorageConfig {
        embedding_dim: 768,      // Use a larger model
        hnsw_m: 48,              // More connections for better recall
        hnsw_ef_construction: 400,
        hnsw_ef_search: 200,
        dedup_threshold: 0.98,   // Stricter deduplication
        ..Default::default()
    },
    search: SearchConfig {
        default_k: 20,
        hybrid_weight: 0.8,      // Lean more toward semantic
        mmr_lambda: 0.6,
        rerank_enabled: true,
    },
    safety: SafetyConfig {
        pii_detection: true,
        credit_card_redaction: true,
        ssn_redaction: true,
        custom_patterns: vec![
            "INTERNAL_ONLY".into(),
            "CONFIDENTIAL".into(),
        ],
    },
};

Safety Gate

PII Detection Details

The safety gate inspects all captured content before it enters the ingestion pipeline. It operates in three modes:

Safety Decisions

DecisionBehaviorWhen
AllowContent stored as-isNo sensitive patterns detected
AllowRedacted(String)Content stored with PII replaced by tokensPII detected, redaction enabled
Deny { reason }Content rejected, not storedCustom deny pattern matched

Detected PII Patterns

Credit Cards -- sequences of 13-16 digits (with optional spaces or dashes):

4111111111111111       -> [CC_REDACTED]
4111 1111 1111 1111    -> [CC_REDACTED]
4111-1111-1111-1111    -> [CC_REDACTED]

Social Security Numbers -- XXX-XX-XXXX format:

123-45-6789            -> [SSN_REDACTED]

Email Addresses -- word@domain.tld patterns:

user@example.com       -> [EMAIL_REDACTED]
admin@company.org      -> [EMAIL_REDACTED]

Custom Patterns -- configurable substring deny list. When a custom pattern is matched, the entire frame is denied (not just redacted):

let config = SafetyConfig {
    custom_patterns: vec!["TOP_SECRET".to_string(), "CLASSIFIED".to_string()],
    ..Default::default()
};

WASM Safety API

The WASM bindings expose a simplified safety classifier:

pipe.safety_check("my card is 4111-1111-1111-1111"); // "deny"
pipe.safety_check("set password to foo123");          // "redact"
pipe.safety_check("the weather is nice today");       // "allow"

The WASM classifier also detects sensitive keywords: password, secret, api_key, api-key, apikey, token, private_key, private-key.


Advanced Configuration

WASM Deployment

Bundle Tiers

OSpipe provides four WASM bundle sizes depending on which features you need:

TierSizeFeatures
Micro11.8KBEmbedding + vector search only
Standard225KBFull pipeline (embed, insert, search, filtered search)
Full350KB+ deduplication + safety gate + query routing
AI2.5MB+ on-device neural inference (ONNX)

Web Worker Setup

For best performance, run OSpipe in a Web Worker to avoid blocking the main thread:

// worker.js
import { OsPipeWasm } from "@ruvector/ospipe-wasm";

const pipe = new OsPipeWasm(384);

self.onmessage = (event) => {
  const { type, payload } = event.data;

  switch (type) {
    case "insert":
      const emb = pipe.embed_text(payload.text);
      pipe.insert(payload.id, emb, JSON.stringify(payload.metadata), Date.now());
      self.postMessage({ type: "inserted", id: payload.id });
      break;

    case "search":
      const queryEmb = pipe.embed_text(payload.query);
      const results = pipe.search(queryEmb, payload.k || 10);
      self.postMessage({ type: "results", data: results });
      break;
  }
};

SharedArrayBuffer

For multi-threaded WASM (e.g., parallel batch embedding), set the required headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Platform Build

Build Targets

# Native (current platform)
cargo build -p ospipe --release

# WASM (browser)
cargo build -p ospipe --target wasm32-unknown-unknown --release

# Generate JS bindings
wasm-pack build examples/OSpipe --target web --release

# Windows (cross-compile)
cross build -p ospipe --target x86_64-pc-windows-gnu --release

# macOS ARM (cross-compile)
cross build -p ospipe --target aarch64-apple-darwin --release

# macOS Intel (cross-compile)
cross build -p ospipe --target x86_64-apple-darwin --release

# Linux ARM (cross-compile)
cross build -p ospipe --target aarch64-unknown-linux-gnu --release

Conditional Compilation

OSpipe uses conditional compilation to separate native and WASM dependencies:

  • Native (cfg(not(target_arch = "wasm32"))) -- links against ruvector-core, ruvector-filter, ruvector-cluster, ruvector-delta-core, ruvector-router-core, and cognitum-gate-kernel
  • WASM (cfg(target_arch = "wasm32")) -- uses wasm-bindgen, js-sys, serde-wasm-bindgen, and getrandom with the js feature

The src/wasm/helpers.rs module contains pure Rust functions (cosine similarity, hash embedding, safety classification, query routing) that compile on all targets and are tested natively.

Quantization Tiers

OSpipe progressively compresses older embeddings to reduce long-term storage costs. The default quantization schedule:

AgeMethodBits/DimMemory vs f32Description
0 hoursNone (f32)32100%Full precision for recent content
24 hoursScalar (int8)825%Minimal quality loss, 4x compression
1 weekProduct~2~6%Codebook-based compression
30 daysBinary13%Single bit per dimension, 97% savings

Custom Tiers

use ospipe::config::{StorageConfig, QuantizationTier, QuantizationMethod};

let storage = StorageConfig {
    quantization_tiers: vec![
        QuantizationTier { age_hours: 0,    method: QuantizationMethod::None },
        QuantizationTier { age_hours: 12,   method: QuantizationMethod::Scalar },
        QuantizationTier { age_hours: 72,   method: QuantizationMethod::Product },
        QuantizationTier { age_hours: 360,  method: QuantizationMethod::Binary },
    ],
    ..Default::default()
};

Memory Estimate

For 1 million frames at 384 dimensions:

TierBytes/VectorTotal (1M vectors)
f321,5361.43 GB
int8384366 MB
Product~96~91 MB
Binary4846 MB

With the default age distribution (most content aging past 30 days), long-term average storage is approximately 50-80 MB per million frames.


API Reference

Rust API

Core Types

TypeModuleDescription
OsPipeConfigconfigTop-level configuration
CaptureConfigconfigCapture subsystem settings
StorageConfigconfigHNSW and quantization settings
SearchConfigconfigSearch weights and defaults
SafetyConfigconfigPII detection toggles
CapturedFramecaptureA captured screen/audio/UI frame
CaptureSourcecaptureSource enum: Screen, Audio, Ui
FrameContentcaptureContent enum: OcrText, Transcription, UiEvent
FrameMetadatacaptureMetadata (app, window, monitor, confidence, language)
OsPipeErrorerrorUnified error type

Pipeline

Type / FunctionModuleDescription
IngestionPipeline::new(config)pipeline::ingestionCreate a new pipeline
IngestionPipeline::ingest(frame)pipeline::ingestionIngest a single frame
IngestionPipeline::ingest_batch(frames)pipeline::ingestionIngest multiple frames
IngestionPipeline::stats()pipeline::ingestionGet ingestion statistics
IngestResultpipeline::ingestionEnum: Stored, Deduplicated, Denied
PipelineStatspipeline::ingestionCounters for ingested/deduped/denied/redacted
FrameDeduplicatorpipeline::dedupCosine similarity sliding window

Storage

Type / FunctionModuleDescription
VectorStore::new(config)storage::vector_storeCreate a new vector store
VectorStore::insert(frame, embedding)storage::vector_storeInsert a frame with its embedding
VectorStore::search(query, k)storage::vector_storeTop-k nearest neighbor search
VectorStore::search_filtered(query, k, filter)storage::vector_storeSearch with metadata filters
SearchResultstorage::vector_storeResult with id, score, metadata
SearchFilterstorage::vector_storeFilter by app, time range, content type, monitor
StoredEmbeddingstorage::vector_storeStored vector with metadata and timestamp
EmbeddingEngine::new(dim)storage::embeddingCreate an embedding engine
EmbeddingEngine::embed(text)storage::embeddingGenerate a normalized embedding
EmbeddingEngine::batch_embed(texts)storage::embeddingBatch embedding generation
cosine_similarity(a, b)storage::embeddingCosine similarity between two vectors
Type / FunctionModuleDescription
QueryRouter::new()search::routerCreate a query router
QueryRouter::route(query)search::routerRoute a query to optimal backend
QueryRoutesearch::routerEnum: Semantic, Keyword, Graph, Temporal, Hybrid
HybridSearch::new(weight)search::hybridCreate a hybrid search with semantic weight
HybridSearch::search(store, query, emb, k)search::hybridCombined semantic + keyword search

Safety

Type / FunctionModuleDescription
SafetyGate::new(config)safetyCreate a safety gate
SafetyGate::check(content)safetyCheck content, return safety decision
SafetyGate::redact(content)safetyRedact and return cleaned content
SafetyDecisionsafetyEnum: Allow, AllowRedacted(String), Deny { reason }

WASM API (OsPipeWasm)

MethodParametersReturnsDescription
new(dimension)usizeOsPipeWasmConstructor
insert(id, embedding, metadata, timestamp)&str, &[f32], &str, f64Result<(), JsValue>Insert a frame
search(query_embedding, k)&[f32], usizeJsValue (JSON array)Semantic search
search_filtered(query_embedding, k, start, end)&[f32], usize, f64, f64JsValue (JSON array)Time-filtered search
is_duplicate(embedding, threshold)&[f32], f32boolDeduplication check
embed_text(text)&strVec<f32>Hash-based text embedding
batch_embed(texts)JsValue (Array)JsValue (Array)Batch text embedding
safety_check(content)&strStringReturns "allow", "redact", or "deny"
route_query(query)&strStringReturns "Semantic", "Keyword", "Graph", or "Temporal"
len()--usizeNumber of stored embeddings
stats()--String (JSON)Pipeline statistics

Testing

# Run all 56 tests
cargo test -p ospipe

# Run with verbose output
cargo test -p ospipe -- --nocapture

# Run only integration tests
cargo test -p ospipe --test integration

# Run only unit tests (embedding, WASM helpers)
cargo test -p ospipe --lib

# Build for WASM (verify compilation)
cargo build -p ospipe --target wasm32-unknown-unknown

# Build with wasm-pack for JS bindings
wasm-pack build examples/OSpipe --target web

Test Coverage

Test CategoryCountModule
Configuration2tests/integration.rs
Capture frames3tests/integration.rs
Embedding engine6src/storage/embedding.rs
Vector store4tests/integration.rs
Deduplication2tests/integration.rs
Safety gate6tests/integration.rs
Query routing4tests/integration.rs
Hybrid search2tests/integration.rs
Ingestion pipeline5tests/integration.rs
Cosine similarity3tests/integration.rs
WASM helpers18src/wasm/helpers.rs
Total56

  • ADR: OSpipe Screenpipe Integration -- Architecture Decision Record with full design rationale
  • Screenpipe -- Open-source local-first desktop recording + AI memory
  • RuVector -- 70+ Rust crates for vector search, graph neural networks, and attention mechanisms
  • @ruvector/ospipe -- TypeScript SDK (npm)
  • @ruvector/ospipe-wasm -- WASM package (npm)

License

Licensed under either of:

at your option.