Skip to content

[Epic] Knowledge graph: typed reference edges in Lakebase with agent-facing graph tools #82

Description

@IceRhymers

Summary

Add a persisted knowledge-graph layer to the code-search corpus: typed, raw (unresolved) call/import edges with enclosing-symbol attribution, extracted by the existing tree-sitter walk for all 7 supported languages, stored in Lakebase, resolved at query time into ranked candidate sets, and exposed to agents via new find_references / list_imports MCP tools with Web UI parity.

Why now

Lexical, symbol, and semantic retrieval are solid, but agents cannot ask structural questions: who calls this, what imports that, which function contains this call site. A 3-lane deep-dive trace confirmed zero relationship data exists anywhere in the schema or indexer today, and that the wedge — typed edges, enclosing-symbol attribution, and a queryable import graph — is inexpressible via composed search_code + sym:.

Design decisions (deep-dive, probe-validated)

  • Raw edges, query-time resolution. Edges store (repo, file, enclosing symbol, target_name, kind, line) — never a resolved cross-file symbol_id FK. Resolution happens per query via name-join against symbols (same-repo preferred). Cross-repo staleness becomes structurally impossible; edges inherit the existing per-file delete-and-reinsert lifecycle.
  • Candidate-set semantics (grep, not LSP). Live probe on 30,406 call sites (this repo + flask + requests + fastapi): 28.8% unique / 33.4% ambiguous (shallow: 2–31 candidates) / 37.8% external. Tools return ranked candidate sets and say so in their docstrings.
  • Typed edge tables, native Postgres. Apache AGE is not on Lakebase's curated extension list; no graph extension, no generic nodes/edges JSONB.
  • Tier A (1-hop) only. Multi-hop (blast radius, call hierarchy, path-between) is a follow-up epic; the schema must not preclude it.
  • Dedicated MCP tools, not query atoms. Reference lists with enclosing symbols mismatch the file-grouped-line-matches envelope (list_repos/get_file boundary precedent). callers: atoms may be revisited later.
  • Extraction cost is proven cheap: +12% of parse+walk on the Python probe.

Delivery sequence

  1. #83 — db: raw reference-edge schema + lifecycle-safe grants
  2. #84 — indexer: typed call/import edges with enclosing-symbol attribution (Python)depends on db: add raw reference-edge schema with lifecycle-safe grants #83.
  3. #85 — indexer: edge capture for JS, TS, TSX, Go, Java, Rustdepends on indexer: emit typed call/import edges with enclosing-symbol attribution (Python) #84 (pattern proven first).
  4. #86 — search: query-time candidate-set resolver + service payloadsdepends on db: add raw reference-edge schema with lifecycle-safe grants #83, indexer: emit typed call/import edges with enclosing-symbol attribution (Python) #84.
  5. #87 — app: find_references and list_imports MCP toolsdepends on search: query-time candidate-set resolver over raw edges #86.
  6. #88 — webui: graph tools parity and documentationdepends on search: query-time candidate-set resolver over raw edges #86, app: find_references and list_imports MCP tools #87.

Non-negotiable correctness rules

  • Edge rows follow the per-file delete-and-reinsert lifecycle inside the existing indexing transaction (pure DML, no network) and are swept with branch/orphan reconciliation.
  • No cross-file or cross-repo symbol_id FK is ever persisted at extraction time.
  • Tool payloads use the established contract: recoverable conditions are structured fields (truncated, truncation_reason, …), never exceptions; ordering is deterministic; branch scoping is byte-identical to search_code's default-branch coalesce.
  • The lexical query parser/grammar is untouched in this epic.
  • INDEX_SEMANTICS_VERSION is bumped deliberately (once, or once per staged language batch) so the CAS skip logic forces re-extraction.

Acceptance criteria

  • Raw-edge table(s) migrated; app SP SELECT / job SP DML verified on a fresh deploy without manual grant re-application (or the runbook documents the required step).
  • Typed call and import edges with enclosing-symbol attribution emitted for Python, JS, TS, TSX, Go, Java, Rust, with per-language fixtures.
  • find_references and list_imports MCP tools return ranked candidate sets with enclosing symbols, honor branch scoping, truncation flags, and deterministic ordering.
  • "What tests cover symbol X" works as a documented, tested composition (find_references + path filter).
  • Web UI parity via shared app/service.py builders.
  • Resolution-quality distribution measured on the indexed corpus and recorded against the probe baseline.
  • Indexing-time overhead of edge extraction ≤ ~15% of parse+walk.
  • Docs: edge model ("raw edges, query-time resolution, candidate sets"), tool reference, reindex runbook note.

Out of scope

  • Multi-hop traversal (blast radius, call hierarchy, ego-neighborhood, path-between) — follow-up epic.
  • Extraction-time resolution, precomputed transitive closure, cross-repo invalidation machinery.
  • SCIP/stack-graphs/LSP-grade resolution.
  • Graph x semantic fusion queries.
  • callers:-style query-grammar atoms.

References

  • Spec: .omc/specs/deep-dive-code-search-knowledge-graph.md
  • Trace: .omc/specs/deep-dive-trace-code-search-knowledge-graph.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    epicTracking epic with sub-issues

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions