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.
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.addIPC. - 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 thebuiltins/set): file · git · shell · http · web · gh · k8s · pkg · ssh · test · notify · search · graph · db · data · code · cloud.
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.
| Engine | Role | Where |
|---|---|---|
LMDB via lmdb-js | Embedded KV store — the substrate for the custom graph layer: entities, edges, deterministic keys. | src/db/graph/ |
| LanceDB | Embedded vector DB — entity embeddings + ANN search, across per-purpose tables. | src/db/lance/ |
DuckDB via @duckdb/node-api | In-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.
| Function | Does | Defined in |
|---|---|---|
findCallers | entities that call a given entity | src/db/search.ts |
findCallees | entities a given entity calls | src/db/search.ts |
outNeighbors | outgoing edges from a node | src/db/graph/edges.ts |
inNeighbors | incoming edges to a node | src/db/graph/edges.ts |
transitiveClosure | everything reachable from a node | src/db/graph/traversal.ts |
unreachable | nodes with no path from a root set | src/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.
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 = 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.
- 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.
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:
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.
- Daemon owns all DB access. The CLI, MCP, and the IDE workbench communicate via IPC only — none of them opens LMDB or LanceDB directly.
- Local-first. Ollama is always available; embeddings are local-only. Cloud LLM access goes through
CliProvider(theclaude+codexCLI subprocesses) — no direct REST. - Dependency-closure scoping. Graph searches span only the transitive
DEPENDS_ONclosure of the active repo. - 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.
- No raw file dumps. Context is always structured entity summaries + relations from the graph — never a blind file paste.
- Repo registry is the contract. Registry membership is established exclusively via the
repo.addIPC; anEntitywhoserepopath isn't registered fails the upsert withUnregisteredRepoError. - 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.