vipune /ˈʋi.pu.ne/
███
░░░
█████ █████ ████ ████████ █████ ████ ████████ ██████
░░███ ░░███ ░░███ ░░███░░███░░███ ░███ ░░███░░███ ███░░███
░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███████
░░███ ███ ░███ ░███ ░███ ░███ ░███ ░███ ░███ ░███░░░
░░█████ █████ ░███████ ░░████████ ████ █████░░██████
░░░░░ ░░░░░ ░███░░░ ░░░░░░░░ ░░░░ ░░░░░ ░░░░░░
░███
█████
░░░░░
A minimal memory layer for AI agents.
In Finnish mythology, Antero Vipunen is a giant who sleeps underground, holding all the world's knowledge and ancient songs. vipune is your agent's sleeping giant — a local knowledge store that remembers everything.
Store semantic memories, search by meaning, and detect conflicts. Single binary CLI. No API keys required.
Features
- Semantic search - Find memories by meaning, not keywords (ONNX embeddings, bge-small-en-v1.5)
- Conflict detection - Automatically warns when adding duplicate or similar memories
- Zero configuration - Works out of the box (auto-detected git projects, sensible defaults)
- Single binary - Just one CLI tool, no daemon, no database server
- No API keys - Everything runs locally, no network dependencies
- Project scoped - Memories isolated by git repository
Installation
Platform Support
Supported: macOS ARM64, Linux x86_64, Linux ARM64
Not supported: Windows (due to ONNX Runtime compilation complexity)
Quick install (macOS and Linux)
|
The installer detects your platform, downloads the correct binary, and adds it to
~/.cargo/bin/by default. If you don't use Rust, restart your shell or runexport PATH="$HOME/.cargo/bin:$PATH"to make vipune available. Use--prefix=/usr/localfor system-wide installs.
To verify the download before running, download the checksum file and check:
Pre-built binary
macOS Apple Silicon (arm64)
Download and extract:
&&
Linux x86_64
Download and extract:
&&
Linux ARM64
Download and extract:
&&
Build from source
Requires the Rust toolchain (1.92+). On Linux, you may also need libgomp1 and libc6.
Latest release (recommended)
Or clone and build manually
&&
The binary will be at ./target/release/vipune. Install it:
&&
Or add to PATH temporarily:
Install Claude/Pi skill (optional)
For agents with skill auto-discovery (Claude Code, Pi), install the vipune skill artifact to enable software-development-tuned memory patterns:
&& \
The skill becomes available at this URL once this change is merged to main.
This adds domain-specific patterns: issue/PR linkage, failed-approach tracking, pre-flight gotcha checks, dev-loop mapping, and ADR-lite decision capture. Use <project>/.claude/skills/ for project-scoped installation. See Agent Integration for details.
Note: Tier-2 (automated install via vipune skill install) and Tier-3 (cross-agent AGENTS.md snippets) are future follow-ups.
Uninstall
Or via cargo: cargo uninstall vipune
Optionally, clear all data:
Embedding Models
vipune embeds memories with a local ONNX model downloaded from HuggingFace Hub on first use, pinned to an exact revision for reproducibility. The model is chosen per database via the embedding_model setting (see Configuration) — set it before your first vipune add, because a database records which model it was created with.
Built-in profiles
| Model ID | Revision | Languages | Download size | Prefixes |
|---|---|---|---|---|
BAAI/bge-small-en-v1.5 (default) |
5c38ec7c405ec4b44b94cc5a9bb96e735b38267a |
English | ~66 MB | none |
intfloat/multilingual-e5-small |
614241f622f53c4eeff9890bdc4f31cfecc418b3 |
~100 languages (xlm-roberta) | ~470 MB | automatic query: / passage: |
Choosing a model: the default bge-small-en-v1.5 is fast and small but English-focused. If your memories are in other languages (e.g. Finnish, Swedish, German), select the multilingual profile instead — it embeds ~100 languages, at the cost of a much larger one-time download:
# Either set the environment variable
# ...or in ~/.config/vipune/config.toml
Automatic prefixes (e5 only)
The e5 model requires task prefixes to work well. vipune applies them automatically: query: is prepended to search queries and passage: to stored text at embedding time. Do not add these prefixes yourself — they are never written into stored content, search output, or the FTS index; they exist only at embed time.
The bge-small-en-v1.5 profile uses no prefixes.
Note: the prefix consumes tokens of the 512-token limit, so slightly longer texts that embed fine under bge may be rejected (exit code 3) under e5 — over-length input is always rejected, never truncated.
Switching models on an existing database
A database records the model id + revision it was created with. If the configured model no longer matches the recorded identity, add, update, and search refuse with an error naming both identities — the stored embeddings are in a different vector space and results would be meaningless.
To migrate an existing database to a different model:
Library consumers can call vipune::migrate_model(db_path, &config) in-process — see the Library Usage section for the close-then-reopen cutover sequence.
This re-embeds every stored memory with the newly configured model (a plain vipune reindex would re-embed nothing, since existing vectors already look real). The migration is crash-safe and pre-checked:
- Pre-flight —
reindex --forcefirst token-counts every stored row with the target model's passage prefix. If any row would exceed the 512-token limit once prefixed, it refuses to start, writes nothing, and lists the offending memory ids. The migration marker is only ever written in a state the re-embed pass can complete. - Marker-first — it records a
migrating to <id>@<revision>marker before re-embedding. - Re-embed + record — it re-embeds every row, then in one transaction records the new identity and clears the marker.
If the run is interrupted, the marker stays and operations refuse until you re-run vipune reindex --force, which performs a full idempotent pass from the beginning.
Air-gapped / Offline Usage
vipune downloads the embedding model from HuggingFace Hub on first run. Each model is pinned to a specific revision to ensure reproducibility:
BAAI/bge-small-en-v1.5— revision5c38ec7c405ec4b44b94cc5a9bb96e735b38267aintfloat/multilingual-e5-small— revision614241f622f53c4eeff9890bdc4f31cfecc418b3
For air-gapped environments, pre-fetch the default model before going offline:
# Install huggingface-cli first if you don't have it
# Download the model revision to the cache directory
If you selected the multilingual profile instead, pre-fetch that one as well:
The model will be cached in the HF Hub cache layout at ~/.cache/huggingface/hub/ and vipune will use it without network access. You can verify the cache before going offline:
Note: When upgrading vipune, the pinned revision may change. Check the release notes and re-download the new revision if the SHA has changed.
Library consumers: The constants vipune::EMBED_MODEL_ID and vipune::EMBED_MODEL_REVISION are exported for tracking the pinned model version programmatically.
Quick Start
Add a memory:
Search by semantic meaning:
Add with metadata (optional):
CLI Commands
| Command | Description |
|---|---|
vipune add <text> |
Store a memory (with type, status, conflict detection) |
vipune search <query> |
Find memories by meaning (filters by type/status) |
vipune get <id> |
Retrieve a memory by ID |
vipune list |
List memories (default: active only) |
vipune delete <id> |
Delete a memory |
vipune update <id> [text] |
Update content, metadata, type, or status |
vipune validate <text> |
Check if text is within embedding token limits |
vipune version |
Show version |
Complete CLI reference • Quickstart guide • Search guide • Architecture
Library Usage
vipune can also be used as a Rust crate for programmatic integration:
# Cargo.toml
[]
= "0.5.0"
use ;
// Initialize memory store
let config = default;
let mut store = new.expect;
// Add a memory
let project_id = "my-project";
let result = store.add_with_conflict
.expect;
// Search memories
let results = store.search
.expect;
for memory in results
v0.4+ features: MemoryType, MemoryStatus, supersedes flag, and telemetry (retrieval_count) are available for type-aware memory management. See the CLI reference for details.
Direct embedding (EmbeddingEngine): callers that embed directly (bypassing MemoryStore) use the role-aware API — embed_passage for stored text and embed_query for search/classification text. The engine prepends the model profile's prefix (e5: query: / passage: ; bge: none) before tokenisation, and the 512-token limit is enforced on the prefixed text (over-length input is rejected, never truncated). The engine performs no database model-identity check; if you keep vector caches outside vipune, key them by vipune::current_identity's ModelIdentity (model id + revision) so a model switch invalidates them.
Model migration (in-process): if your configured model no longer matches the database's recorded identity, all embedding operations refuse. To migrate in-process (for library consumers):
- Close the store —
drop(store)or close theDatabasehandle.migrate_modelopens its own connection withbusy_timeout=0; a live handle on the same file will cause it to fail immediately with a locked-database error. - Call
vipune::migrate_model— it runs the same crash-safe pre-flight + marker-first + re-embed lifecycle asreindex --force:
use ;
// Start from your fully loaded config (file + environment values) and
// re-point the model id at the migration target.
let mut config = load.expect;
config.embedding_model = "intfloat/multilingual-e5-small".into;
// The old store must be dropped before this call.
let report = migrate_model
.expect;
println!;
// Reopen the store with the new model.
let store = new
.expect;
- Reopen the store — create a new
MemoryStorewith the same path and the new model.
The CLI equivalent is vipune reindex --force, which wraps the same library call with progress output.
See the crate documentation at docs.rs for complete API reference.
Configuration
vipune works with zero configuration. All paths use the user's home directory:
Default paths:
- Database:
~/.vipune/memories.db - Model cache: by default
~/.cache/huggingface/hub(or$HF_HOME/hubwhenHF_HOMEis set) — standard HuggingFace Hub layout, created byhf-hubon first download - Config file:
~/.config/vipune/config.toml
Environment variables (override defaults):
VIPUNE_DATABASE_PATH- SQLite database locationVIPUNE_EMBEDDING_MODEL- embedding model profile:BAAI/bge-small-en-v1.5(default) orintfloat/multilingual-e5-small— see Embedding ModelsHF_HOME- HuggingFace cache home directory (relocates the model cache to$HF_HOME/hub; vipune's resolver honoursHF_HOMEonly, and hf-hub then reads the token from$HF_HOME/token). Use an absolute, user-owned path: a relative value resolves against the working directoryVIPUNE_PROJECT- Project identifier (overrides auto-detection)VIPUNE_SIMILARITY_THRESHOLD- Conflict detection threshold, 0.0-1.0 (default:0.85)VIPUNE_RECENCY_WEIGHT- Recency bias in search results, 0.0-1.0 (default:0.3)VIPUNE_HYBRID- Enable hybrid search (semantic + BM25), true/false or 1/0
Config file (~/.config/vipune/config.toml):
= "~/.vipune/memories.db"
= "BAAI/bge-small-en-v1.5"
= 0.85
= 0.3
MCP Server
vipune can act as an MCP (Model Context Protocol) server, enabling AI agents like Claude Code and Cursor to use it as a native memory provider.
Note: MCP is enabled by default. Library users can use
default-features = falsefor sync-only builds.
Setup (Claude Code)
Add to your Claude Code configuration (~/.claude/settings.json or project .claude.json):
Setup (Cursor)
Add to your Cursor MCP configuration with the same JSON structure.
Available Tools
- store_memory: Store information for later recall
- search_memories: Find memories by meaning (supports
hybridparam for semantic + BM25) - list_memories: List recent memories
- supersede_memory: Replace an existing memory with new content
- get_memory: Retrieve a memory by ID
- delete_memory: Permanently delete a memory
- update_memory: Update a memory's content, metadata, type, status, or importance
See the Claude Desktop on macOS guide for a complete Desktop setup with configuration examples and macOS-specific gotchas.
Agent Integration
vipune works with any agent that can run shell commands — no plugins, adapters, or API keys required. Configure your agent with a few lines of instructions, grant shell command permissions, and the agent can use vipune search and vipune add to maintain persistent memory across tasks.
→ See Agent Integration Guide for per-tool setup instructions (Claude Code, Cursor, Windsurf, Cline, Roo Code, GitHub Copilot, Goose, Aider, OpenCode, Zed, and more).
See also: Search Guide for agent-friendly query patterns.
Exit codes for agent workflows:
0- Success1- Error (missing file, invalid input, etc.)2- Conflicts detected (similar memories found)3- Content too long (exceeds embedding token limit)
Recency Scoring
Search results can be weighted by recency using the --recency flag or VIPUNE_RECENCY_WEIGHT config:
# Increase recency bias (recent memories rank higher)
# Pure semantic similarity (no recency bias)
The final score combines semantic similarity and recency time decay:
score = (1 - recency_weight) * similarity + recency_weight * time_score- Default balance: 70% semantic, 30% recency
Benchmarks
The repo ships a Criterion benchmark for the Database::search path. It benchmarks search over a synthetic corpus of 384-dim vectors at 1k and 10k rows, seeded only through the public API — no ONNX model and no network access required.
Run the benchmark locally:
A plain cargo bench compares the run against a stored main baseline when one exists (lenient mode: reports differences, never fails on them) and never overwrites the baseline.
Record or refresh the main baseline deliberately — this is the only run that writes to the baseline store:
After a performance-shaped change, force a strict comparison against it (fails only if the baseline is missing; a regression is reported in red text but does not fail the run):
Or a lenient comparison (reports the difference, no failure if the baseline is missing):
Criterion stores results and baselines under target/criterion/. No committed baseline is bundled: baselines are machine-specific, so each machine records its own main baseline and later runs diff against that stable reference numerically.
The bench reports timings only — it asserts no timing thresholds, so a slower result is a signal to look at, never a build failure.
License
Apache-2.0 © Janni Turunen