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).
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.
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.
RRF blending dense vectors and sparse lexical indices. FST dictionary scanning provides microsecond safe query guardrails and intent-entity score boosting.
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.
Complete Model Context Protocol HTTP/SSE server endpoint (/mcp/sse) allowing immediate, zero-config integration with Claude Code, Antigravity, and Codex.
Per-agent orthogonal matrix rotation on embeddings. Cosine similarity preserved under encryption. Keys are in-memory only.
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 requests require a nuts-auth bearer token. The GPU backend starts on demand, so the first request can take longer.
| Method | Endpoint | Description |
|---|---|---|
| GET | /health | Gateway status; model backend starts on an authorized API request |
| GET | /mcp/sse | MCP Server SSE handshake |
| POST | /mcp/message | MCP Server JSON-RPC message router |
| POST | /sessions/:id/agent/chat | Stream non-blocking GhostAgent cognitive turns (SSE) |
| POST | /sessions/:id/ingest | Chunk + embed text into session |
| GET | /sessions/:id/search?q=... | Semantic search (supports RRF hybrid & lexical_only) |
| GET | /sessions/:id | Session metadata |
| DELETE | /sessions/:id | Delete session |
| GET | /temp | List temp stores with TTL |
| POST | /temp/:name/ingest | Ingest into temp store (2 hr TTL) |
| GET | /temp/:name/search?q=... | Search temp store |
| DELETE | /temp/:name | Delete temp store |
| POST | /agent/:id/register | Register per-agent orthogonal key |
| POST | /agent/:id/encrypt | Encrypt embeddings |
| POST | /agent/:id/decrypt | Decrypt embeddings |
| POST | /invert | Reconstruct approximate text from a GTR-T5-base vector (every chunk keeps one; see return_vectors) |
| POST | /embed | Batched 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/embed | SigLIP image vector (768d) from image_base64; same space as SigLIP text. EmbeddingGemma 2 images replace SigLIP once its vision path is served |
| POST | /audio/transcribe | Transcript from audio_base64 via the transcription service |
| POST | /audio/embed | Transcript + 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.
# 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
| Param | Default | Description |
|---|---|---|
q | required | Query text |
n | 5 | Number of results |
hybrid | false | Blend semantic vectors + BM25 scores (Reciprocal Rank Fusion) |
lexical_only | false | Bypass vector embedder, execute pure BM25 search |
guardrail | true | Enable FST toxic term scanning and automatic query blocking |
role | organize | organize (768d, the store's text model; the query gets EmbeddingGemma 2's query prefix) or retrieve (1536d OpenAI) |
time_weight | 0.0 | Blend semantic + recency score (0–1) |
decay_halflife_hours | 168 | Recency decay half-life in hours |
include_nearby | false | Return temporally adjacent chunks |
agent_id | — | Agent ID for encrypted search |
openai_api_key | — | Per-request OpenAI key for retrieve role (overrides server key) |
POST /sessions/:id/ingest, /temp/:name/ingest)| Field | Default | Description |
|---|---|---|
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" |
model | embeddinggemma-2 | The 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_vectors | none | ["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.
POST /embed)| Field | Default | Description |
|---|---|---|
texts | required | 1–256 strings, 32 KiB each |
model | embeddinggemma-2 | embeddinggemma-2 (default), gtr-t5-base or siglip-base-patch16-224; 503 if that model is not loaded |
task | none | embeddinggemma-2 only: prompt prefix by name, e.g. query, document, Clustering, Classification, STS, CodeRetrieval, QuestionAnswering, FactChecking (case-insensitive) |
dimensions | 768 | embeddinggemma-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.
| Route | Body | Response |
|---|---|---|
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.
| Variable | Default | Description |
|---|---|---|
PORT | 8080 | Listen port |
BIND_ADDR | 127.0.0.1 | Listen address; the Docker images set 0.0.0.0 |
MODEL_PATH | models/gtr-t5-base.onnx | GTR-T5-base ONNX embedder |
TOKENIZER_PATH | models/tokenizer.json | Tokenizer |
VISION_MODEL_PATH | models/siglip-vision.onnx | SigLIP vision tower (/image/embed) — optional |
SIGLIP_TEXT_MODEL_PATH | models/siglip-text.onnx | SigLIP text tower (/embed model=siglip-base-patch16-224) — optional |
SIGLIP_TOKENIZER_PATH | models/siglip-tokenizer.json | SigLIP tokenizer |
EMBEDDINGGEMMA2_MODEL_PATH | models/embeddinggemma2-text.onnx | EmbeddingGemma 2 text path (/embed model=embeddinggemma-2) — optional |
EMBEDDINGGEMMA2_TOKENIZER_PATH | models/embeddinggemma2-tokenizer.json | EmbeddingGemma 2 tokenizer |
EMBEDDINGGEMMA2_MAX_TOKENS | 2048 | EmbeddingGemma 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_URL | https://auth.nuts.services/api/validate | API token validation endpoint |
| Image | Contents |
|---|---|
deepbluedynamics/shivvr | CUDA 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-cpu | CPU 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:int8 | One-shot volume installer (440 MB int8): EmbeddingGemma 2 text + GTR-T5-base (linux/amd64, linux/arm64) |
deepbluedynamics/shivvr-gateway | The GPU-less front door: auth, this page, and a proxy to the backend (latest, X.Y.Z, vX.Y.Z; linux/amd64) |
# 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
| Layer | Choice |
|---|---|
| Runtime | Rust + Tokio + axum |
| Cognition | GhostAgent cognitive RAG turn loop (OpenAI / Anthropic compat) |
| MCP Server | HTTP/SSE JSON-RPC 2.0 Model Context Protocol transport layer |
| Hybrid Index | Lume 0.16: FST phrase tagger + stemmed BM25 with MaxScore pruning |
| Embedding | EmbeddingGemma 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) |
| Storage | Ephemeral RwLock<HashMap> — no disk, no volume mounts |
| GPU | CUDA 12.6 via ort EP on Cloud Run L4 — CPU fallback automatic |
| Inversion | vec2text gtr-base (projection + T5 enc/dec) on the GTR vector every chunk keeps |
| Images | SigLIP base patch16-224 for now; EmbeddingGemma 2 vision replaces it once served |