Obsidian search and LLM conversation import with voracle
voracle, a Rust MCP / CLI for Obsidian search, incremental embedding caches, and conversation import for LLM integration in development workflows.
The search tool
I implemented voracle, a Rust MCP / CLI that searches Obsidian notes with local embedding and reranking. It retrieves development notes and imports LLM conversations.
- MCP server: stdio JSON-RPC 2.0, exposing
searchandreadtools - CLI commands:
-ingest(index building),-list/-rg(semantic search),-distil(external LLM conversation ingestion) - Multiple vault roots: specified via
--rootflags orVORACLE_PATHenvironment variable (colon-separated) - Embedding cache: differential ingest via
.embeddings.jsonl. Unchanged notes are restored from cache - Model placement: ONNX models stored in
~/.local/share/voracle/models/
# Build index (first run embeds all chunks, subsequent runs only diff)
voracle -ingest
# Semantic search
voracle -list "kubernetes networking"
# Start MCP server
voracle --server
# Ingest LLM conversation (from clipboard)
voracle -distil c
Background
Its predecessor, valut-oracle, used ONNX Runtime with pplx-embed-v1-0.6b and ColBERT mxbai-edge-colbert. MCP uses stdio JSON-RPC 2.0.
It handles vault search alongside the Rust MCP tools pathfinder and shelpa (now filesystem).
Articles, design notes, and domain files had outgrown my memory. I wanted to find chunks from vague queries and bring the existing distil conversation import into the same tool.
Multiple vault root support
I replaced vault_path: PathBuf with roots: Vec<PathBuf>. --root supplies multiple roots for recursive scanning by VaultResolver.
voracle --root /path/to/vault1 --root /path/to/vault2 --server
Also configurable via the VORACLE_PATH environment variable, expanded as colon-separated paths. Resolution priority: --root flags > VORACLE_PATH > error.
export VORACLE_PATH=/Volumes/VALUT/obsidian/docs:/Volumes/VALUT/obsidian/articles
Embedding cache
Embedding every chunk is costly with 500+ notes, so .embeddings.jsonl caches unchanged notes.
Each root has a JSONL cache with one row per note: note_id, content_hash (DefaultHasher), mtime_secs, and chunk vectors. The dot prefix hides it from Obsidian UI.
Matching mtime uses the cache. Otherwise, a content-hash check determines whether to re-embed. A touch or copy that only changes the timestamp can reuse the vectors.

The -f flag forces a full re-embed, ignoring the cache. A cached_hits field was added to IngestResult to report how many notes were restored from cache.
Distil command integration
I integrated agent-gateway’s distil shell script for Claude CLI, Codex CLI, and Gemini CLI conversations. Its old POST /v1/obsidian/ingest handler had already been removed.
Three input sources are supported: inline as the second argument, piped from stdin, or from the clipboard via pbpaste if neither is provided. Model aliases map c/claude → claude-opus-4-6, x/codex → codex-5.3, g/gemini → gemini-3.1-pro.
voracle -distil c # from clipboard
voracle -distil claude "text" # inline
cat file.md | voracle -distil x # stdin
Output goes to _notes/llm-completions/{model_dir}/{correlation_id}.md in the first vault root. Frontmatter includes date, status: raw, correlation_id, and model. If the index is already built, a semantic search on the first 100 words is used to populate related note IDs in the related field.
MCP tool cleanup
Removing MCP ingest
MCP initially listed search, read, and ingest. Ingest became a CLI prerequisite; handle_tool_call no longer dispatched it, but its definition remained.
I removed ingest from tools.rs and tool_definitions() in server.rs, leaving search and read.
Results include a callable read_tool_call. Pages of about 1000 characters limit context use.
The final CLI interface looks like this:
usage: voracle [--root <path>]... <command> [args]
options:
--root <path> vault root directory (repeatable)
falls back to VORACLE_PATH (colon-separated)
index:
-ingest [-f] build index (required before search)
search (loads .index.bin):
-list <query> [-k N] semantic file list, default top 3
-rg <query> [-k N] grep-like semantic search with context
-read <note_id> print note content to stdout
tools:
-embed <text> print embedding vector (TSV)
-rerank <query> <file>... rerank files by ColBERT MaxSim score
-status show index status
-distil <model> [content] ingest LLM conversation into vault
model: c(laude), x(codex), g(emini)
reads from: arg, stdin, or clipboard
server:
--server stdio MCP server (JSON-RPC 2.0)
-list and -rg both load .index.bin for semantic search, but -list outputs file paths and scores while -rg displays matched chunks with surrounding context. -read prints full note content to stdout as a CLI-only command, separate from the MCP read tool. -embed and -rerank are low-level inference tools for pipeline debugging and integration with other tools.
Renaming to voracle
I shortened valut-oracle to voracle, including package, binary, and environment variable names.
Cargo.toml: package name changed tovoraclesrc/cli/mod.rs: environment variable changed fromVALUT_ORACLE_PATHtoVORACLE_PATH, all usage messages updatedsrc/config.rs: model directory changed to~/.local/share/voracle/models/src/main.rs: tracing filter changed tovoracle=infosrc/mcp/server.rs:ServerInfo.namechanged tovoracle- All documentation (README.md, 5 files in docs/) unified to the new name
Models live in ~/.local/share/voracle/models/, honoring XDG_DATA_HOME. pathfinder can embed about 32 MB with include_bytes!, but these 700 MB models stay external. git filter-repo removed them from repository history.
