Skip to content

Architecture

jarvis is local-first: everything runs on your machine. No code leaves it, no telemetry, no account, no network calls at runtime.

ComponentRole
Language indexersscip-python, scip-typescript, scip-java, scip-swift — one external process per index run, each emitting a .scip protobuf file for its language
SCIP → SQLite converterscip expt-convert (fork with the typeHierarchy fix) flattens the .scip file into index-<sha>.db’s query-friendly tables
SQLite index storeindex-<sha>.db, opened mode=ro&immutable=1 at query time — read-only, immutable, safe for concurrent reads
Zoektembedded zoekt-webserver subprocess; trigram-indexed lexical/regex search over git-tracked files
jarvis-server (MCP)FastMCP stdio server exposing the 9 tools; each MCP client spawns and owns its own jarvis-server process over stdio — there is no shared server process across clients
┌─────────────────────────────────────────────────────────────┐
│ MCP Client (Claude Code / Cursor / Codex CLI) │
└──────────────────────────┬──────────────────────────────────┘
│ stdio (JSON-RPC)
┌──────────────────────────▼──────────────────────────────────┐
│ jarvis-server (Python) — MCP stdio server │
│ 9 tools: navigation, search, graph, status │
└──────┬───────────────────────────┬──────────────────────────┘
│ reads │ shells out to
┌──────▼────────────────────────┐ │
│ ~/.jarvis/ (on disk) │ │ Language indexers + scip + zoekt
│ index-<sha>.db (SQLite) │ │ (installed by setup.sh)
│ .zoekt/ shards │ │
│ lancedb/ vectors (optional) │ │
│ registry.db (repos + edges) │ │
└───────────────────────────────┘ └──────────────────────────────┘
~/.jarvis/
├── bin/ # external binaries (from setup.sh)
├── scip/_/<slug>/_/
│ ├── index-<sha>.db # SQLite SCIP index (read-only at runtime)
│ └── current # pointer file (contains the live <sha>)
├── .zoekt/
│ └── <slug>_v16.*.zoekt # Zoekt search shards
├── lancedb/ # optional, semantic extra only
│ └── <slug>_v*.lance # per-repo vector tables
└── registry.db # repos table + dependency-graph edges

Indexing (jarvis index <path>): detect the primary language → run its scip-* indexer → scip expt-convert the .scip output into a new index-<sha>.db → rebuild the repo’s dependency-graph edges in registry.db → run zoekt-git-index to build .zoekt shards → only once every stage succeeds, os.replace() flips the current pointer to the new SQLite file. A query already reading the old file keeps working throughout; a failure at any stage leaves the previously published index live.

Querying (e.g. goToDefinition): the client sends a stdio JSON-RPC request → server.py’s FastMCP dispatcher routes it to the Query Engine (query.py) → the engine opens the published index-<sha>.db read-only → resolves the symbol against global_symbols and mentions → returns {"symbol", "definitions": [Location, ...]} plus the five flat freshness fields (commit, generated_at, stale, freshness, checked_at) spread at the top level of the response — never nested under a "freshness" key.

GuaranteeHowWhy it matters
Read-only queriesSQLite opened mode=ro&immutable=1Safe concurrent reads; no accidental mutations
Atomic publishos.replace() pointer swap after all stages completeZero downtime; no partial-state windows
Rebuild-not-accumulate graphDELETE outgoing edges before recomputeRemoved dependencies retracted automatically
Single language per indexDetection by extension pluralityAvoids polyglot complexity
One slug per pathRegistry-enforced at index timePrevents slug collisions
Fail-safe serverBroad exception handling → {"error": "..."}Server never crashes on query bugs
  • SQL over custom format — standard sqlite3 tooling, ACID guarantees, no custom serialization.
  • Git-indexed search — excludes gitignored content by construction; reflects HEAD.
  • Zoekt — proven at scale (Sourcegraph), regex support, cross-compiled binaries available.
  • Atomic publish — the only safe way to reindex without blocking queries.