Give your AI coding agent real structural understanding of your codebase. Veska parses your repo into a queryable code graph + semantic index and serves it to your agent (and you) over MCP - so answers come back as exact
file:linespans, not grep-and-guess. Fully local, in-process, zero-setup.
Veska is a local code-intelligence daemon. It runs on your laptop, parses your repository into a code graph (nodes + edges), embeds that graph semantically, and serves both to your editor and your AI agent over MCP - so they reason from the same structural ground truth instead of guessing.
- Grounded structural answers. Every function, type, file, and call traces to a node, edge, or commit. Structural recall stays current within the save → staging freshness budget.
- Eventually-consistent semantic search.
semantic_searchembeds the graph with an in-process embedder (model2vec by default - no external service); during the indexing lag window it falls back to a BM25 lexical index and flags the responsedegraded_reasons. - Promotion checks. On every commit, synchronous checks emit advisory
Findings: dead code, contract drift, leaked secrets, and vulnerablego.moddependencies via the OSV.dev advisory database. All four ship on by default:veska initwrites an active[vuln_source]config block to~/.veska/config.toml(seedocs/operations/CONFIG-SURFACE.md) unless you opt out withveska init --no-vuln(or answer "no" at the interactive prompt). Lifecycle: the block is read at daemon start, so it takes effect from the nextveska service start(restart the service after editing the block). New scans pick it up automatically; to scan already-promoted repos retroactively, runveska reindex <path>. - Duplicate & similar-code detection. Find copy-paste and drifted clones
for de-dupe triage:
eng_find_clonesfor one symbol-group mode at a time (exactbyte-identical, ornearfuzzy via stored similarity), andeng_find_clustersfor a whole-repo (or cross-repo) sweep acrossexact,structural(Type-2, same shape after renaming), andneartiers, ranked tightest first. Exact/structural are deterministic hashes; near reads the similarity scores auto-link already stored (no new embedding sweep). - Optional LLM features. An off-by-default post-promotion review pipeline and per-node summaries (Ollama-backed).
- Mechanical wiki. Hot-zones and entry-points computed from the graph,
no LLM in the path. The
eng_get_hot_zoneandeng_get_entry_pointsMCP tools return data in-memory and write nothing; theveska wikiCLI renders the same data intodocs/veska/{hot_zones,entry_points}.mdinside the repo (re-runnable, idempotent - bracket markers in each page preserve any hand edits outside the managed block). A context-pack tool sits alongside. - Cross-actor attribution. A single
actor_kind: human | agent | systemenum distinguishes who changed what in the audit log.
make build produces a single binary at bin/veska; bin/veska-daemon and
bin/veska-mcp are symlinks to it. The argv[0] dispatcher in
cmd/veska/main.go routes each invocation into its own package.
| Invocation | Role |
|---|---|
veska |
CLI - init, repo, reindex, service, doctor, backup, wiki, … Run veska --help for the full list. |
veska-daemon (symlink) |
Long-running process - owns the SQLite store, the fsnotify watcher, the embedder, and the post-promotion queue. Composition root: internal/cli/daemon/wire.go. |
veska-mcp (symlink) |
Thin stdio shim proxying an editor's MCP connection to the daemon's Unix socket. Routes into internal/cli/mcp. |
Semantic search and auto-linking find the nearest embeddings to a query. Two
backends do that, chosen with VESKA_VECTOR_BACKEND: memory (memvec, the
default - exact linear scan, lowest RAM, no setup) and usearch (approximate
HNSW - flat query latency at scale, needs libusearch_c.so). Measured across Go
repos (memvec is the exact-recall oracle; regenerate with make eval-backend-matrix):
| repo | symbols | q p95 memvec | q p95 usearch | usearch recall | RAM memvec | RAM usearch |
|---|---|---|---|---|---|---|
| go-git | 11,262 | 4.0 ms | 0.3 ms | 0.9990 | 34 MiB | 65 MiB |
| veska | 12,900 | 4.3 ms | 0.4 ms | 0.9994 | 39 MiB | 65 MiB |
| grpc-go | 19,520 | 6.2 ms | 0.3 ms | 0.9979 | 59 MiB | 129 MiB |
| consul | 37,272 | 11.6 ms | 0.4 ms | 0.9965 | 113 MiB | 129 MiB |
memvec's query latency grows with repo size while usearch stays flat (~0.3 ms); usearch trades ~2x the RAM and a slower index build for that. Stick with the default until linear-scan latency is noticeable. Full discussion: Vector storage backends.
- Go 1.26+
- Go repositories only, for now. The tree-sitter parser ships a single
Go grammar, so the code graph is built from
.gofiles. Other languages are a deliberate future step, not a current capability. - No external services for core use. SQLite, the vector index, and the default embedder all run in-process. A fresh machine indexes and searches with nothing else installed or running.
Semantic search needs an embedder. Veska elects one at boot in preference order - it never mixes vector spaces, so exactly one embedder owns the index at a time:
- model2vec (
potion-code-16M) - a fast, in-process static code embedder. The default and recommended choice. Get it either way:- Fat binary (
make build, default) - the model is compiled into the binary. Zero setup: nothing to install, no download, no network. - Thin binary (
make build-small) +veska install model2vec- a one-time ~62 MB download into~/.veska/.
- Fat binary (
- static-v2 - an in-binary fallback that works with no model files at all (lower quality). Used only when model2vec is unavailable.
No Ollama, no network, and no separate process is required for search.
Ollama is only for the optional LLM features - the post-promotion
review pipeline and per-node summaries (both off by default). It is
not used for embeddings in the default config. (Power users can force an
Ollama embedding model with VESKA_EMBEDDER=ollama, but model2vec is faster
and higher-quality on code, so this is rarely worthwhile.)
Install Ollama only if you want those LLM features:
# macOS: brew install ollama && ollama serve &
# Linux (snap): sudo snap install ollama && ollama serve &
# Linux (curl): curl -fsSL https://ollama.com/install.sh | sh && ollama serve &make build is the fat binary by default - it embeds the
model2vec weights into the binary so the install is zero-setup: no separate
download, no network, no static-v2 fallback at boot.
make build # default: ~104 MB fat binary (model2vec ~62 MB embedded
# into a ~42 MB thin build). Zero setup at runtime.
make build-small # ~42 MB thin: veska, veska-daemon, veska-mcp (+ layercheck).
# Use this only when you want size-sensitive binaries
# (CI, containers); you must then run `veska install model2vec`
# to avoid booting on the low-quality static-v2 fallback.
make test # go test ./...
make all # build-small + test + vet + lint + layercheck
# (uses the thin build to keep the test loop fast)Binaries land in ./bin/. Either export PATH="$PWD/bin:$PATH" or use the
./bin/ prefix in the Quick Start below.
After a make build, drop the binaries into a user bin directory in one step:
make install # → ~/.local/bin (default)
VESKA_INSTALL_DIR=/usr/local/bin sudo make install # system-wideFor a self-contained tarball (the three fat binaries + install.sh + a
README), run make release-archive. The archive at
dist/veska-<version>-<os>-<arch>.tar.gz is the same shape a future
GitHub release will ship - ./install.sh from inside the extracted
directory does the same thing as make install .
# 1. Build veska (default: fat, zero-setup embedder).
make build
# Size-sensitive builds can `make build-small` instead, then run
# `./bin/veska install model2vec` to avoid the low-quality static-v2 fallback.
# 2. Initialize veska's data directory at ~/.veska/.
./bin/veska init
# 3. Start the daemon.
#
# Pick one:
# - Just kicking the tires? Background it: ./bin/veska-daemon &
# - Want it on every boot, auto-restart on
# crash, logs under ~/.veska/logs? use the service form below.
#
# For a real install, run it as an OS service (systemd --user on Linux,
# launchd on macOS). Uninstall with `./bin/veska service uninstall`.
./bin/veska service install
./bin/veska service start
# 4. Register a repo. --wait blocks until the cold scan finishes (a few
# seconds for most repos) so the first search below is already hot.
# Without --wait the scan kicks off in the background; the next
# `eng_search_semantic` call may then return `[]` with
# `degraded_reasons=embeddings_pending` until indexing catches up.
# Tail ~/.veska/logs/daemon.log for the "cold scan: complete" line.
./bin/veska repo add /path/to/your/repo --wait
# 5. Sanity-check.
./bin/veska doctor statusThe first veska repo add registers the repo, installs the git post-commit
hook with an absolute path to the veska binary, and dispatches a cold scan
through the daemon. Subsequent commits drive promotion via eng_promote_repo
on the daemon's MCP socket.
To force a re-scan of an already-registered repo (e.g. after a model swap):
./bin/veska reindex /path/to/your/repoSafe to run while the daemon is up - the CLI dispatches the cold-scan
through the daemon's eng_reindex_repo MCP tool , so your
editor's MCP connection is not interrupted. With the daemon stopped, the
same command falls back to a direct in-process reparse.
Point your editor's MCP client at bin/veska-mcp (a stdio command), and seed a
per-agent instruction file with veska init --agent <name>. The full
walkthroughs - editor configs (Claude Desktop, Cursor, Zed, Continue), the shell
JSON-RPC interface, and the first-call sanity check - live in the manual:
Quickstart
and Connecting your editor.
State lives under ~/.veska/ (VESKA_HOME). Daemon config is
~/.veska/config.toml - see docs/operations/CONFIG-SURFACE.md.
Key environment variables:
| Var | Purpose | Default |
|---|---|---|
VESKA_HOME |
Data root | ~/.veska |
VESKA_EMBEDDER |
Embedder election: auto (model2vec→static-v2), or force model2vec / static / ollama |
auto |
VESKA_VECTOR_BACKEND |
memory (in-process memvec linear scan) or usearch (HNSW) |
memory |
VESKA_OLLAMA_URL |
Ollama endpoint - LLM review + summaries, and VESKA_EMBEDDER=ollama |
http://localhost:11434 |
VESKA_EMBED_MODEL |
Ollama embedding model - only when VESKA_EMBEDDER=ollama |
nomic-embed-text |
The elected embedder is recorded in ~/.veska/embedder.locked. Switching
embedders requires a re-index (veska reindex) since their vectors aren't
comparable.
cmd/veska/ single binary entry point; argv[0] dispatcher in main.go
internal/
core/
domain/ pure entities: Node, Edge, Graph, Task, Finding
ports/ interface contracts (GraphStorage, VectorStorage, VulnSource, …)
application/ use-case services: ingester, promoter, embedder, checks, review, wiki
cli/ composition roots: daemon/wire.go and the mcp stdio shim
infrastructure/ adapters: sqlite, vector, embedding/{model2vec,static,ollama,elect}, treesitter, mcp, git
repo/ repos-table registry
platform/ cross-cutting operational concerns (config, doctor, health, …)
docs/ user manual, architecture summary, and operational runbooks
The daemon exposes 38 tools over a Unix-socket JSON-RPC server (forwarded to
editors by veska-mcp). Tool names follow eng_<verb>_<object>. Quick map:
| Family | Tools |
|---|---|
| Admin | eng_get_status, eng_get_config, eng_get_current_repo, eng_get_repo, eng_list_repos |
| Repo lifecycle | eng_add_repo, eng_remove_repo, eng_promote_repo, eng_reindex_repo, eng_set_repo_alias, eng_remove_repo_alias |
| Graph | eng_find_symbol, eng_get_node, eng_get_file_nodes, eng_get_call_chain |
| Search | eng_search_semantic, eng_search_similar, eng_find_related (semantic neighbors of the code at a file_path+line) |
| Duplicates | eng_find_clones (duplicate groups for one mode: exact byte-identical or near fuzzy), eng_find_clusters (whole-repo / cross-repo de-dupe triage across exact/structural/near tiers, tightest first) |
| Blast radius | eng_get_blast_radius, eng_get_diff_blast_radius, eng_get_dirty_blast_radius |
| Context | eng_get_context_pack, eng_find_changed_symbols (takes ref_a/ref_b or aliases base/head; defaults to HEAD~1..HEAD; chunks filtered, comment-only diffs surface non_symbol_changes_only in degraded_reasons) |
| Dependencies | eng_list_dependencies (external modules the repo CALLS into, ranked by call-site count) |
| Misc | eng_find_owner, eng_find_todos |
| Findings | eng_list_findings, eng_get_finding, eng_close_finding, eng_reopen_finding |
| Suppressions | eng_list_suppressions, eng_get_suppression, eng_suppress_finding, eng_close_suppression |
| Wiki | eng_get_hot_zone, eng_get_entry_points |
See the MCP tools reference for the
full response shape, repo_id aliasing, parameter aliases, cross-repo edges, and
the parked task-tool family.
make test # go test ./... - unit + integration suites
make test-mcp # python pytest harness against a running daemon (fast)
make test-mcp-deep # add cross-validation against the live SQLitetests/mcp/ spawns bin/veska-mcp as a subprocess, drives every registered
tool with happy/bad/edge inputs, and pretty-prints each call's transcript so
the suite doubles as a human-readable smoke. Requires VESKA_HOME to point
at a running daemon's data dir and at least one veska repo add'd repo.
docs/PRODUCT.md- What Veska is, in plain English.docs/ARCHITECTURE.md- High-level system architecture and design.docs/manual/- User and operator manual (MkDocs source).docs/operations/- Config surface and runbooks.
Veska is licensed under the GNU Affero General Public License v3.0
(AGPL-3.0-only) - see LICENSE. You may use, study, modify, and
share it freely; if you run a modified version as a network service, the AGPL
requires you to offer that version's source to its users.
Third-party components (Go dependencies and, in embed_model builds, the
potion-code-16M model weights) are redistributed under their own permissive
licenses, reproduced in THIRD_PARTY_NOTICES.
Regenerate that file with make notices after a dependency change.
