Context Graphs and GraphRAG: Making Relationships Retrievable
In Semantica, ContextGraph and GraphRAG address a specific retrieval problem: a relevant answer may depend on relationships, time, and connected evidence that no single text chunk contains. This chapter explains how typed graph state, bounded traversal, vector search, and agent memory work together, so a reader can judge when structural context improves retrieval and when a returned path is only a useful signal rather than a proof.
The distinction from the previous source-to-graph step is important. Extraction made entities and relationships available. A graph makes them queryable. GraphRAG then decides how much of that structure should join a semantic search result before an LLM generates an answer. Each step adds value, but each also adds a new way for poor data or loose assumptions to affect the result.
A graph changes the question
A vector store is good at finding text that resembles a query in meaning. If the query asks for material about a particular vulnerability, product, or policy, similarity search can locate passages whose language is close. But many useful questions are relational: Which systems are affected by this vulnerability? Which organization is connected to an observed event through an intermediary? What was true at the time of a decision? What other entities sit within a specified number of steps?
A ContextGraph represents those questions directly. A node has an identifier, a type, optional content, properties, and metadata. An edge points from one node to another and carries a relationship type, a weight, and optional metadata. The difference between “Acme” and “Acme is a supplier of Component X” is the difference between an item and a usable relationship. The edge makes the connection explicit; its properties can preserve a confidence value, a source marker, or a time window.
That structure supports several kinds of retrieval that a flat embedding does not provide by itself. A graph can follow multiple hops, filter which relationship types may be followed, and ignore edges below a minimum weight. It can return the hop distance and the path that led to a neighbor. In Semantica, optional distance metadata also reports a distance band and a confidence-decay value formed from the weights along the path. A direct connection and a fourth-hop connection are therefore not presented as equally strong merely because both are reachable.
This is not an argument that graph search replaces semantic search. The two answer different questions. Similarity helps discover a starting point when the user does not know the exact identifier or wording. Traversal follows explicit structure once an entity or candidate is available. A query about a threat actor may find the actor through semantic content, then traverse to tools, infrastructure, and targets. A query about a contract may find the contract text, then follow typed links to a supplier, an obligation, and a governing policy. The combination is useful precisely because discovery and connection are different operations.
Time changes the meaning of a connection. Semantica allows nodes and edges to carry valid_from and valid_until values. A graph can then exclude an expired relationship from a current view while retaining it for historical analysis. The temporal layer distinguishes valid time—when a fact was true in the world—from transaction time—when the system recorded or superseded that fact. A leadership relationship, campaign infrastructure node, or policy version can be queried against the relevant date rather than against whatever happens to be current now.
That distinction prevents a common analytical error. Suppose a security graph records that an infrastructure endpoint was active during a campaign and later retired. A present-day query should not describe the endpoint as active simply because the node remains in storage. Conversely, a historical investigation should not lose the endpoint merely because it is no longer current. state_at() and the temporal query interfaces make the selected time part of the query's meaning.
ContextGraph itself is an in-memory property graph. Its implementation uses indexes and a re-entrant lock around mutations, which supports concurrent writes within the process. That makes it convenient for tests, local analysis, and a shared runtime context. It does not make the graph a durable, independently scaling database. Applications must explicitly persist it, and AgentContext.save() is the higher-level operation that coordinates memory, vector state, and graph state. Separate graphs can also be linked without merging their contents, but links must be reconnected after loading because live object references are not serialized.
These operational details are part of the retrieval model. If the graph was not saved, a restart removes the structure that GraphRAG depends on. If a cross-graph link is not resolved, a path stops at the boundary. If a relationship has no meaningful type or a node is duplicated under several names, traversal can produce a technically valid but analytically weak neighborhood. Structure helps only when the structure expresses the domain accurately.
How a GraphRAG request is assembled
Semantica's GraphRAG path begins with a query and can draw from three stores: a vector store, a knowledge graph, and agent memory. AgentContext automatically chooses graph-backed retrieval when a knowledge graph is configured, but callers can force vector-only retrieval. That makes the comparison testable: the same application can ask what it retrieves with semantic similarity alone and what it retrieves when structural context is enabled.
The intended flow is a bounded expansion rather than a walk through the entire graph. Vector retrieval finds candidate content. Graph retrieval identifies relevant nodes and related entities, then follows relationships within a hop budget. Memory retrieval contributes stored context. The results are combined, scored, deduplicated, and passed to the caller. When an LLM is requested, the retriever supplies the ranked context and selected paths as part of a prompt.
The score is not simply the raw value returned by one backend. The current ContextRetriever normalizes scores within vector, graph, and memory result groups. hybrid_alpha controls the relative weighting of vector and graph results. Graph results may receive a small boost for related context, and an additional semantic comparison can re-rank the final content when the vector store exposes an embedding function. Duplicate graph hits are merged by node identity, while non-graph results are merged by a content key. These choices are ranking mechanics, not declarations that one source is factually superior.
An anchored query adds another structural signal. If a caller knows the relevant entity, proximity can be blended into a result using hop distance. A neighbor one hop away can be ranked differently from one three hops away, and the path can be retained for inspection. The public AgentContext API separates two controls that are easy to confuse: max_expansion_hops governs graph expansion configured on the context, while max_hops with an anchor limits the proximity radius used for scoring. A call to query_with_reasoning() also accepts a hop bound for constructing its returned reasoning paths. The names describe related but different operations.
The hop budget is a quality and cost control. A second or third hop can reveal a useful connection that a document-level search misses. Additional hops also increase the number of entities and relationships that may be assembled, which increases latency and the tokens sent to an LLM. A broad subgraph can dilute the answer as easily as it can enrich it. The repository's guides recommend starting with small expansion depths and tuning them against the domain rather than assuming that more context is always better.
query_with_reasoning() adds a generation step. It retrieves context, collects related entities, builds bounded paths, and formats context and path information for an LLM provider. The returned structure includes the generated response, source excerpts, a path string, a confidence value, and counts. The internal prompt asks the model to use the retrieved context, cite entities and relationships from the paths, explain multi-hop connections when relevant, and say when the context does not contain the answer. If generation fails, the implementation can return a summary of the retrieved context instead of treating the entire retrieval as unavailable.
That behavior makes the path useful as an inspection aid. An analyst can see a route such as actor to tool to target and ask whether each edge is supported. A source list shows which context items reached the model. If the application filters graph state by time before retrieval, that selected view can be part of the context presented to the model. These surfaces are more informative than a bare answer string.
They are not a guarantee of answer correctness. In the current implementation, the returned confidence is computed from retrieved scores and whether paths were found. It is a local heuristic, not a calibrated probability that the answer is true. The path is a traversal route, not necessarily a causal explanation. It shows how the retriever connected nodes; it does not by itself establish that the first node caused the last event, that the relationship is complete, or that the source assertion was accurate.
There is also a meaningful difference between the platform's conceptual GraphRAG description and every current code path. The documentation describes graph traversal as seeded by semantically relevant vector matches. For a graph-like object with a query() method, the current retriever calls that graph query and then gathers related entities; ContextGraph.query() is a simple keyword search over node content. A dictionary-shaped graph can use an embedding-based entity and relationship path when the vector store supplies embeddings. The architecture is genuinely hybrid, but its behavior depends on the graph representation and available methods. A responsible deployment should test the concrete path it will use rather than infer behavior from the label “GraphRAG.”
This qualification does not erase the value of the design. It identifies where to evaluate it. A team can ask whether its graph query finds the right anchors, whether entity links are stable, whether score blending improves retrieval on representative questions, and whether the assembled context stays within a usable size. It can compare vector-only and graph-enabled results, inspect missed connections, and set a hop limit that matches the domain's real reasoning distance.
What structural retrieval can and cannot establish
The strongest case for GraphRAG is a question whose answer is distributed across connected records. A threat investigation may need to move from an actor to a tool, from that tool to an affected organization, and from the organization to a sector. A clinical evidence graph may need to connect a drug to an enzyme, a metabolic effect, and an interaction risk. A regulatory graph may need to follow cross-references between a rule, an article, and a data-quality obligation. In each case, the graph contributes a relationship that a text-similarity score might rank too low or miss entirely.
The weaker case is a simple lookup with a known source. If the answer is a single sentence in a document, vector search may be faster and easier to inspect. A graph adds extraction, identity management, traversal, and storage overhead. It can also add false connections. Over-connecting every pair of entities mentioned in one document creates a dense neighborhood in which proximity says little. Inconsistent names split one real entity into several nodes and make the graph appear less connected than the underlying domain. Missing edges make a complete answer look unavailable.
GraphRAG therefore amplifies graph quality. It does not repair poor extraction, duplicate entities, or weak relationship definitions. The same applies to temporal context. A timestamp filter can exclude stale facts only when the source or pipeline supplied a meaningful validity window. It cannot infer the correct end date from an absent or ambiguous record. Historical reconstruction is only as reliable as the time semantics attached to the graph.
The right reading of a GraphRAG result is consequently layered. The semantic score indicates relevance under the retrieval method. The graph path indicates a structural route. The edge weights and distance decay indicate how the implementation ranks that route. The source metadata indicates where the assertions came from. The LLM response is a synthesis over the supplied context. Those layers can be inspected separately, and none should be silently promoted into another layer's certainty.
This is the practical contribution of Semantica's context layer. It gives an application a place to preserve relationships and time, a way to combine structure with semantic discovery, and an output that can expose sources and paths alongside generated language. It makes retrieval more inspectable and often more expressive for relational questions. It does not turn every graph edge into truth, every traversal into causality, or every confidence value into a measured probability.
The next engineering question follows directly from that boundary. If the graph is going to guide retrieval and later inference, the team must know whether its nodes refer to the right things, how conflicting source claims are handled, and which evidence remains attached to each assertion. Structural retrieval can only be as credible as the quality and accountability controls that produced the structure.