Features

Indexed once, ready in every worktree

What you get

You work in several Git worktrees or run agents in parallel. The graph and embeddings are built once, in the first checkout. Every further worktree reuses them, with the full graph and semantic search, and indexes what differs in it and nothing else.

Before and after

serde 1.0.229 sits in four linked Git worktrees (git worktree add), each with the full graph and semantic search. The times come from an Apple M5, and the first index ran with an empty inference cache.

Without a shared index

The first checkout builds everything itself: the graph from 219 files and the embeddings for 2,443 code spans, each one through the model once.

$ greppy index .
…
indexed 219 files (120 unsupported, 0 unreadable, 0 oversize, 0 file-limit, 0 time-budget); 4571 nodes extracted; generation 1 (project: serde)
embedded 2443 code spans (0 local-store reused, 0 global-cache hits, 2443 inference misses, 4573 considered, 2150 non-definition skipped, 0 missing-file, 0 invalid-span, 0 oversize, 0 failed, 0 stale pruned)

With a shared index

Every further worktree reuses the shared Base and indexes nothing but its Delta.

$ greppy index .
…
greppy index: linked worktree uses shared Base 61d1f6b54eac at 7fc3b4c30c94f73a96ebd1553f2b090d928fc3a8 (reused); only the Git/dirty Delta will be indexed
indexed Delta generation 1: 0 changed/deleted paths, 0 private nodes (project: serde)

In the third worktree, a new function demo_format_count is added to serde/src/lib.rs. greppy indexes exactly this one file, and semantic search finds the function right after. In the fourth worktree the function does not exist.

$ greppy index .
…
indexed Delta generation 2: 1 changed/deleted paths, 1 private nodes (project: serde)
$ greppy search "how many formats are supported" --limit 3
serde/src/lib.rs:288  demo_format_count — returns a fixed count of 3
…
$ greppy search-symbol demo_format_count
status: no_matches
…
Time Spans through the model
First checkout 91.8 s 2,443
Worktree 1, creates the Base 22.0 s 0
Worktree 2 3.2 s 0
Worktree 3 3.1 s 0
Worktree 4 2.4 s 0
Worktree 3 after the new function 5.1 s 1
Agent workspace (greppy index --agent-worktree) 2.8 s 0

The checkout has its own store of 28.7 MB. Each worktree holds a Delta of 0.28 to 0.35 MB and reads the shared Base alongside it. All worktrees see the same graph as the checkout: 339 files, 4,573 nodes, 9,427 edges, plus the new function in the third worktree.

With large repositories the gap grows. Since 0.4.1, building the Base uses the inference cache too: on a Mac with an M-series chip, a worktree with 41,012 spans took 161 s instead of 1,520 s.

How it works

The first linked worktree creates an immutable Base: the graph and embeddings of the Git state, identified by Git tree, store schema, indexer and model versions. The embeddings come from the user-wide inference cache that the first index filled; an unchanged span goes through the model once. Every worktree and every agent also gets a private Delta with exactly the paths that differ for it: modified, deleted, renamed or new, committed or not. Queries see Base and Delta together. One process builds a Base, and all others wait and reuse it. A worktree's binding to its Base stays fixed, even when the main checkout moves on.

The commands

Command what it does
greppy index . in the first linked worktree: create the Base; in every further one: reuse the Base and index nothing but the Delta
greppy index --agent-worktree create the built-in agent's workspace, Base included, ahead of time
greppy index status --json phase, progress and readiness, mode (overlay or single), Base and Delta; never blocks
greppy cache status --json stores, Bases, models and their sizes
greppy cache clear --root . --yes delete this worktree's store and the repository's shared Bases

More in the docs