Why a local index, not a vector store
RAG is a great default for prose. It is a poor default for code. Every NCI design decision falls out of choosing structure over similarity for the part of an agent’s context that is type-aware.
Side-by-side
| Concern | RAG over node_modules | NCI’s local index |
|---|---|---|
| Granularity | Chunks | Symbols |
| Retrieval shape | top-k similarity | Exact match by (package, version, name) + FTS5 fallback |
| Latency | Network + vector search | Local SQLite open |
| Determinism | Depends on embedding model | Byte-stable |
| Privacy | Embeddings leave the machine | Nothing leaves the disk |
| Versioning | Tied to embedder | Tied to your lockfile |
| Update cost | Re-embed | Incremental re-index — only changed packages |
When RAG still helps
NCI is not a replacement for everything an agent needs. RAG is still the right tool for:
- The user’s own documentation, READMEs, design docs.
- Long-form code (think handlers, business logic) that has no public API.
- Search across history (PRs, issues) where similarity is the goal.
The right shape is “NCI for type-aware questions about libraries, RAG for prose-aware questions about your project”. They sit side-by-side in the agent’s tool catalog, not in competition.
What this buys the agent
- The agent never re-asks the same question.
- It never paraphrases a signature wrong.
- It never invents a method that does not exist.
- It can answer “is
useStatepart of the public API of the version of React I have?” with one tool call.
Where the size argument breaks
The same symbols compress dramatically when stored as structured rows: one signature text + one js_doc text per declaration, indexed by FTS5 for free-text fallback. There is no separate vector store — the FTS index lives in the same SQLite file as symbols and ships at no additional cost.