Code graphs vs LSP vs ctags: choosing a code intelligence layer
ctags answers where a symbol lives, LSP answers what code means as you type, and a code graph answers what a change touches. A decision table for all three.
ctags, the Language Server Protocol, and a code graph all index your code, but they answer different questions: ctags answers where is this defined, LSP answers what does this mean as I type, and a code graph answers how does this all fit together — and what does a change touch. Choosing between them is really choosing which question your tooling needs answered, and for AI coding agents the answer is usually the one the first two can't give.
Three tools, three questions
ctags scans source and emits a portable index of symbol definitions — a
tags file mapping names to file and line. No running process, decades of
editor support, a long tail of languages. Its model is deliberately shallow:
it knows where a symbol is defined, not which of three same-named functions
you meant, and not who calls what. That shallowness is also why it works
everywhere.
LSP standardizes how an editor talks to a per-language server that does real semantic analysis as you edit: completion, hover types, find-references, diagnostics, rename. It is precise because it type-checks, and interactive because it re-analyzes as you type. The trade-offs follow from the design: one server per language, intelligence scoped to an editor session, and no durable artifact you can query from a script or hand to another tool.
A code graph makes explicit what the codebase already is — files import files, functions call functions — and keeps that whole-project model queryable. CGraph, our open-source engine, extracts the graph deterministically (the same tree always produces the same graph), resolves references across files and languages, and serves it to tools and agents from a resident daemon.
The decision table
| Question | ctags | LSP | Code graph |
|---|---|---|---|
Where is X defined? |
Yes | Yes | Yes |
| Live completion / hover / rename as I type | No | Yes | No |
| Type-aware precision within a file | No | Yes | Partial |
Who calls X (find references) |
No | Yes (per language) | Yes |
What does changing X transitively break? |
No | Partial | Yes |
| Shortest path between two symbols | No | No | Yes |
| One model spanning many languages | Partial | No | Yes |
| A persistent artifact other tools can query | Yes | No | Yes |
| Budgeted context for an LLM | No | No | Yes |
The honest reading: LSP wins every interactive-editing row — nothing beats a type-aware language server while a human types. ctags wins on simplicity and reach. The code graph wins the whole-project structural rows: transitive impact, paths between symbols, one model across languages, and context packed to an LLM's token budget.
The overlap at the shallow end — all three answer "where is X defined" — is
fine; it means you rarely need all three for one task. Precision is the real
trade: LSP type-checks inside a file, while a graph resolves statically
across the project, so dynamic dispatch and runtime wiring produce some
approximate edges — a trade CGraph makes deliberately in exchange for a
reproducible, diffable artifact.
When to reach for which
Reach for LSP when a human is editing and wants live, type-aware help. Reach for ctags when you want dependable go-to-definition with zero setup — lightweight editors, remote shells. Reach for a code graph when the question is structural and whole-project: what breaks if I change this signature, how do these modules connect — or when the "user" is not a human at all.
That last case is the one that changed our ranking. An agent that navigates by search re-derives the repo's structure on every turn — the grep-and-read loop we measured in the token tax of grep-and-read — and it cannot open an LSP session or profit from live completions. A warm graph is the only layer of the three built to hand whole-project structure to an agent within a token budget, which is exactly how CGraph serves it over MCP.
Takeaway
These are complements, not competitors: keep LSP and ctags for humans editing, and add a code graph when the question is whole-project structure or the consumer is an AI agent. Deciding which layers your team's tooling — and your agents — should stand on is an architecture decision we help teams make.