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, the VS Code extension, and the JetBrains plugin — 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 IDE plugins │ │ │ └──────────┬─────────┴─────────┬─────────┘ ▼ ▼ 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/

Document indexing and summaries

Repository documentation is indexed as documents and sections for retrieval of prose, decisions, and constraints. Background summaries follow the configured cheap model tier, which can use Ollama or a cloud CLI. The summary queue yields to code indexing; unchanged document summaries are preserved across a full reindex.

Embedding backends are local: Ollama or embedded ONNX. Selecting a cloud reasoning model sends relevant context through the installed Claude / Codex CLI, while graph and vector storage remain on disk.

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 six languages, resolves build manifests, and generates embeddings — all locally.

tree-sitter parse→ entities + relations→ manifest resolution→ embeddings
  • Languages — TypeScript, Python, Go, Java, Scala, Kotlin.
  • 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).

Docs from the graph

The docgen framework (src/docgen/) turns the indexed graph into self-contained HTML documents with embedded Mermaid diagrams — no external assets, no network. Generation is graph-grounded: every rendered claim carries provenance back to the entities and relations it came from, via a typed intermediate representation (DocumentIR) that the renderer walks.

  • Three-surface parity — the same generation runs as the insrc_docgen MCP tool, the docgen.generate / docgen.list daemon IPC, and a built-in tool, all over one implementation.
  • Oversized fallback — a document too large for a single in-process render is handed to a subprocess so a big repo never blocks the daemon.
  • Boot-time asset validator — on startup the daemon verifies the bundled runtime assets (Mermaid, svg-pan-zoom, the manifest) are present and intact (validateDocgenAssets, src/daemon/index.ts), so a broken install fails loudly rather than emitting half-rendered docs.

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/ brainstorm · define · design · plan · build · code-review · tracker docgen/ HTML+Mermaid docs-from-graph (IR · render · asset-validator) mcp/ MCP servers (insrc_analyze_step · insrc_workflow_step · insrc_docgen) 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. Embeddings run locally through Ollama or the embedded ONNX backend. 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