LISTEN TO THIS ARTICLE

A knowledge graph is useful when your application needs to query explicit relationships: which service depends on a component, who owns it now, or which evidence supports a claim. It does not automatically make an agent accurate, current or safe.

Start with a question your existing retrieval system handles badly. Inspect the missing evidence before choosing a database. If the problem is stale documents, poor access filtering or ambiguous names, adding graph storage alone will preserve those problems in another format.

What graphs add to retrieval

A graph represents entities and their relationships explicitly. In an RDF representation, statements have a subject, predicate and object; the object can also be a literal value. The W3C specification defines that model. Property graphs use their own node, relationship and property conventions.

That structure lets an application follow a defined relationship instead of relying entirely on text similarity. A service can depend on a library whose maintainer belongs to a particular team. The traversal is useful only if those relationships are correctly recorded.

Vector retrieval finds candidates by similarity. A complete retrieval system can also apply metadata filters, combine keyword search, run repeated searches or join structured records. Weaviate's filter documentation, for example, describes property filters and combined conditions. It is therefore misleading to say vector systems cannot handle dates or support questions spanning documents.

The practical distinction is where you represent and maintain the relationships. Sometimes a relational table and a join are sufficient. Sometimes graph traversal makes the required paths easier to express. Neither choice prevents a language model from misreading the retrieved evidence.

A useful graph preserves the identity and evidence behind a relationship.

A useful graph preserves the identity and evidence behind a relationship.

Choose a narrow question

Consider an internal engineering assistant asked: “Who maintains the library used by this service?” Define the expected path from service to dependency to maintainer. Decide what counts as a dependency: an imported package, a deployed service or a declared build requirement.

Then inspect the records. Does the package have a stable identifier? Is ownership recorded in an authoritative source? Are historical assignments distinguishable from current ones? Can the requester read the ownership record?

These questions expose the ingestion and permission work before a graph demonstration makes retrieval look solved. For a broader architecture comparison, use the retrieval, fine-tuning and long-context guide.

Resolve identity before adding edges

Display names are weak identifiers. Different services may share the name “Atlas”, while the same service appears under a repository name, an internal alias and a deployment label.

Prefer an authoritative identifier scoped to its system, such as a repository ID with its hosting organisation. Keep labels and aliases as attributes. An extracted name should resolve to a candidate entity only when the available evidence supports that match. If several candidates remain, return an ambiguity for review instead of silently merging them.

Record the source of a proposed relationship and the extraction method. A fluent extraction is not confirmation that the source states the relationship. Review disputed assignments, unsupported links and accidental entity merges before making them available to an agent.

A small, runnable example

The following synthetic example separates identical labels by namespace, closes an old maintainer assignment and checks the caller's current permissions before returning a relationship. It uses the Python standard library and deliberately avoids embedding models and database services.

Save it as graph_memory.py, then run python3 graph_memory.py.

from datetime import date

entities = {
    "catalog:atlas": ("catalog", "Atlas"),
    "analytics:atlas": ("analytics", "Atlas"),
}

def resolve(namespace, label):
    matches = [key for key, value in entities.items()
               if value == (namespace, label)]
    if len(matches) != 1:
        raise ValueError("Entity is missing or ambiguous")
    return matches[0]

def current_owner(rows, entity, day, allowed):
    # allowed comes from the current authenticated permission check.
    return [r["owner"] for r in rows
            if r["entity"] == entity
            and r["source"] in allowed
            and r["start"] <= day
            and (r["end"] is None or day < r["end"])]

entity = resolve("catalog", "Atlas")
rows = [{
    "entity": entity, "owner": "Platform", "source": "private-roster",
    "start": date(2026, 1, 1), "end": None,
}]
changed = date(2026, 6, 1)
rows[0]["end"] = changed
rows.append({
    "entity": entity, "owner": "Infrastructure",
    "source": "private-roster", "start": changed, "end": None,
})
print(current_owner(rows, entity, changed, {"private-roster"}))
print(current_owner(rows, entity, changed, set()))

The output is ['Infrastructure'] followed by []. The old assignment remains available for a historical query, but is excluded on the change date. Someone without permission to read the source receives no owner.

The complete example and tests also exercise historical queries, name ambiguity, a different entity with the same label and permission revocation. Run that file with --test to reproduce the checks.

This is a teaching example, not an access-control service. Production code must obtain identity and permissions from a trusted service, protect writes and make updates atomic. It must prevent disclosure through intermediate results, summaries, cached responses and error messages as well as the final answer. The caller must not choose their own access list.

Storing a timestamp does not decide which fact should be trusted now.

Time is a data contract

The example stores when an assignment is valid. It does not separately record when the system learned about it. Those times can differ: a late-arriving correction may change the historical answer without changing the original ingestion date.

Choose the temporal semantics your question needs. Preserve source timestamps, ingestion timestamps and any validity interval separately where relevant. Define how corrections, deletions and conflicting sources affect queries. Test boundary dates and late updates.

Graphiti is an example of a framework that explicitly implements temporal graph capabilities. Those capabilities are implementation work, not a property every graph database provides automatically.

Storing a timestamp does not decide which fact should be trusted now.

A cached summary can remain stale after the underlying edge changes. Refresh or invalidate derived outputs when their evidence changes, and recheck permissions when serving them.

GraphRAG is a design family

Microsoft GraphRAG describes a pipeline that extracts a graph from text, organises communities and builds summaries for retrieval. That is a particular architecture, not a requirement for every graph-backed assistant.

You might instead retrieve relevant documents and expand selected entities through a small curated graph. Or query structured relationships directly and use a model only to explain the results with citations. Choose the least complicated route that answers your target questions.

Published benchmark gains do not establish expected savings or accuracy for your corpus. Extraction quality, question selection, baselines and evaluation methods all affect the result. Measure your own system before committing to a larger indexing pipeline.

Test the value before expanding

Build an evaluation set containing ordinary lookups, relationship questions, ambiguous names, corrected facts and restricted records. Keep the source corpus, access rules and answer criteria consistent across candidate systems.

Compare the existing retrieval route with the proposed graph route. Check answer correctness, evidence validity, missing entities, false merges, stale answers and unauthorised disclosures. Record ingestion work, update delay, query latency and cost alongside quality.

Use the agent evaluation checklist to keep the acceptance record. Keep graph construction failures visible rather than treating an empty retrieval result as proof that no relationship exists.

Expand when the relationship representation fixes observed failures at an acceptable maintenance cost. If ordinary retrieval with better filters solves them, the graph has not yet earned its place.

Sources