Code graph
The code graph is a read-mostly index of a project's code and docs as nodes and typed edges. It is not the source tree, not git, and not a substitute for opening a file when you need the body.
It exists so an agent (or a human) can traverse — "what calls this?", "what implements that?", "which doc mentions this symbol?" — instead of grepping and re-reading files on every turn.
Design intent (repo docs/design/code-graph.md, #170): docs and code live in one graph. A doc reference whose target no longer resolves is drift-as-data (Ambiguous / drift rows), not a separate audit product. Provenance on edges reuses the same axis as memories and facts — what do we know about X, and how sure are we?
All endpoints require authentication. Responses are wrapped in ApiEnvelope<T>. Canonical API host: https://api.atamaia.ai.
Project-scoped: every query takes projectId.
What it indexes
| Kind of thing | How it appears |
|---|---|
| Source symbols | File, Directory, Module, Class, Interface, Struct, Enum, Function, Method, Property, Field, Constant, Variable, Parameter, TypeAlias |
| Documentation | Document, DocSection — physical repo docs and DB-stored docs (DocId when mirrored from the docs table) |
Live project 1 (Atamaia), 2026-08-13:
| Count | |
|---|---|
| Nodes | 14,089 |
| Edges | 18,137 |
| Graph commit | 7427ff53… (not stale vs repo at probe time) |
Largest node kinds: Property, Method, Class, File. Edge mass is mostly Contains (structural) and Calls, plus Mentions from docs (many Inferred/Ambiguous).
Nodes
A node is one symbol or doc unit.
| Field | Meaning |
|---|---|
id |
Long id (also usable as symbol in queries) |
kind |
GraphNodeKind name |
name |
Short name |
qualifiedName |
FQN or doc path + heading trail |
language |
e.g. csharp, typescript, markdown |
filePath |
Repo-relative; null for pure DB docs |
startLine / endLine |
Span in file |
signature |
When extracted |
nodeKey |
Stable ingester identity for upsert |
contentHash |
Incremental re-ingest |
commitSha |
Commit last extracted at |
embedding / search vector |
Semantic + FTS support |
Edges
A edge is directed and typed.
| Field | Meaning |
|---|---|
from → to |
Node ids |
kind |
GraphEdgeKind |
provenance |
Asserted / Inferred / Ambiguous (same enum family as memories) |
sourceLine |
Call/import/reference site when known |
Edge kinds (GraphEdgeKind)
Contains · Calls · Imports · Inherits · Implements · Uses · References · Returns · HasParameter · Overrides · Documents · Mentions
Contains dominates (tree structure). Calls / Inherits / Implements are the navigation agents need for "who uses this." Documents / Mentions link doc nodes to code. Mentions often carry Inferred or Ambiguous provenance — resolution is harder than parsing.
Hebbian strength does not apply. Graph edges do not co-activate; they are structural/extract claims. (Standing-rule links are also non-Hebbian for a different reason — see docs-standing-rules.md.)
Staleness
Every search/neighbors/drift/node result can carry:
| Field | Meaning |
|---|---|
graphCommit |
Commit the index was built at |
repoCommit |
Current git rev-parse HEAD if RepositoryPath is set and readable |
stale |
True when both known and disagree |
populated |
False if the project has zero nodes |
Empty and stale are different answers. An unpopulated graph used to look "not stale" (nothing to compare) while every search returned empty — that read as "nothing matches" when it meant "never indexed." populated: false carries an explicit note.
When stale: true, line numbers and signatures may not match the working tree. Prefer re-ingest over trusting spans.
Live project 1 probes: populated: true, stale: false.
Query surface
Search
GET /api/graph/search?projectId=&query=|&q=&kind=&language=&limit=
MCP: graph_search.
- Primary: case-insensitive match on
nameorqualifiedName. - Exact name ranks first, then shorter names.
- If fewer than
limitlexical hits and embeddings are available: semantic fallback (cosine > 0.3) fills the remainder. Fallback rather than blend — an exact name match must not be buried under neighbours. limitclamped 1–200 (MCP default 25).
Live: query=TaskService&kind=Class returns Atamaia.Services.Projects.TaskService among hits.
Neighbors
GET /api/graph/neighbors?projectId=&symbol=&direction=both&edgeKind=&limit=
MCP: graph_neighbors.
direction |
Meaning |
|---|---|
out |
What this node calls / contains / implements / … |
in |
Callers / parents / … |
both |
Default |
symbol is a numeric node id or a simple/qualified name (simple name resolves preferring lower KindId order).
Live: symbol=WebSearchService → out Contains on SearchAsync/SearchSearxngAsync/… and Implements IWebSearchService.
Node detail
GET /api/graph/node?projectId=&symbol=
MCP: graph_node (cold tool via help in some packagings).
Full node + docs that mention it + neighbor counts by edge kind (out:Calls, in:Contains, …).
Stats
GET /api/graph/stats?projectId=
Totals and histograms by node kind and edge kind×provenance. Operational health check before trusting search.
Drift
GET /api/graph/drift?projectId=&doc=&limit=
MCP: graph_drift.
Documented-but-not-implemented symbols: names docs reference that resolved to no code node at ingest. Regenerable projection on rebuild. Live project 1: total 0 at probe time.
Rebuild
POST /api/graph/rebuild
{ "projectId": 1, "repoPath": "optional override" }
Returns GraphIngestResult (files, nodes, edge counts by family, documents, unresolved doc refs, elapsedMs).
Not on the MCP GraphTools surface — gated admin/ops path. Agents query; operators rebuild.
MCP vs REST
| Capability | REST | MCP GraphTools |
|---|---|---|
| search | yes | graph_search |
| neighbors | yes | graph_neighbors |
| node | yes | graph_node |
| drift | yes | graph_drift |
| stats | yes | no (use REST / atamaia_call) |
| rebuild | yes | no |
What it is for
- Orientation — find the class/method that owns a concern without listing the repo.
- Impact — callers/callees before a change (
direction=inon Calls). - Doc honesty — drift list and Ambiguous Mentions.
- Same traversal habit as memory — agents already know "search then neighbors"; code stops being a special-case grep loop.
It is not for:
- Reading full file bodies (no content payload on nodes — path + lines only).
- Guaranteeing compile-correct resolution on every language (Asserted edges are strong; Inferred/Ambiguous are not).
- Replacing tests or a typechecker.
- Cross-project global search (always pass
projectId).
Honest limits
- Index lag — if HEAD moved and rebuild did not, spans lie. Check staleness every session that depends on lines.
- Semantic search is secondary — behavioral queries work only when embeddings exist and score > 0.3; name search is the reliable path.
- Neighbor resolve ambiguity — simple name
Ordermay hit the wrong kind; prefer qualified name or id from a prior search. - Mentions quality — large Inferred/Ambiguous Mentions counts mean doc→code links are best-effort.
- In-memory semantic candidate load — service comment: ~11k nodes acceptable; order-of-magnitude growth wants SQL
<=>+ index. - Rebuild cost / locking — not characterized here; treat as ops, not a hot agent loop.
Endpoint catalog
GET /api/graph/search
GET /api/graph/neighbors
GET /api/graph/node
GET /api/graph/stats
GET /api/graph/drift
POST /api/graph/rebuild
Related but different: GET /api/demo/memory-graph is a demo memory visualization route, not this code graph.