Flat vector RAG treats every function like an island. In a microservice estate that is how you get answers that cite payments-api when the bug lives three hops away in ledger-writer. GraphRAG stitches call graphs, ownership, and deploy boundaries into retrieval so the model walks the hop chain humans already keep in their heads.
⚡ TL;DR: Index symbols as nodes and RPC/event edges as first-class retrieval signals. Expand top-k chunks along the call graph (depth 1–2) with ownership filters before generation. Prefer graph expansion over dumping more random neighbors. Pair with Hybrid Search for Code and Bedrock Retrieval Filters so lexical + vector + graph stay tenant-safe.
Model services as a typed graph
Store nodes for services, packages, and symbols; edges for calls, publishes, consumes, and owned_by.
// graph/types.ts
export type NodeKind = "service" | "package" | "symbol";
export type EdgeKind = "calls" | "publishes" | "consumes" | "owned_by";
export type GraphNode = {
id: string; // payments-api::InvoiceService.create
kind: NodeKind;
service: string;
path: string;
owner: string; // CODEOWNERS team
chunkId: string; // vector store id
};
export type GraphEdge = {
from: string;
to: string;
kind: EdgeKind;
weight: number; // call frequency / static confidence
};
Build edges from OpenAPI clients, EventBridge schemas, and static imports — not from README prose. Compare store choices in OpenSearch vs Aurora pgvector.
Retrieve, then expand along edges
# graphrag_retrieve.py — illustrative
from dataclasses import dataclass
@dataclass
class Hit:
chunk_id: str
score: float
node_id: str
def retrieve_and_expand(query: str, k: int = 6, depth: int = 2) -> list[Hit]:
seeds = vector_search(query, k=k) # BM25+kNN hybrid
frontier = {h.node_id for h in seeds}
expanded = list(seeds)
for _ in range(depth):
nxt = set()
for nid in frontier:
for edge in outbound(nid, kinds=("calls", "publishes", "consumes")):
if edge.weight < 0.3:
continue
chunk = chunk_for(edge.to)
expanded.append(Hit(chunk.id, edge.weight * 0.5, edge.to))
nxt.add(edge.to)
frontier = nxt
# ✅ Dedupe by chunk_id, keep highest score
return dedupe_keep_best(expanded)[:12]
❌ Expanding every import in node_modules or every transitive type-only edge. Cap depth and filter by service boundary.
Ownership metadata keeps answers actionable
When the graph returns ledger-writer, attach the CODEOWNERS team and on-call so the assistant can say who to ping — not just what file. Same discipline as RAG over ADRs.
# chunk metadata (ingest)
service: ledger-writer
symbol: PostingJournal.append
owner: team-ledger
oncall: pagerduty:ledger
path: src/journal/PostingJournal.ts
Fail closed on cross-domain hops
Product policy: agents may traverse calls inside a bounded context, but crossing payments → growth-experiments requires an explicit allow edge. Encode that as a graph ACL checked before expansion — mirrors tenant isolation filters.
| Signal | Use | Anti-pattern |
|---|---|---|
| Static call edge | Always expand depth 1 | Blind full-repo BFS |
| Event publish/consume | Expand with schema id | Matching topic names only |
| Ownership | Cite team in answer | Orphan chunks with no owner |
| Deploy unit | Prefer same service | Mixing unrelated cell graphs |
Closing checklist
✅ Dos
– ✅ Build edges from contracts (OpenAPI, events), not prose
– ✅ Hybrid seed retrieval, then depth-limited graph expand
– ✅ Stamp owner + service on every node
– ✅ ACL cross-domain hops
– ✅ Evaluate with multi-hop questions (“who writes the ledger after checkout?”)
❌ Don’ts
– ❌ Don’t treat graph edges as optional metadata the model might ignore
– ❌ Don’t expand into vendored dependencies
– ❌ Don’t return chunks without a path + service citation
– ❌ Don’t skip reindex when CODEOWNERS or OpenAPI change
Related reading
- Hybrid Search for Code: BM25, Vectors, and Symbol Indexes Together
- Bedrock Retrieval Filters: Tenant Isolation for Multi-SaaS Code RAG
- RAG on AWS: OpenSearch vs Aurora pgvector for Codebase Chat
- RAG over ADRs: Architecture Decision Retrieval for Coding Agents
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
