shivvr 🔪 v0.6.1

Ephemeral semantic embedding & cognitive agent service.

Chunk text. Embed text, images and voice with Gemma 4 embeddings (EmbeddingGemma 2) by default, with GTR-T5-base as an option. Search via hybrid FST-BM25 vector rank fusion. Run an agent that streams turn-based reasoning over native Model Context Protocol (MCP).

View API Quick Start GitHub Health
On-demand
Uptime
CUDA
Compute
Ephemeral
Sessions
Ephemeral
Chunks
✓
Encryption
Optional
Inversion

Capabilities

Ingest

Sentence-boundary chunking + EmbeddingGemma 2 (default, 768d) or GTR-T5-base embeddings. Each session or temp store keeps the model it was created with. Stores in RwLock<HashMap> — pure ephemeral compute.

Inversion

Every chunk also keeps a GTR-T5-base vector (no extra inference). Ask for it with return_vectors and /invert turns it back into approximate text, whatever the session's search model.

FST-BM25 Hybrid Search

RRF blending dense vectors and sparse lexical indices. FST dictionary scanning provides microsecond safe query guardrails and intent-entity score boosting.

Cognitive GhostAgent

Turn-based agent loop that streams each turn over SSE. Its tools: search memory and ingest documents with the session's own embedding model (Gemma 4 embeddings by default, GTR-T5-base if the session chose it), list sessions, and run sandboxed commands. Powered by OpenAI or Anthropic; pick the model with OPENAI_MODEL or ANTHROPIC_MODEL.

Native MCP Server

Complete Model Context Protocol HTTP/SSE server endpoint (/mcp/sse) allowing immediate, zero-config integration with Claude Code, Antigravity, and Codex.

Crypto

Per-agent orthogonal matrix rotation on embeddings. Cosine similarity preserved under encryption. Keys are in-memory only.

Dual embedding

organize role uses the store's text model (EmbeddingGemma 2 by default, or GTR-T5-base; 768d). retrieve role uses OpenAI text-embedding-ada-002 (1536d) — pass your own key or set server-side.

API

API requests require a nuts-auth bearer token. The GPU backend starts on demand, so the first request can take longer.

MethodEndpointDescription
GET/healthGateway status; model backend starts on an authorized API request
GET/mcp/sseMCP Server SSE handshake
POST/mcp/messageMCP Server JSON-RPC message router
POST/sessions/:id/agent/chatStream non-blocking GhostAgent cognitive turns (SSE)
POST/sessions/:id/ingestChunk + embed text into session
GET/sessions/:id/search?q=...Semantic search (supports RRF hybrid & lexical_only)
GET/sessions/:idSession metadata
DELETE/sessions/:idDelete session
GET/tempList temp stores with TTL
POST/temp/:name/ingestIngest into temp store (2 hr TTL)
GET/temp/:name/search?q=...Search temp store
DELETE/temp/:nameDelete temp store
POST/agent/:id/registerRegister per-agent orthogonal key
POST/agent/:id/encryptEncrypt embeddings
POST/agent/:id/decryptDecrypt embeddings
POST/invertReconstruct approximate text from a GTR-T5-base vector (every chunk keeps one; see return_vectors)
POST/embedBatched text vectors, no store side effects. model: embeddinggemma-2 (default, 768d; optional task and dimensions, see below), gtr-t5-base (768d) or siglip-base-patch16-224 (SigLIP text space, comparable with /image/embed)
POST/image/embedSigLIP image vector (768d) from image_base64; same space as SigLIP text. EmbeddingGemma 2 images replace SigLIP once its vision path is served
POST/audio/transcribeTranscript from audio_base64 via the transcription service
POST/audio/embedTranscript + text vector from audio_base64 (embeddinggemma-2 by default, or model: gtr-t5-base)

Request limits: 32 MiB bodies on image, audio and ingest routes, 8 MiB on /embed, 2 MiB elsewhere; 120 s server timeout on inference routes, 30 s on the rest.

Quick start

# Ingest into session
curl -H "Authorization: Bearer <nuts-auth-token>" -X POST https://shivvr.nuts.services/sessions/my-session/ingest \
  -H "Content-Type: application/json" \
  -d '{"text": "Supreme Raven is protected by Known Opossum.", "source": "vault_specs"}'

# Autonomous Agent Conversational Chat (Streams Thoughts, ToolCalls, & Answer via SSE)
curl -H "Authorization: Bearer <nuts-auth-token>" -i -X POST https://shivvr.nuts.services/sessions/my-session/agent/chat \
  -H "Content-Type: application/json" \
  -d '{"message": "Who protects the Supreme Raven?"}'

# Vector-Lexical Hybrid RRF Search
curl -H "Authorization: Bearer <nuts-auth-token>" "https://shivvr.nuts.services/sessions/my-session/search?q=Known+Opossum&hybrid=true"

# High-speed Lexical-Only BM25 Search (Bypasses ONNX embedder)
curl -H "Authorization: Bearer <nuts-auth-token>" "https://shivvr.nuts.services/sessions/my-session/search?q=Opossum&lexical_only=true"

# Ingest and get each chunk's GTR vector too, then invert it back to text
curl -H "Authorization: Bearer <nuts-auth-token>" -X POST https://shivvr.nuts.services/sessions/my-session/ingest \
  -H "Content-Type: application/json" \
  -d '{"text": "The harbor was quiet at dawn.", "return_vectors": ["gtr-t5-base"]}'
curl -H "Authorization: Bearer <nuts-auth-token>" -X POST https://shivvr.nuts.services/invert \
  -H "Content-Type: application/json" \
  -d '{"embedding": [/* chunks[0].embeddings["gtr-t5-base"] */], "model": "gtr-t5-base"}'

# Text vectors with EmbeddingGemma 2 (query prefix, 256d)
curl -X POST https://shivvr.nuts.services/embed \
  -H "Content-Type: application/json" \
  -d '{"texts": ["Who protects the Supreme Raven?"], "model": "embeddinggemma-2", "task": "query", "dimensions": 256}'

# Upload an image: base64 the file into a JSON body, then ingest it (stored as an [image] chunk, searchable by text)
printf '{"image_base64": "%s", "text": "boat at the fuel dock"}' "$(base64 -w0 photo.jpg)" > image.json
curl -H "Authorization: Bearer <nuts-auth-token>" -X POST https://shivvr.nuts.services/sessions/my-session/ingest \
  -H "Content-Type: application/json" --data @image.json

# Or just get the image vector back (768d), nothing stored
curl -H "Authorization: Bearer <nuts-auth-token>" -X POST https://shivvr.nuts.services/image/embed \
  -H "Content-Type: application/json" --data @image.json

# Upload a voice file (WAV): it is transcribed, then the transcript is chunked and embedded
printf '{"audio_base64": "%s"}' "$(base64 -w0 note.wav)" > voice.json
curl -H "Authorization: Bearer <nuts-auth-token>" -X POST https://shivvr.nuts.services/sessions/my-session/ingest \
  -H "Content-Type: application/json" --data @voice.json

# Or get the transcript and its vector back, nothing stored
curl -H "Authorization: Bearer <nuts-auth-token>" -X POST https://shivvr.nuts.services/audio/embed \
  -H "Content-Type: application/json" --data @voice.json

# Synchronize Claude Code or Antigravity with shivvr's Native MCP Server
nemesis8 mcp add https://shivvr.nuts.services/mcp/sse

Search parameters

ParamDefaultDescription
qrequiredQuery text
n5Number of results
hybridfalseBlend semantic vectors + BM25 scores (Reciprocal Rank Fusion)
lexical_onlyfalseBypass vector embedder, execute pure BM25 search
guardrailtrueEnable FST toxic term scanning and automatic query blocking
roleorganizeorganize (768d, the store's text model; the query gets EmbeddingGemma 2's query prefix) or retrieve (1536d OpenAI)
time_weight0.0Blend semantic + recency score (0–1)
decay_halflife_hours168Recency decay half-life in hours
include_nearbyfalseReturn temporally adjacent chunks
agent_id—Agent ID for encrypted search
openai_api_key—Per-request OpenAI key for retrieve role (overrides server key)

Ingest request (POST /sessions/:id/ingest, /temp/:name/ingest)

FieldDefaultDescription
text—Text to chunk and embed. Send text, audio_base64, image_base64 or any mix
audio_base64—A base64 WAV voice file. It is transcribed, the transcript is added to text, and chunks get source: "audio". Needs the transcription service (TRANSCRIPTION_URL; /health shows audio_available)
image_base64—A base64 JPEG or PNG. Stored as one [image] chunk (plus any text as its caption) with source: "image"
modelembeddinggemma-2The store's text model: embeddinggemma-2 or gtr-t5-base. Set on the first ingest; later ingests use the store's model, and naming a different one is a 400
return_vectorsnone["gtr-t5-base"] returns each chunk's GTR vector under embeddings, ready for /invert
source, metadata—Stored on every chunk

Response: {"chunks_created", "tokens_processed", "time_ms", "model", "chunks"}; each chunk's embedding is the store's search vector.

Embed request (POST /embed)

FieldDefaultDescription
textsrequired1–256 strings, 32 KiB each
modelembeddinggemma-2embeddinggemma-2 (default), gtr-t5-base or siglip-base-patch16-224; 503 if that model is not loaded
tasknoneembeddinggemma-2 only: prompt prefix by name, e.g. query, document, Clustering, Classification, STS, CodeRetrieval, QuestionAnswering, FactChecking (case-insensitive)
dimensions768embeddinggemma-2 only: 128, 256, 512 or 768 (Matryoshka truncation, re-normalized)

Response: {"model", "dim", "vectors"}, unit-length vectors. Use task=query for search queries and task=document for the passages they search.

Image and voice requests

RouteBodyResponse
POST /image/embed{"image_base64": "..."} (JPEG or PNG){"embedding", "dimension", "model"}, 768d, in the same space as /embed with model: siglip-base-patch16-224, so a text query can find the image
POST /audio/embed{"audio_base64": "...", "model": "embeddinggemma-2"} (WAV; model optional, or gtr-t5-base){"transcript", "embedding", "dimension", "model"}
POST /audio/transcribe{"audio_base64": "..."} (WAV){"transcript"}

Bodies up to 32 MiB, so about 24 MB of file after base64. Build the JSON in a file and send it with --data @file.json; a large base64 string is too long for a command-line argument.

Environment

VariableDefaultDescription
PORT8080Listen port
BIND_ADDR127.0.0.1Listen address; the Docker images set 0.0.0.0
MODEL_PATHmodels/gtr-t5-base.onnxGTR-T5-base ONNX embedder
TOKENIZER_PATHmodels/tokenizer.jsonTokenizer
VISION_MODEL_PATHmodels/siglip-vision.onnxSigLIP vision tower (/image/embed) — optional
SIGLIP_TEXT_MODEL_PATHmodels/siglip-text.onnxSigLIP text tower (/embed model=siglip-base-patch16-224) — optional
SIGLIP_TOKENIZER_PATHmodels/siglip-tokenizer.jsonSigLIP tokenizer
EMBEDDINGGEMMA2_MODEL_PATHmodels/embeddinggemma2-text.onnxEmbeddingGemma 2 text path (/embed model=embeddinggemma-2) — optional
EMBEDDINGGEMMA2_TOKENIZER_PATHmodels/embeddinggemma2-tokenizer.jsonEmbeddingGemma 2 tokenizer
EMBEDDINGGEMMA2_MAX_TOKENS2048EmbeddingGemma 2 input token cap (up to 8192; cost grows faster than linearly with length)
OPENAI_API_KEY—Enables OpenAI completions and retrieve embeddings
ANTHROPIC_API_KEY—Enables Anthropic completions and GhostAgent loops
NUTS_AUTH_JWKS_URL—Gateway requires auth
NUTS_AUTH_VALIDATE_URLhttps://auth.nuts.services/api/validateAPI token validation endpoint

Images

ImageContents
deepbluedynamics/shivvrCUDA build with EmbeddingGemma 2, GTR-T5-base, SigLIP and the vec2text inverter baked in (latest, X.Y.Z, vX.Y.Z; linux/amd64)
deepbluedynamics/shivvr:latest-cpuCPU build with no models baked in (latest-cpu, X.Y.Z-cpu, vX.Y.Z-cpu; linux/amd64, linux/arm64); mount /models from shivvr-models:int8
deepbluedynamics/shivvr-models:int8One-shot volume installer (440 MB int8): EmbeddingGemma 2 text + GTR-T5-base (linux/amd64, linux/arm64)
deepbluedynamics/shivvr-gatewayThe GPU-less front door: auth, this page, and a proxy to the backend (latest, X.Y.Z, vX.Y.Z; linux/amd64)

Run it yourself

# GPU: all models are in the image (~4.4 GB pull)
docker run -d --name shivvr --gpus all -p 127.0.0.1:8080:8080 deepbluedynamics/shivvr

# CPU (amd64/arm64, incl. Raspberry Pi 5):
# 1. a volume for the models
docker volume create shivvr-models
# 2. copy the int8 models (440 MB) into it; this fills /target, replacing what's there, and exits
docker run --rm -v shivvr-models:/target deepbluedynamics/shivvr-models:int8
# 3. start shivvr with the models mounted read-only
docker run -d --name shivvr -p 127.0.0.1:8080:8080 -v shivvr-models:/models:ro deepbluedynamics/shivvr:latest-cpu

Stack

LayerChoice
RuntimeRust + Tokio + axum
CognitionGhostAgent cognitive RAG turn loop (OpenAI / Anthropic compat)
MCP ServerHTTP/SSE JSON-RPC 2.0 Model Context Protocol transport layer
Hybrid IndexLume 0.16: FST phrase tagger + stemmed BM25 with MaxScore pruning
EmbeddingEmbeddingGemma 2 (768d default, Matryoshka to 512/256/128) & GTR-T5-base (768d) via ONNX Runtime 2.0
Text embedding (/embed)EmbeddingGemma 2 (default), GTR-T5-base, SigLIP text tower (768d, shared space with /image/embed)
StorageEphemeral RwLock<HashMap> — no disk, no volume mounts
GPUCUDA 12.6 via ort EP on Cloud Run L4 — CPU fallback automatic
Inversionvec2text gtr-base (projection + T5 enc/dec) on the GTR vector every chunk keeps
ImagesSigLIP base patch16-224 for now; EmbeddingGemma 2 vision replaces it once served