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
fromto 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 name or qualifiedName.
  • Exact name ranks first, then shorter names.
  • If fewer than limit lexical 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.
  • limit clamped 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

  1. Orientation — find the class/method that owns a concern without listing the repo.
  2. Impact — callers/callees before a change (direction=in on Calls).
  3. Doc honesty — drift list and Ambiguous Mentions.
  4. 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

  1. Index lag — if HEAD moved and rebuild did not, spans lie. Check staleness every session that depends on lines.
  2. Semantic search is secondary — behavioral queries work only when embeddings exist and score > 0.3; name search is the reliable path.
  3. Neighbor resolve ambiguity — simple name Order may hit the wrong kind; prefer qualified name or id from a prior search.
  4. Mentions quality — large Inferred/Ambiguous Mentions counts mean doc→code links are best-effort.
  5. In-memory semantic candidate load — service comment: ~11k nodes acceptable; order-of-magnitude growth wants SQL <=> + index.
  6. 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.