Graph expansion (related entities)
Hybrid retrieval over Vespa gives you the chunks that match a query. Graph expansion is the optional step that surfaces records you didn't directly match — but that the matched ones reference.
The platform maintains a property graph in NebulaGraph with one node per entity (page, issue, pull request, document) and edges for every cross-record reference the connector detects:
refers_to— a chunk's text contains an explicit reference to another entity. Today's extractor catches:- Jira-style issue keys (
PROJ-123), - GitHub
owner/repo#Nor bare#Ninside a GitHub chunk, - Confluence inter-page links (markdown-style
[Title](Title)), - URLs into known sources (
*.atlassian.net/browse/…,*.atlassian.net/wiki/spaces/…/pages/…,github.com/owner/repo/(issues|pull)/N).
- Jira-style issue keys (
supersedes— emitted by the supersession engine when one entity replaces another.
When graph expansion is enabled for a tenant, every search response includes both the direct hits and a small set of related entities pulled by walking one or two hops from the top results.
Stub vertices
When the extractor finds a reference to an entity that hasn't been ingested yet (e.g. a Confluence page that mentions PROJ-123 before the Jira connector runs), it writes a stub vertex with record_type = "unknown". The stub is upgraded in place once the real entity arrives — same canonical entity id, last-write-wins.
When it helps
Consider this query: "why did we change the retry strategy in the billing service?"
Without expansion:
- The chunk explaining the retry change ranks #1.
- The chunk explaining
why(a PR description or an incident postmortem) might rank #8 or not at all.
With expansion:
- The chunk explaining the retry change ranks #1.
- The platform walks
closesandmentionsedges from that entity, finds the linked PR + incident report, and includes their top chunks as related hits.
Graph expansion is most useful for "what's the context behind this?" queries. It's neutral for straight keyword lookups.
How to enable it
Graph expansion is opt-in per tenant:
UPDATE tenant_query_config
SET graph_expansion_enabled = true,
graph_expansion_max_hops = 2,
graph_expansion_max_related = 5
WHERE tenant_id = 'acme';max_hops caps how far the expansion walks (default 1; 2 is the practical upper bound). max_related caps how many extra hits the platform returns (default 5; setting it higher will dilute the primary hit set).
The query coordinator picks up the change on its next config-refresh tick.
What the response looks like
Vespa hits get an additional related_entities array per hit when graph expansion fires. Each entry carries the related entity id (entity_id), the edge type that connected them (edge_type), and a brief snippet of the surrounding source text (snippet):
{
"hits": [
{
"chunk_id": "a4b2…",
"parent_entity_id": "github|acme|pull_request|812",
"text": "switched the billing retry strategy to a token bucket…",
"related_entities": [
{
"entity_id": "github||ref|acme/widget#803",
"edge_type": "refers_to",
"snippet": "…closes acme/widget#803 — billing rate-limit exhaustion…"
},
{
"entity_id": "confluence|acme.atlassian.net|page|74215",
"edge_type": "refers_to",
"snippet": "…see the [Post-incident review](Post-incident review)…"
}
]
}
]
}Related entities are subject to the same access-control checks as direct hits. A user who can't read the linked PR doesn't see it surface — even if the parent hit they ran the query against is fully accessible.
Cost characteristics
Expansion adds one NebulaGraph round-trip per query (regardless of max_hops). On a single-region deployment with NebulaGraph co-located, this adds 5–15 ms of latency. The graph projection is asynchronous from ingestion — there's no extra cost in the ingest path.
If you don't enable expansion, the graph still gets populated. Disabling expansion only skips the query-time walk; nothing in the ingest pipeline depends on whether your tenant has it on.

