Every developer has lived through this: you join a codebase, something needs to change in "the part that validates sessions," and grep -r "session" returns four hundred lines of noise while the actual function is called is_token_current. ripgrep is the fastest tool in the world at finding words you can already spell. It cannot help you when you don't know the words — which is most of the time for humans, and all of the time for an AI agent dropped into an unfamiliar repository.

That gap is exactly why zvec-grep is climbing. The project (zvec-ai/zvec-grep, Apache 2.0) has collected roughly 3,800 GitHub stars since its July 2026 debut and is currently sitting on GitHub's weekly trending page. Its pitch, in the project's own words: "Know the words — or don't. Just zg."

Under one zg command it unifies four search routes: ripgrep for exact text, BM25 for ranked keyword search, vector search for meaning, and a hybrid that fuses them all. Everything runs locally by default — index, embeddings, and queries — with no account, no API key, and no GPU. This tutorial installs version 0.2.2, indexes two real workspaces, runs every query route against them with the actual output, and then hands the whole thing to a coding agent over MCP. Every command below was run on September 27, 2026, and every output shown is real.

What you'll need #

  • Node.js 22 or newer — check with node --version. The package ships as an npm binary.
  • A terminal and roughly 160 MB of free disk for one-time model downloads (observed: 125 MB for the English retrieval model, 32 MB for the code model).
  • No API key, no GPU, no account. The bundled "Potion" embedding models are static lookup tables — there is no tokenization step at query time, so even a modest CPU answers in under a second.
  • A workspace to search. We'll build two disposable ones, so you can follow along without touching your own code.

Step 1 — Install zg #

The package lives on npm as @zvec/zvec-grep:

npm install -g @zvec/zvec-grep
zg --version
# 0.2.2

Two things worth knowing up front. First, this tutorial pins version 0.2.2 and every flag below was verified against that build's --help. The project is pre-1.0 and its online docs track the development branch — at the time of writing, the docs show a newer flag form (zg --index) while 0.2.2 ships subcommands (zg index, zg query, zg status, zg install). When in doubt, zg <command> --help on your machine is the source of truth. Second, zg query --help reveals the full surface: hybrid/FTS/vector/fuse routes, a managed ripgrep passthrough, glob and file-type filters, and symbol-aware search. That's the whole tour in one help screen — worth reading once.

Step 2 — Your first index: the bookshelf test #

Before pointing zg at code, verify that its semantic search actually works — with a test no keyword engine can pass. Download two public-domain books into a scratch directory:

mkdir -p /tmp/zg-bookshelf && cd /tmp/zg-bookshelf
curl -sL -o alice-in-wonderland.txt \
  https://www.gutenberg.org/files/11/11-0.txt
curl -sL -o sherlock-holmes.txt \
  https://www.gutenberg.org/files/1661/1661-0.txt

Now build the index. For English prose, the docs recommend the retrieval-tuned model local/potion-retrieval-32m:

zg index --embedding local/potion-retrieval-32m

On first use zg downloads the model (~125 MB) from Hugging Face and caches it under ~/.zvec-grep/models — after that, everything is offline. The observed result: 2 files scanned, 433 entities, 0 failures, in about a minute. The index itself lands in <workspace>/.zvec-grep/ (manifest.json, files.zvec, index.zvec), so it's per-project and travels with the repo.

Here's the test. Ask about a passage using words that never appear in it:

zg query --human \
  "An unseen creature left a few marks. What did the detective infer?" \
  --limit 3

Real output (abridged):

query groups (1):
Q1 [primary]: An unseen creature left a few marks. What did the detective infer?
hits: 3

#1 matchedBy=fts+vector sherlock-holmes.txt:5463-5505
...
#2 matchedBy=fts+vector sherlock-holmes.txt:5455-5462
#3 matchedBy=fts+vector sherlock-holmes.txt:5506-5513

All three hits land in The Hound of the Baskervilles, and the top hit is the exact passage: the detective examining the footprints — five pads, long nails, a print "about the size of a dessert spoon," a long body, very short legs, able to climb a curtain, carnivorous. No query term appears in that passage; the match is pure meaning. Note the matchedBy=fts+vector label: the default positional query is hybrid, running BM25 and vector search in parallel and fusing the rankings.

Step 3 — Index a real codebase #

Books prove the semantics; now the actual use case. Build a tiny demo repo:

mkdir -p /tmp/zg-code/src /tmp/zg-code/tests && cd /tmp/zg-code
# src/auth.py — PBKDF2 password hashing, credential checks, sessions
# src/theme.py — saves/restores the UI theme in a JSON prefs file
# src/retry.py — exponential-backoff decorator
# tests/test_auth.py, README.md

For code, switch to the code-tuned model:

zg index --embedding local/potion-code-16m-v2

This one downloads just 32 MB and produces 256-dimensional cosine vectors. Result: 5 files, 10 entities. That entity count is the interesting part — zg doesn't just chunk text. A structural extractor parses supported languages (Python, JS/TS, C/C++, Go, Java, Rust) into symbols with signatures, so a function definition becomes a searchable unit that carries its name, parameters, and docstring. Markdown sections get their own treatment, and formats that can't be searched meaningfully — PDFs, Office files, archives, executables, audio/video, databases — are skipped outright.

Check the index health any time with:

zg status
✓ Workspace index is ready
  /tmp/zg-code

  Coverage    ████████████████████ 100%  5 / 5 files
  Entities    10
  Truncated   0 fragments
  Queue       0 pending · 0 failed

  Embedding   local/potion-code-16m-v2
              256 dimensions · cosine

  Storage     .zvec-grep/index.zvec

Step 4 — Learn the four query routes #

zg's power is that it doesn't make you choose one search philosophy. Ask the way that's natural, then narrow down:

# Hybrid (default): BM25 + vectors, fused and ranked
zg query "where authentication is validated" --limit 3
# → #1 matchedBy=fts+vector src/auth.py:11-18
#   def validate_credentials(username: str, password: str, user_store: dict) -> bool:

# Vector only: pure meaning, no keyword overlap required
zg query --vector "theme preference persistence on startup" --limit 3
# → #1 matchedBy=vector src/theme.py:15-25  (save_theme)
#   #2 matchedBy=vector src/theme.py:7-13   (load_theme)

# FTS only: classic ranked keywords, scoped by glob
zg query --fts "load_theme" -g "src/**" --limit 3

# Fuse: run several query groups in parallel, merge the rankings
zg query "authentication flow" --fts "SESSION_TTL" --fuse --limit 4

The vector-only query is the one worth staring at: "theme preference persistence on startup" shares zero words with load_theme/save_theme, yet both surface at the top. That is the query a new hire — or an agent — would actually type.

Two more routes complete the picture. --rg passes through to a managed ripgrep (exact, exhaustive, with symbol breadcrumbs in the output):

zg query --rg -F "SESSION_TTL" src
# src/auth.py
#   6:  SESSION_TTL_SECONDS = 3600
#   20-26 [function create_session] 25:  "expires_at": time.time() + SESSION_TTL_SECONDS,

And --prefer-symbol biases results toward definitions rather than usages — pair it with --symbol-type function when you want the declaration, not the call sites:

zg query "session" --prefer-symbol --symbol-type function --limit 3
# → is_session_valid, test_expired_session_invalid, create_session

A quick decision table:

RouteUse it when…
zg query "…" (hybrid)You don't know what to call it — the daily driver
--vectorYou know the concept, not the vocabulary ("persistence on startup")
--ftsYou know a distinctive term and want ranked keyword hits
--fuseSeveral angles on one question; merge their rankings
--rgExact identifier, regex, or constant — ripgrep, exhaustively
--prefer-symbolYou want the definition, not every usage

Practical knobs: --limit caps results (default 7), --preview none|short|full controls how much context each hit shows, and --human forces the readable layout when output is piped.

Conceptual diagram: source files flow into a local indexer that builds an inverted keyword index and a vector index side by side, while a query splits into keyword and semantic paths that fuse into ranked results
Image: AI-generated illustration for AI Frontier Post.

Step 5 — Keep the index honest #

An index is only useful if you can trust its freshness. zg exposes that explicitly instead of hiding it:

# Default: background refresh (file events + interval sweeps)
zg query "retry backoff"

# Reindex changed files first, then answer
zg query "retry backoff" --refresh wait

# Answer from the index as-is, even if files changed
zg query "retry backoff" --refresh off

Hits carry a freshness marker, so --refresh off will tell you a result is possibly_stale rather than silently serving it. When the index needs surgery: zg index --rebuild rebuilds it in place, and zg index --drop --yes removes it entirely. This explicitness is a design choice worth appreciating — most "smart search" tools either reindex opaquely or go stale quietly; zg does neither.

Step 6 — Hand it to your agent #

Semantic code search is arguably more valuable to agents than to humans — an agent exploring a new repo asks "where is X handled?" constantly, and burning context on blind grep sweeps is expensive. zg ships a first-class MCP path:

# Wire zg into your coding agent (codex, claude, qwen, qoder,
# opencode, cursor, copilot, vscode, all, or auto)
zg install --target claude

# Or run the daemon yourself: stdio for agents, HTTP for tools
zg server on
# Server: ready
# URL: http://127.0.0.1:7999/mcp
zg server status
zg server off

Two details matter. The MCP endpoint binds to loopback only (127.0.0.1) — your index never leaves the machine, which is the whole point of the local-first design. And the default agent toolset exposes exactly one tool, zvec_grep_search, keeping the agent's tool surface minimal; --mcp-toolset full adds rg, index, index_drop, index_status, and server_status for operators who want them.

The project's own guidance is refreshingly honest about the division of labor: use semantic search for meaning, but keep native grep/ripgrep for exact identifiers, paths, and regex. The --rg route exists precisely so the agent doesn't have to shell out to a second tool.

zg vs the alternatives #

ToolStrengthWhere zg wins
ripgrepInstant, exact, zero setupWhen you don't know the words; zg keeps rg inside it anyway
GitHub code searchSymbol-aware, huge indexLocal, private, works on unpushed code, no cloud round-trip
Sourcegraph / CodyFull platform, cross-repoOne npm install, no account, no per-seat anything
DIY vector RAGFully customizableNo chunking/embedding plumbing; BM25+vectors+rg pre-fused

The honest summary: zg doesn't replace ripgrep — it absorbs it and adds the two things rg can't do (ranked keywords, meaning). Its real competitors are the hosted code-intelligence platforms, and its pitch against them is radical simplicity: one CLI, one local index, no bill.

Conceptual illustration: a coding agent sends a natural-language question to a small local index on a laptop, which returns the exact function definition while raw grep noise fades into the background
Image: AI-generated illustration for AI Frontier Post.

Limitations worth knowing #

No tutorial is complete without the rough edges — all observed or documented, none guessed:

  • Pre-1.0 API churn is real. The online docs already describe a newer flag form than the 0.2.2 you install from npm today. Pin your version, and trust zg --help over blog posts (including this one, in six months).
  • Semantic search costs latency. One published independent evaluation measured roughly 0.9 s per semantic query versus 0.01 s for ripgrep, and found semantic hits skew toward explanatory prose (READMEs, comments) over production code. Use --rg when milliseconds matter.
  • Some formats are skipped by design — PDFs, Office documents, archives, executables, media files, and databases never enter the index. If your answers live in a Confluence export, zg won't see them.
  • Remote embeddings are gated. Cloud models (OpenAI, Qwen, custom endpoints) require explicit opt-in flags and your own API key — local is the default and the path this tutorial tested.
  • Benchmarks are vendor-reported. The project cites SWE-QA-Bench (with Claude Code + Opus 5) and BrowseComp-Plus (with Codex + GPT-5.6-class models) plus case studies on pylint, matplotlib, and Django. Treat those as the project's claims, not independent verification — the one independent evaluation linked below is more mixed, which is normal for a three-month-old tool.

The takeaway #

The reason zvec-grep is trending isn't a benchmark number — it's that the workflow finally matches how developers actually think. You describe the behavior; it finds the code. The hybrid default means you rarely have to care which engine answered, the managed ripgrep means you never lose exact search, and the MCP server means your agent gets the same superpower with one install command. For the price of npm install -g @zvec/zvec-grep and ~160 MB of one-time model downloads, "where is the session logic?" becomes an answered question instead of a twenty-minute spelunk.

Index one of your own repos tonight. Run zg status, then ask it the question you'd normally grep for — phrased the way you'd explain it to a new teammate. If the top hit is right, you'll understand the 3,800 stars.