Your agent asked a question and then couldn’t find the answer it needed
You built an agent that uses a knowledge graph. The agent thinks: “I need to find the customer’s purchase history.” It should query the graph for [Customer] –purchase–> [Product]. But instead it searches for “customer purchases” in the vector database and gets back 47 documents.
The agent has a knowledge graph available but doesn’t know how to use it. It’s like having a structured database and treating it like Google.
This is the problem agentic knowledge graph engineering solves.
Three layers: Data, Agent, Verification
Input: "What's the revenue impact of Feature X on customers in region Y?"
↓
[Layer 1] Agent understands the question
- Parses: Need Feature, Customer Region, Revenue metrics
↓
[Layer 2] Agent queries the graph
- Finds: All customers in Region Y who use Feature X
- Finds: Their revenue before/after adopting Feature X
↓
[Layer 3] Agent verifies the answer
- Computes: Revenue impact = (after - before) / before
- Formats: "Revenue increased by 23% for region Y customers"
↓
Output: Answer with proof path
Layer 1: Agent understands questions (semantic parsing)
Your agent receives a question. It needs to break it into graph queries.
Problem: How does the agent know what to search for?
Solution: Schema-aware reasoning
class SchemaAwareAgent:
def __init__(self, graph_schema):
self.schema = graph_schema
"""
Schema knows:
- Node types: Customer, Product, Feature, Region, etc.
- Relationships: uses, has_feature, in_region, etc.
- Properties: Customer.id, Product.revenue, etc.
"""
def parse_question(self, question):
"""Break question into graph queries"""
prompt = f"""
The knowledge graph has these entities and relationships:
{self.schema}
Question: {question}
What entities does this question ask about?
What relationships do you need to follow?
Return a structured plan, not a query yet.
"""
plan = call_claude(prompt)
return plan
# Returns something like:
# "Need to find [Feature X] --has--> [Product]
# then find [Customer] --in--> [Region Y] --uses--> [Product]
# then find [Revenue] data for those customers"
Then: Translate plan to queries
def plan_to_queries(self, plan):
"""Convert the plan to actual Cypher queries"""
prompt = f"""
Given this plan: {plan}
And this schema: {self.schema}
Write Cypher queries to answer it.
Return one query per step.
"""
queries = call_claude(prompt)
return queries
# Returns:
# Query 1: MATCH (f:Feature {name: "Feature X"}) RETURN f
# Query 2: MATCH (c:Customer)-[:in]->(r:Region {name: "Region Y"})
# (c)-[:uses]->(p:Product)-[:has]->(f:Feature {name: "Feature X"})
# RETURN c
# Query 3: MATCH (c:Customer) WHERE c.id IN [...]
# RETURN c.id, c.revenue_before, c.revenue_after
Result: Agent asked a natural language question, broke it into graph queries, got structured data back.
Layer 2: Agent queries the graph (navigation)
Your agent has a plan and queries. Now it needs to execute them and reason about results.
class GraphNavigator:
def __init__(self, graph_db):
self.db = graph_db # Neo4j connection
def navigate(self, queries, question):
"""Execute queries and reason about results"""
results = []
for i, query in enumerate(queries):
result = self.db.execute(query)
results.append(result)
# After each query, decide what to do next
if i < len(queries) - 1:
# Intermediate step, continue
pass
else:
# Final result, analyze it
answer = self.analyze_results(results, question)
return answer
def analyze_results(self, results, question):
"""Turn raw query results into an answer"""
prompt = f"""
The user asked: {question}
Here are the query results:
{results}
What's the answer to the question?
Show your reasoning.
"""
answer = call_claude(prompt)
return answer
Example execution:
Question: "What's the revenue impact of Feature X on region Y?"
Query 1 result:
Feature X exists (found it)
Query 2 result:
5 customers in region Y use Feature X
[customer_ids: 101, 102, 103, 104, 105]
Query 3 result:
Customer 101: revenue before = $50k, after = $65k (+30%)
Customer 102: revenue before = $80k, after = $95k (+19%)
... (5 customers total)
Analysis:
Average revenue impact: +23%
Total revenue impact: +$120k/year
Customers benefiting: 5
Answer: "Feature X increased revenue by 23% for region Y customers
(5 customers, +$120k total impact)"
Layer 3: Agent verifies answers (reasoning)
The agent found an answer. Before returning it, verify it’s correct.
Verification strategies:
Strategy 1: Re-derive the answer
def verify_by_rederiving(answer):
"""Ask the LLM to verify by deriving again independently"""
prompt = f"""
I got this answer: {answer}
Verify it by deriving the same result from first principles.
Show your work.
If you get a different answer, flag it.
"""
verification = call_claude(prompt)
if verification.matches(answer):
return True # Verified
else:
return False # Mismatch, investigate
Strategy 2: Check constraints
def verify_constraints(answer):
"""Verify the answer satisfies all constraints"""
constraints = [
"Revenue impact must be > -100% (can't lose more than you have)",
"Customer count must match query results",
"Time periods must be consistent",
]
for constraint in constraints:
check = call_claude(f"Does {answer} satisfy: {constraint}?")
if not check:
return False # Failed a constraint
return True # All constraints satisfied
Strategy 3: Query alternative paths
def verify_by_alternative_path(question):
"""Derive the answer via a different graph path"""
# Original path: Customer -> Feature usage -> Revenue
# Alternative path: Product -> Revenue changes -> Customers using it
alt_query = """
MATCH (p:Product)-[:has]->(f:Feature {name: "Feature X"})
(r:RevenueChange)-[:caused_by]->(f)
(c:Customer)-[:affected_by]->(r)
WHERE c.region = "Region Y"
RETURN SUM(r.impact)
"""
alt_result = execute_query(alt_query)
# Compare original result with alternative
if alt_result ≈ original_result:
return True # Verified via independent path
else:
return False # Results don't match, investigate
Real example: Procurement agent
Setup: Knowledge graph with vendors, products, costs, quality ratings.
Nodes:
- Vendor, Product, Contract, CustomerAccount, Region
Relationships:
- Vendor --supplies--> Product
- Product --costs--> Price
- Product --quality--> Rating
- Vendor --has-contract--> Contract
- Contract --with--> CustomerAccount
- CustomerAccount --in--> Region
Agent request: “Find the best vendor for Product X for our California customers within $50k budget”
Layer 1: Parse
Entities needed: Product (X), Region (California), Budget ($50k)
Relationships: Product --supplied-by--> Vendor
Vendor --costs--> Price (must be < $50k)
Vendor --quality--> Rating
CustomerAccount --in--> Region
Layer 2: Query
Query 1: Find all vendors supplying Product X
MATCH (v:Vendor)-[:supplies]->(p:Product {name: "Product X"})
RETURN v
Query 2: Filter by budget and get quality ratings
MATCH (v:Vendor)-[:supplies]->(p:Product {name: "Product X"})
(v)-[:costs {amount: cost}]->(price)
WHERE cost < 50000
RETURN v.name, v.quality_rating, cost
ORDER BY v.quality_rating DESC
Query 3: Check contracts with our California customers
MATCH (v:Vendor)-[:supplies]->(p:Product {name: "Product X"})
(v)-[:has_contract]->(c:Contract)-[:with]->(ca:CustomerAccount)
(ca)-[:in]->(r:Region {name: "California"})
RETURN DISTINCT v.name
Layer 3: Verify
Verify: "Is Vendor A really the best choice?"
Constraint checks:
- Cost < $50k? ✓
- Supplies Product X? ✓
- Has existing contracts in California? ✓
- Quality rating > 0.85? ✓
Re-derive: "Which vendor has highest quality + existing CA presence?"
Result: Vendor A (matching original answer)
Answer: "Vendor A is the best choice: $40k, quality 0.92, existing CA contracts"
Common pitfalls
Pitfall 1: Over-specifying the schema
Don’t create 100 relationship types. Start with 10-15 that matter.
Bad: vendor_supplies_product_at_cost_in_q1_2026
Good: vendor --supplies--> product (with properties: cost, quarter)
Pitfall 2: Not versioning the graph
Products change, prices change, regions change. Your graph gets stale.
# Wrong: Update nodes in place, lose history
MATCH (p:Product {id: 123})
SET p.price = 50 # Lost the old price
# Right: Version the data
CREATE (price:Price {amount: 50, effective_date: "2026-04", valid: true})
MATCH (p:Product {id: 123})
SET p.valid = false
CREATE (p)-[:superseded_by]->(new_p:Product {id: 123, version: 2})
Pitfall 3: Forgetting the agent can’t always query the graph
Sometimes the question is ambiguous. Sometimes the graph is incomplete.
# Handle uncertainty
if graph_query_succeeded:
answer = use_graph_result()
elif graph_incomplete:
answer = "I need more information about [X]"
elif question_ambiguous:
answer = "Did you mean option A or option B?"
else:
answer = "I couldn't find that in my knowledge base"
The bridge to reasoning engines
This post showed how agents reason over a knowledge graph. The next post shows how to add logic on top:
- This post: Agent navigates the graph (structured reasoning)
- Next post: Logic engines verify answers (formal reasoning)
- Post 12: Combine both in production
The takeaway
Agentic knowledge graph engineering is about three things:
- Schema-aware parsing: Agent understands what entities and relationships matter
- Efficient navigation: Agent executes smart queries, not brute-force searches
- Verification: Agent checks its own answers before returning them
Start simple:
- Define your schema (10-15 key entities and relationships)
- Extract into a graph (using LLMs)
- Build an agent that queries it (few-shot learning)
- Verify answers (re-derivation or constraints)
Then iterate. Add relationships as you discover they matter. Update your schema as your domain evolves.
Next in this series: Reasoning Engines: Logic Programming + LLM Inference