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"
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 Type | Created when | Example |
|---|---|---|
related_to | Two memories have a cosine similarity of at least 0.85 | Two notes about Acme's Enterprise plan pricing |
shares_entity | Two memories carry the same entity:* tag | "Alice is the CTO of Acme Corp" and "Bob reports to Alice" |
precedes | A related memory was created before another | An earlier Acme status update before a later one |
linked_by | Your agent links two memories with POST /v1/memories/{id}/links, with an optional label | Bob's concern linked to the Acme deal, label blocks_deal |
supersedes | A newer memory is very similar (cosine of at least 0.92) to an older one and shares an entity with it | A 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)
Explicit Links for Relations Your App Knows
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"])
Combining Graph + Vector Search
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.



