NewRelease 0.12.3 is out: Engine v0.12.3, Dashboard 0.5.2, Helm 0.12.3, the four SDKs, CLI 0.8.2 and MCP 0.12.3Release 0.12.3 is outSee what's new →
Engineering

Knowledge Graphs for AI Agents: Extraction and Traversal

How to build and query knowledge graphs that give your AI agents structured understanding of entities, relationships, and multi-hop reasoning.

Dakera AI TeamPublished Updated 6 min read
On this page
  1. Why Agents Need Knowledge Graphs
  2. Dakera 's Knowledge Graph Model
  3. Building the Graph: Entity Extraction
  4. Graph Traversal
  5. Combining Graph + Vector Search
  6. Graph Maintenance
  7. Real-World Patterns
  8. Performance Considerations

Why Agents Need Knowledge Graphs

Vector search answers the question: "what memories are similar to this query?" Knowledge graphs answer a fundamentally different question: "how do these things relate to each other?"

Consider a personal assistant agent that knows:

  • "Alice is the CTO of Acme Corp"
  • "Acme Corp is evaluating our Enterprise plan"
  • "Bob reports to Alice"
  • "Bob mentioned concerns about data residency"
Memory graph of the four assistant memories. Alice is the CTO of Acme Corp shares the entity Acme Corp with Acme Corp is evaluating our Enterprise plan, and the entity Alice with Bob reports to Alice, which shares the entity Bob with Bob mentioned concerns about data residency. Dakera creates these shares_entity edges when the memories are stored in a namespace that extracts entities.Memory graph of the four assistant memories. Alice is the CTO of Acme Corp shares the entity Acme Corp with Acme Corp is evaluating our Enterprise plan, and the entity Alice with Bob reports to Alice, which shares the entity Bob with Bob mentioned concerns about data residency. Dakera creates these shares_entity edges when the memories are stored in a namespace that extracts entities.
Memories are the nodes; a shared entity links them.Full size ↗Full size ↗

With only vector search, the query "who at Acme Corp has concerns about our product?" might surface the Bob memory if the embeddings align. But it can't reliably traverse from Acme Corp to its employees to their concerns. A knowledge graph makes this traversal explicit and deterministic.

Dakera's Knowledge Graph Model

Dakera's knowledge graph is designed for agent use cases. Its nodes are the agent's memories, not separate entity records, and its edges are typed links between memories. Entities enter the graph as tags on the memories that mention them, and memories that share an entity are linked.

Entities as Tags

Entity extraction is opt-in per namespace. Turn it on with the entity types you care about, and every memory stored afterwards is tagged entity:<type>:<value> for each entity it mentions:

PATCH /v1/namespaces/_dakera_agent_crm/config
{
  "extract_entities": true,
  "entity_types": ["person", "organization", "product"]
}

A rule-based pre-pass finds dates, URLs, UUIDs, emails and IP addresses. GLiNER, a zero-shot named-entity model that runs locally through ONNX, finds the types you listed, so the labels can be your own (deal, contract, service). An agent's memories live in the namespace _dakera_agent_{agent_id}.

Edge Types

Dakera links memories with five edge types:

Edge TypeCreated whenExample
related_toTwo memories have a cosine similarity of at least 0.85Two notes about Acme's Enterprise plan pricing
shares_entityTwo memories carry the same entity:* tag"Alice is the CTO of Acme Corp" and "Bob reports to Alice"
precedesA related memory was created before anotherAn earlier Acme status update before a later one
linked_byYour agent links two memories with POST /v1/memories/{id}/links, with an optional labelBob's concern linked to the Acme deal, label blocks_deal
supersedesA newer memory is very similar (cosine of at least 0.92) to an older one and shares an entity with itA newer note about Alice's role replaces an older one; recall demotes the older memory

There is no works_at or depends_on edge. A relation such as "Alice is the CTO of Acme Corp" lives in the memory text, where extraction and recall read it. When your application knows a typed relation for certain, record it as an explicit link with a label.

Building the Graph: Entity Extraction

Edges reach the graph in two ways: Dakera adds them when memories are stored, and your agent adds explicit links.

Automatic: Store Natural Language

Turn on extraction once, then store facts as plain sentences. Dakera tags each memory with its entities and links it to the agent's other memories:

from dakera import DakeraClient

client = DakeraClient(base_url="http://localhost:3000", api_key="dk-...")

# Once: create the agent and turn on entity extraction for its namespace
client.create_agent("crm")
client.configure_namespace_ner(
    "_dakera_agent_crm",
    extract_entities=True,
    entity_types=["person", "organization", "product"],
)

# Store facts as natural language; Dakera tags entities and links the memories
alice = client.store_memory(agent_id="crm", content="Alice is the CTO of Acme Corp", importance=0.8)
acme = client.store_memory(agent_id="crm", content="Acme Corp is evaluating our Enterprise plan", importance=0.8)
bob = client.store_memory(agent_id="crm", content="Bob reports to Alice", importance=0.7)
concern = client.store_memory(agent_id="crm", content="Bob mentioned concerns about data residency", importance=0.7)

# See what was extracted from a memory
for e in client.memory_entities(alice["id"]).entities:
    print(e.entity_type, e.value)

When your agent learns a relation through tool use (the CRM record says Bob's concern is blocking the deal), record it as a link. It becomes a linked_by edge that carries your label:

client.memory_link(concern["id"], acme["id"], agent_id="crm", label="blocks_deal")

Choosing the Extractor

GLiNER is the default when a namespace sets entity_types; its model (about 782 MB) downloads on first use, or ahead of time with dakera models pull gliner. To extract with an LLM instead (OpenAI, OpenRouter, Anthropic or Ollama), set the namespace's provider with PATCH /v1/namespaces/{ns}/extractor, or server-wide with DAKERA_EXTRACTOR_PROVIDER. Either way the result is the same entity tags, and you write no extraction code. To see what a piece of text would produce without storing it, send it to POST /v1/memories/extract.

Graph Traversal

The primary operation on a knowledge graph is traversal: start from one memory and follow edges breadth-first to discover related information. The server caps traversal depth at 5.

Single-Hop Traversal

# Memories directly connected to the Alice memory
graph = client.memory_graph(alice["id"], depth=1)

for edge in graph.edges:
    print(edge.source_id, edge.edge_type.value, edge.target_id, edge.weight)
# shares_entity edges to "Acme Corp is evaluating our Enterprise plan" (Acme Corp)
# and to "Bob reports to Alice" (Alice)

Multi-Hop Traversal

# Everything within 3 hops of the Acme memory
graph = client.memory_graph(acme["id"], depth=3)

for node in graph.nodes:
    print(node.depth, node.importance, node.content_preview)

# The shortest path between two memories
path = client.memory_path(acme["id"], concern["id"])
print(path.hops, path.path)

Filtered Traversal

The server returns every edge type. The SDKs filter the returned edges for you:

# Only shared entities and explicit links
entity_graph = client.memory_graph(acme["id"], depth=3, types=["shares_entity", "linked_by"])

The real power emerges when you combine knowledge graph traversal with vector search. The graph tells you which memories surround an entity, and recall ranks the memories that answer the question:

def graph_enhanced_search(agent_id: str, query: str, seed_memory_id: str):
    """Use graph traversal to expand context, then recall memories."""

    # Step 1: memories around the seed, two hops out
    graph = client.memory_graph(seed_memory_id, depth=2)

    # Step 2: the related memories become context for the query
    related = [n.content_preview for n in graph.nodes]
    expanded_query = f"{query} (related: {'; '.join(related)})"

    # Step 3: recall ranks memories against the graph-informed query
    results = client.recall(agent_id=agent_id, query=expanded_query, top_k=10)

    return {
        "graph_context": graph.nodes,
        "memories": results.memories
    }

# Example: "What issues does Acme Corp have?"
# Traversal from the Acme memory reaches Alice, Bob and Bob's concern
# Recall uses them as additional context
answer = graph_enhanced_search("crm", "what issues does Acme Corp have?", acme["id"])

Graph Maintenance

Deduplication

Agents may store the same fact more than once. AutoPilot, on by default, merges memories above 0.93 similarity every 6 hours, so repeated facts do not pile up as separate memories in the graph.

Confidence Scoring

Not all extracted facts are equally reliable. Store an importance that reflects your confidence. Every graph node carries its memory's importance, so you can drop low-confidence nodes from a traversal in your code, and recall's min_importance leaves them out of search:

# A fact from a single email: lower importance
weak = client.store_memory(
    agent_id="research",
    content="Jane Doe works at StartupXYZ (from one email, unconfirmed)",
    importance=0.4
)

# Keep only high-confidence nodes from a traversal
graph = client.memory_graph(weak["id"], depth=2)
reliable = [n for n in graph.nodes if n.importance >= 0.6]

# And from recall
results = client.recall(agent_id="research", query="where does Jane Doe work", min_importance=0.6)

Changing Facts

Relationships change over time. Alice might leave Acme Corp. Store the new fact as natural language. If the newer memory is very similar to the old one (cosine of at least 0.92) and shares an entity with it, Dakera adds a supersedes edge and recall demotes the older memory. Related memories also get precedes edges in the order they were created, so timelines stay traversable. To retire the old fact outright, forget it:

# Store the newer fact
client.store_memory(
    agent_id="crm",
    content="Alice is the CTO of NewCo; she left Acme Corp",
    importance=0.9
)

# Or remove the outdated memory
client.forget(agent_id="crm", memory_id=alice["id"])

Real-World Patterns

Customer Intelligence Graph

Map relationships between contacts, companies, deals, and products. Agents can answer "who at Company X has authority over purchasing decisions?" by following shares_entity edges from the company's memories to the people they mention.

Infrastructure Dependency Graph

Store each dependency as a memory ("The Auth Service keeps sessions in Redis") in a namespace that extracts a service type. Every memory that mentions Redis is then one shares_entity hop from a Redis memory, which answers "what will break if Redis goes down?" Where the direction matters, link the memories explicitly with a label such as depends_on.

Project Knowledge Graph

Connect people to projects to decisions to code components. Agents can trace "who decided to use PostgreSQL for the auth service?" through the memories that share those entities.

Performance Considerations

Dakera's memory graph lives with the agent's memories under the server's data root, so there is no separate database to run. It is durable by default (DAKERA_GRAPH_PATH sets another location), and its edges are scoped by namespace, so one agent's links are never read or deleted by another. Extraction runs on the store path inside the Dakera process; with GLiNER as the provider it makes no external calls.

For dense graphs, limit traversal depth to 2 or 3 hops and filter edges by type. The nodes a traversal returns grow with the branching factor at every hop, and a common entity links many memories at once.

Implementation patterns: Knowledge Graph Entity Linking · Semantic Deduplication · Multi-Agent Shared Memory

Add a knowledge graph to your agent with no separate database

Dakera links an agent's memories into a graph with five edge types as you store them, and keeps it durable alongside the memories.

Stay sharp on agent memory
Benchmark releases, engineering deep-dives, and product updates. Once a week max, no fluff.
✓ Subscribed. Thanks!
Prefer a reader? RSS · Atom