Architecture

insrc is one daemon that owns everything. It parses your repos with tree-sitter into a typed entity/relation graph, stores that graph in LMDB alongside Lance vectors, and answers every query with structured, citation-grounded context. Clients — the claude / codex CLIs, the interactive TUI, and the VSCode fork — never touch a database directly; they speak JSON-RPC over a Unix socket.

The shape of the system

Three kinds of client fan in through two entry surfaces — the MCP servers and the raw IPC socket — onto a single daemon. The daemon is the sole owner of the graph, the vectors, the query engine, and the tool registry.

claude / codex CLI insrc TUI VSCode fork │ │ │ └──────────┬─────────┴─────────┬─────────┘ ▼ ▼ MCP servers JSON-RPC over insrc_analyze_step ~/.insrc/daemon.sock insrc_workflow_step │ └──────────┬─────────┘ ▼ ┌────────────────────────┐ │ DAEMON │ owns all DB access │ registry · queue · fs │ │ watcher · tool exec │ └───────────┬────────────┘ ┌────────────────┼────────────────┐ ▼ ▼ ▼ LMDB graph Lance vectors DuckDB entities/edges ANN / embeddings data-driver tools
index LMDB graph + Lance vectors daemon IPC structured context

The daemon

The daemon is the background process everything else attaches to. Its entry point is src/daemon/index.ts, built to out/daemon/index.js — the binary the IDE spawns. It runs a Unix-socket JSON-RPC server (src/daemon/server.ts) at ~/.insrc/daemon.sock and owns the repo registry, the work queue, process lifecycle, and the file-watcher.

  • Repo registry — the authoritative list of workspaces; membership is established only through the repo.add IPC.
  • Queue & lifecycle — indexing jobs and daemon start/stop are marshalled server-side.
  • File-watcher — changes on disk re-index the affected entities.
  • Tool registry + executor — ~110 built-in capability wrappers under src/daemon/tools/ (registry, executor, and the builtins/ set): file · git · shell · http · web · gh · k8s · pkg · ssh · test · notify · search · graph · db · data · code · cloud.
the IPC contract is the surface

The IPC contract is the only surface the IDE consumes. Method names, the socket path (~/.insrc/daemon.sock), and payload shapes stay in lock-step across this repo and the insors-ai/insrc-ide fork, which clones this repo into ~/.insrc/daemon/ and spawns the compiled entry. Mirrored types on both sides keep the two repos honest.

Storage substrate

Three embedded engines, each with a distinct job. Only two of them persist anything.

EngineRoleWhere
LMDB via lmdb-jsEmbedded KV store — the substrate for the custom graph layer: entities, edges, deterministic keys.src/db/graph/
LanceDBEmbedded vector DB — entity embeddings + ANN search, across per-purpose tables.src/db/lance/
DuckDB via @duckdb/node-apiIn-memory query engine that backs the data-driver db_file_* tools.src/daemon/tools/

LMDB graph layer

The graph layer in src/db/graph/ is built directly on LMDB — store.ts, keys.ts, codec.ts, edges.ts, and traversal.ts. Structural queries go through a typed JS API, not a query language: no Cypher, GQL, or SQL is exposed for traversal.

FunctionDoesDefined in
findCallersentities that call a given entitysrc/db/search.ts
findCalleesentities a given entity callssrc/db/search.ts
outNeighborsoutgoing edges from a nodesrc/db/graph/edges.ts
inNeighborsincoming edges to a nodesrc/db/graph/edges.ts
transitiveClosureeverything reachable from a nodesrc/db/graph/traversal.ts
unreachablenodes with no path from a root setsrc/db/graph/traversal.ts

Semantic queries take the other path: LanceDB approximate-nearest-neighbour search over embeddings. Lance keeps several purpose-built tables — entity-vec, session-vec, turn-vec, artifact-vec, and config-vec among them.

DuckDB is an in-memory query engine only. It backs the data-driver db_file_* tools (db_file_describe, db_file_aggregate, db_file_histogram, db_file_outliers, …) and is not used for persistent storage. Persistence lives entirely in LMDB (graph) and LanceDB (vectors).

Entity IDs

Entity identity is deterministic — the same source always yields the same ID, so re-indexing is idempotent and IDs are stable across runs and machines:

id
id = SHA256(repo + file + kind + name)   // hex-32

The indexer

The indexer turns source into the graph. It parses with tree-sitter across five languages, resolves build manifests, and generates embeddings — all locally.

tree-sitter parse entities + relations manifest resolution embeddings
  • Languages — TypeScript, Python, Go, Java, Scala.
  • Output — a typed entity/relation graph, written to the LMDB graph layer.
  • Embeddings — computed locally (Ollama), stored in Lance for ANN search.
Upserting an entity whose repo path isn't registered fails with UnregisteredRepoError — the strict repo-registry contract is enforced right at the storage boundary (src/db/entities.ts, src/db/repos.ts).

Directory layout

The src/ tree, by responsibility:

src/ shared/ Core types, paths, logger (types.ts · paths.ts · logger.ts) indexer/ Tree-sitter parsing + graph construction db/ Storage (LMDB graph + Lance vectors + DuckDB pool) graph/ LMDB-backed graph (store · keys · codec · edges · traversal) lance/ LanceDB tables (entity-vec · session-vec · turn-vec · …) daemon/ Background daemon process index.ts Entry point + IPC handler registry server.ts Unix-socket JSON-RPC server tools/ Tool registry + executor + ~110 built-in wrappers config/ On-disk config store + templates + feedback agent/ providers/ ollama.ts (local) · cli-provider.ts (claude + codex) analyze/ Analyze framework (recipes · decomposer · synthesizer) workflow/ define · design.epic · design.story · tracker · gates mcp/ MCP servers (insrc_analyze_step · insrc_workflow_step) cli/ insrc interactive TUI (ink): panes/ services/ hooks/ ui/ bin/ Executable entrypoints prompts/ Shaper / analyze / workflow prompt templates assets/ Non-TS runtime resources (shipped by copy-assets.mjs)

Two binaries fall out of the build: out/daemon/index.js (the daemon the IDE spawns) and out/bin/insrc-mcp.js (the MCP server you register with claude / codex).

Architectural rules

Seven invariants hold the design together. They are non-negotiable — every subsystem is built to respect them.

invariants
  1. Daemon owns all DB access. The CLI, MCP, and the IDE workbench communicate via IPC only — none of them opens LMDB or LanceDB directly.
  2. Local-first. Ollama is always available; embeddings are local-only. Cloud LLM access goes through CliProvider (the claude + codex CLI subprocesses) — no direct REST.
  3. Dependency-closure scoping. Graph searches span only the transitive DEPENDS_ON closure of the active repo.
  4. Graph + vector split. Structural queries use the LMDB graph layer's typed JS API; semantic queries use LanceDB ANN. No Cypher / GQL / SQL is exposed for graph traversal.
  5. No raw file dumps. Context is always structured entity summaries + relations from the graph — never a blind file paste.
  6. Repo registry is the contract. Registry membership is established exclusively via the repo.add IPC; an Entity whose repo path isn't registered fails the upsert with UnregisteredRepoError.
  7. Structural reference goes trailing. Schemas, catalogs, and manifests belong at the tail of a prompt, not the middle — recency-weighted attention hallucinates against mid-prompt structural info.

Go deeper