| name | devkg-sparql |
| description | Query the Dev Knowledge Graph via SPARQL instead of grepping raw session files. Use this when asked about technologies, relationships between tools, session history, where a topic was discussed, or cross-platform knowledge. Prefer provenance-first SPARQL (message + session) over label-only CONTAINS or grep. |
| user-invocable | true |
| allowed-tools | ["Bash(curl:*)","Bash(jq:*)"] |
DevKG SPARQL Query Skill
CRITICAL CONTEXT-SAFETY RULE
NEVER READ LARGE SPARQL RESULTS, SESSION FILES, JSONL, LOGS, OR GENERATED ARTIFACTS ALL AT ONCE. Always add LIMIT, select only needed variables, inspect counts first, and summarize. Never dump huge result sets, ID lists, raw JSON, or transcript content into chat.
Query the developer knowledge graph at http://localhost:3030/devkg/sparql via SPARQL. This graph contains extracted knowledge triples, entities, Wikidata links, and session metadata from Claude Code, pi, Codex, Cursor, ChatGPT, DeepSeek, Grok, and Warp sessions.
Content limit: sioc:content on messages is capped at ~2000 characters at ingest. SPARQL is enough to locate sessions and reason lightly from triples + snippets. For full quotes or deep thread reconstruction, normalize hasSourceFile and read the JSONL only when needed.
Retrieval Strategy (read this first)
| User intent | Do this first | Do NOT start with |
|---|
| "Where / which sessions discussed X?" | Template 5 (topic + intent + provenance) | Label-only Template 6, or grep |
| "What do we know about technology X?" | Template 1 (entity + provenance) | Grep |
| "How does X relate to Y?" | Template 2 | Grep |
| "Find the exact message wording" | Template 5 or 9 → then JSONL only if snippet truncated | Grepping all projects |
Default for session-discovery questions: multi-signal filter (topic and intent terms) on both triple labels and sioc:content, always joining extractedFrom / extractedInSession, ordered by DESC(?created), with LIMIT.
When Fuseki returns provenance hits, do not fall back to grep. Grep only if Fuseki is down or returns 0 rows after a provenance query.
Execution Pattern
Always use this pattern (POST, URL-encoded query, JSON output). Include Fuseki auth when required:
curl -s -X POST 'http://localhost:3030/devkg/sparql' \
-u admin:admin \
-H 'Accept: application/sparql-results+json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "query=YOUR_SPARQL_HERE" \
| jq -r '.results.bindings[] | [.var1.value, .var2.value] | @tsv'
Adjust the jq expression to match your SELECT variables. Use @tsv for compact tabular output. Always LIMIT results.
For multi-line queries, use double quotes around the --data-urlencode value and escape inner quotes:
curl -s -X POST 'http://localhost:3030/devkg/sparql' \
-u admin:admin \
-H 'Accept: application/sparql-results+json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "query=PREFIX devkg: <http://devkg.local/ontology#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT DISTINCT ?label WHERE {
?e a devkg:Entity ; rdfs:label ?label .
FILTER(LANG(?label) = \"\")
} LIMIT 10" \
| jq -r '.results.bindings[] | .label.value'
Fallback Rule
If Fuseki is unreachable (curl fails or times out) or a provenance query (Template 5/8) returns 0 results, then fall back to grep-based session search:
grep -rli "keyword" ~/.claude/projects ~/.pi/agent/sessions ~/.cursor/projects 2>/dev/null | head -20
Then read matching JSONL files with bounded Python extraction. Only use this as a last resort.
Resolving hasSourceFile to Disk (and Pruned Sources)
hasSourceFile is NOT always a real absolute path. Normalize before any Read:
hasSourceFile prefix | Real on-disk location |
|---|
/Users/... | absolute path — use as-is |
/claude-sessions/<munged>/<file> | ~/.claude/projects/<munged>/<file> |
/pi-sessions/<munged>/<file> | ~/.pi/agent/sessions/<munged>/<file> |
/codex-sessions/<path> | ~/.codex/sessions/<path> |
/cursor-sessions/projects/<slug>/... | ~/.cursor/projects/<slug>/... |
resolve_session_path() {
local sf="$1" p=""
case "$sf" in
/Users/*) p="$sf" ;;
/claude-sessions/*) p="$HOME/.claude/projects/${sf#/claude-sessions/}" ;;
/pi-sessions/*) p="$HOME/.pi/agent/sessions/${sf#/pi-sessions/}" ;;
/codex-sessions/*) p="$HOME/.codex/sessions/${sf#/codex-sessions/}" ;;
/cursor-sessions/projects/*) p="$HOME/.cursor/projects/${sf#/cursor-sessions/projects/}" ;;
*) p="$sf" ;;
esac
local stem="${p%.jsonl}"
if [ -f "$p" ]; then echo "FILE:$p"; return; fi
if [ -f "$stem" ]; then echo "FILE:$stem"; return; fi
if [ -d "$stem/subagents" ]; then ; ;
[ -d ]; ; ;
}
If the path is PRUNED, do NOT grep the filesystem. Re-query KnowledgeTriples for that session via extractedInSession and reconstruct from labels + any remaining sioc:content.
Result Formatting
Present SPARQL results as markdown tables. Never dump raw JSON to the user.
Prefixes (copy into every query)
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX prov: <http://www.w3.org/ns/prov#>
PREFIX sioc: <http://rdfs.org/sioc/ns#>
PREFIX skos: <http://www.w3.org/2004/02/skos/core#>
PREFIX dcterms: <http://purl.org/dc/terms/>
PREFIX devkg: <http://devkg.local/ontology#>
PREFIX data: <http://devkg.local/data/>
PREFIX wd: <http://www.wikidata.org/entity/>
Ontology Cheat Sheet
Classes
| Class | Parent | Description |
|---|
devkg:Session | prov:Activity, sioc:Forum | A working session (conversation) |
devkg:Message | sioc:Post, prov:Entity | A message in a session |
devkg:UserMessage | devkg:Message | Human message |
devkg:AssistantMessage | devkg:Message | AI message |
devkg:ToolCall | prov:Activity | Legacy tool invocation nodes (may be absent in new ingests — do not rely on them) |
devkg:ToolResult | prov:Entity | Legacy tool output (may be absent in new ingests) |
devkg:CodeArtifact | prov:Entity, schema:SoftwareSourceCode | Code file/snippet |
devkg:Entity | prov:Entity | Extracted technical concept |
devkg:KnowledgeTriple | — | Reified triple (subject→predicate→object) with provenance |
devkg:Project | prov:Entity | A development project |
devkg:Developer | prov:Agent | Human developer |
devkg:AIModel | prov:Agent | AI model (Claude, GPT, etc.) |
devkg:Topic | skos:Concept | Knowledge topic |
Structural Predicates (Session/Message graph)
| Predicate | Domain → Range | Notes |
|---|
devkg:usedInSession | Message/ToolCall → Session | Links content to its session |
devkg:hasParentMessage | Message → Message | Thread structure |
devkg:mentionsTopic | Message → Topic | Topic tagging |
devkg:invokedTool | AssistantMessage → ToolCall | Tool usage |
devkg:hasToolResult | ToolCall → ToolResult | Tool output |
devkg:producedArtifact | Activity → CodeArtifact | Code generation |
devkg:belongsToProject | Session → Project | Project membership |
devkg:extractedFrom | KnowledgeTriple → Message | Triple provenance |
devkg:extractedInSession | KnowledgeTriple → Session | Triple provenance |
devkg:tripleSubject | KnowledgeTriple → Entity | Reified subject |
devkg:tripleObject | KnowledgeTriple → Entity | Reified object |
devkg:triplePredicateLabel | KnowledgeTriple → xsd:string | Predicate name |
Key Datatype Properties
| Property | On | Value |
|---|
sioc:content | Message | Message text (truncated ~2000 chars at ingest) |
rdfs:label | Entity/Session/Project | Display name |
dcterms:created | Session/Message | ISO datetime |
devkg:hasSourcePlatform | Session | claude-code, pi-coding-agent, codex, cursor, chatgpt, deepseek, grok, warp |
devkg:hasSourceFile | Session | Logical path to raw source — normalize before Read (see "Resolving hasSourceFile to Disk") |
devkg:hasToolName | ToolCall | Legacy — prefer KnowledgeTriples + message content for discovery |
devkg:hasWorkingDirectory | Session | Project directory path |
owl:sameAs | Entity | Wikidata URI (e.g., wd:Q28865) |
Knowledge Predicates (24 total)
These connect devkg:Entity to devkg:Entity via direct edges AND are stored as devkg:triplePredicateLabel strings on reified devkg:KnowledgeTriple nodes:
uses, dependsOn, enables, isPartOf, hasPart, implements, extends, alternativeTo, solves, produces, configures, composesWith, provides, requires, isTypeOf, builtWith, deployedOn, storesIn, queriedWith, integratesWith, broader, narrower, relatedTo, servesAs
Query Templates
1. Entity Lookup — "What do we know about X?"
Returns all relationships (outbound + inbound) for an entity, with source file and content snippet for provenance. Use CONTAINS for fuzzy matching.
SELECT DISTINCT ?direction ?predicate ?otherLabel ?sourceFile ?platform
(SUBSTR(?content, 1, 150) AS ?snippet) WHERE {
{
?triple a devkg:KnowledgeTriple ;
devkg:tripleSubject ?s ;
devkg:triplePredicateLabel ?predicate ;
devkg:tripleObject ?o ;
devkg:extractedFrom ?msg ;
devkg:extractedInSession ?session .
?s rdfs:label ?sLabel .
?o rdfs:label ?otherLabel .
FILTER(CONTAINS(LCASE(STR(?sLabel)), "ENTITY_LOWER"))
BIND("outbound" AS ?direction)
} UNION {
?triple a devkg:KnowledgeTriple ;
devkg:tripleSubject ?o ;
devkg:triplePredicateLabel ?predicate ;
devkg:tripleObject ?obj ;
devkg:extractedFrom ?msg ;
devkg:extractedInSession ?session .
?obj rdfs:label ?oLabel .
?o rdfs:label ?otherLabel .
FILTER(CONTAINS(LCASE(STR(?oLabel)), "ENTITY_LOWER"))
BIND("inbound" AS ?direction)
}
OPTIONAL { ?session devkg:hasSourceFile ?sourceFile }
OPTIONAL { ?session devkg:hasSourcePlatform ?platform }
OPTIONAL { ?msg sioc:content ?content }
}
ORDER BY ?direction ?predicate
Replace ENTITY_LOWER with the lowercase entity name (e.g., neo4j, opentelemetry).
The sourceFile column is a logical path to the original JSONL/JSON file — normalize it with resolve_session_path (see "Resolving hasSourceFile to Disk") before Read; if it resolves to PRUNED, reconstruct from the triples instead.
2. Entity-to-Entity — "How does X relate to Y?"
SELECT DISTINCT ?predicate ?sourceSnippet WHERE {
?triple a devkg:KnowledgeTriple ;
devkg:tripleSubject ?s ;
devkg:triplePredicateLabel ?predicate ;
devkg:tripleObject ?o ;
devkg:extractedFrom ?msg .
?s rdfs:label ?sLabel .
?o rdfs:label ?oLabel .
OPTIONAL { ?msg sioc:content ?c . BIND(SUBSTR(?c, 1, 150) AS ?sourceSnippet) }
FILTER(
CONTAINS(LCASE(STR(?sLabel)), "ENTITY_X") &&
CONTAINS(LCASE(STR(?oLabel)), "ENTITY_Y")
)
}
3. Predicate Search — "What uses/enables/solves X?"
SELECT DISTINCT ?subjectLabel ?objectLabel WHERE {
?triple a devkg:KnowledgeTriple ;
devkg:tripleSubject ?s ;
devkg:triplePredicateLabel "PREDICATE" ;
devkg:tripleObject ?o .
?s rdfs:label ?subjectLabel .
?o rdfs:label ?objectLabel .
FILTER(CONTAINS(LCASE(STR(?subjectLabel)), "ENTITY_LOWER")
|| CONTAINS(LCASE(STR(?objectLabel)), "ENTITY_LOWER"))
}
Replace PREDICATE with one of the 24 predicates (e.g., uses, integratesWith).
4. Session Listing — "What sessions exist?"
SELECT ?session ?platform ?created ?title WHERE {
?session a devkg:Session .
OPTIONAL { ?session devkg:hasSourcePlatform ?platform }
OPTIONAL { ?session dcterms:created ?created }
OPTIONAL { ?session dcterms:title ?title }
}
ORDER BY DESC(?created)
LIMIT 50
5. Topic + Intent → Sessions (PRIMARY for "where did we discuss X?")
Use this first for session discovery, career/product/person questions, or "exact piece of a session."
Do not start with label-only Template 6.
Replace TOPIC_LOWER (required) and add intent terms in the second FILTER (at least one).
Example: topic=linkedin, intent=profile|career|roberto|headline|authority.
SELECT DISTINCT ?created ?platform ?sourceFile ?subj ?pred ?obj
(SUBSTR(REPLACE(STR(?content), "\n", " "), 1, 200) AS ?snippet)
WHERE {
{
# Path A: KnowledgeTriple labels match topic + intent
?kt a devkg:KnowledgeTriple ;
devkg:tripleSubject ?s ;
devkg:tripleObject ?o ;
devkg:triplePredicateLabel ?pred ;
devkg:extractedFrom ?msg ;
devkg:extractedInSession ?sess .
?s rdfs:label ?subj .
?o rdfs:label ?obj .
FILTER(LANG(?subj) = "" && LANG(?obj) = "")
BIND(LCASE(CONCAT(STR(?subj), " ", STR(?obj))) AS ?tripleText)
FILTER(CONTAINS(?tripleText, "TOPIC_LOWER"))
FILTER(
CONTAINS(?tripleText, "INTENT1")
|| CONTAINS(?tripleText, "INTENT2")
|| CONTAINS(?tripleText, "INTENT3")
)
}
UNION
{
# Path B: message text matches topic + intent (catches misses in entity extraction)
?msg a ?msgType ;
sioc:content ?content ;
sioc:has_container ?sess .
FILTER(?msgType IN (devkg:AssistantMessage, devkg:UserMessage))
?kt a devkg:KnowledgeTriple ;
devkg:extractedFrom ?msg ;
devkg:extractedInSession ?sess ;
devkg:tripleSubject ?s ;
devkg:tripleObject ?o ;
devkg:triplePredicateLabel ?pred .
?s rdfs:label ?subj .
?o rdfs:label ?obj .
FILTER(LANG(?subj) = "" && LANG(?obj) = "")
BIND(LCASE(STR(?content)) AS ?msgText)
FILTER(CONTAINS(?msgText, "TOPIC_LOWER"))
FILTER(
CONTAINS(?msgText, "INTENT1")
|| CONTAINS(?msgText, "INTENT2")
|| CONTAINS(?msgText, "INTENT3")
)
}
OPTIONAL { ?msg sioc:content ?content }
OPTIONAL { ?sess devkg:hasSourcePlatform ?platform }
OPTIONAL { ?sess devkg:hasSourceFile ?sourceFile }
OPTIONAL { ?sess dcterms:created ?created }
}
ORDER BY DESC(?created)
LIMIT 40
Present as a session table grouped by sourceFile (date, platform, hit count, 1–2 sample facts/snippets). Reason from triples + snippets when possible; open JSONL only if the user needs full wording beyond the 2000-char cap.
If intent is unknown, keep topic FILTER and drop the intent FILTER (broader recall).
6. Topic Search (label-only) — fallback entity scan
Simpler label scan. Prefer Template 5 when the user asks where or which sessions.
SELECT DISTINCT ?session ?platform ?created ?sourceFile WHERE {
?triple a devkg:KnowledgeTriple ;
devkg:extractedInSession ?session .
{ ?triple devkg:tripleSubject ?e . ?e rdfs:label ?label . }
UNION
{ ?triple devkg:tripleObject ?e . ?e rdfs:label ?label . }
FILTER(CONTAINS(LCASE(STR(?label)), "TOPIC_LOWER"))
OPTIONAL { ?session devkg:hasSourcePlatform ?platform }
OPTIONAL { ?session dcterms:created ?created }
OPTIONAL { ?session devkg:hasSourceFile ?sourceFile }
}
ORDER BY DESC(?created)
LIMIT 30
7. Cross-Platform Overlap — "What entities appear across platforms?"
SELECT ?label (GROUP_CONCAT(DISTINCT ?platform; separator=", ") AS ?platforms)
(COUNT(DISTINCT ?platform) AS ?platformCount) WHERE {
?triple a devkg:KnowledgeTriple ;
devkg:tripleSubject ?e ;
devkg:extractedInSession ?session .
?session devkg:hasSourcePlatform ?platform .
?e rdfs:label ?label .
FILTER(LANG(?label) = "")
}
GROUP BY ?label
HAVING(COUNT(DISTINCT ?platform) > 1)
ORDER BY DESC(?platformCount)
LIMIT 40
8. Wikidata Enrichment — "What is X?"
SELECT ?label ?wikidataURI ?description WHERE {
?entity a devkg:Entity ;
rdfs:label ?label ;
owl:sameAs ?wikidataURI .
FILTER(STRSTARTS(STR(?wikidataURI), "http://www.wikidata.org"))
FILTER(CONTAINS(LCASE(STR(?label)), "ENTITY_LOWER"))
FILTER(LANG(?label) = "")
OPTIONAL { ?entity dcterms:description ?description }
}
LIMIT 20
9. Full-Text Content Search — "Find messages mentioning keyword X"
Searches both user and assistant messages (assistant text holds most extractable knowledge).
SELECT ?platform ?created ?sourceFile
(SUBSTR(REPLACE(STR(?content), "\n", " "), 1, 200) AS ?snippet)
WHERE {
?msg a ?msgType ;
sioc:content ?content ;
sioc:has_container ?session .
FILTER(?msgType IN (devkg:AssistantMessage, devkg:UserMessage))
OPTIONAL { ?session dcterms:created ?created }
OPTIONAL { ?session devkg:hasSourcePlatform ?platform }
OPTIONAL { ?session devkg:hasSourceFile ?sourceFile }
FILTER(CONTAINS(LCASE(?content), "KEYWORD_LOWER"))
}
ORDER BY DESC(?created)
LIMIT 20
10. Session Insight Pack — "Summarize what session S knew"
Given a session URI or sourceFile, return predicate mix + sample provenance facts (no JSONL required for a light summary).
SELECT ?pred (COUNT(?kt) AS ?n) WHERE {
?sess devkg:hasSourceFile ?sourceFile .
FILTER(CONTAINS(STR(?sourceFile), "SESSION_PATH_FRAGMENT"))
?kt a devkg:KnowledgeTriple ;
devkg:extractedInSession ?sess ;
devkg:triplePredicateLabel ?pred .
}
GROUP BY ?pred
ORDER BY DESC(?n)
LIMIT 24
Follow with sample facts:
SELECT ?subj ?pred ?obj
(SUBSTR(REPLACE(STR(?content), "\n", " "), 1, 160) AS ?snippet)
WHERE {
?sess devkg:hasSourceFile ?sourceFile .
FILTER(CONTAINS(STR(?sourceFile), "SESSION_PATH_FRAGMENT"))
?kt a devkg:KnowledgeTriple ;
devkg:extractedInSession ?sess ;
devkg:tripleSubject ?s ;
devkg:tripleObject ?o ;
devkg:triplePredicateLabel ?pred ;
devkg:extractedFrom ?msg .
?s rdfs:label ?subj . ?o rdfs:label ?obj .
FILTER(LANG(?subj) = "" && LANG(?obj) = "")
OPTIONAL { ?msg sioc:content ?content }
}
LIMIT 15
11. 2-Hop Neighborhood — "What connects to X and what connects to those?"
Traverses outbound edges from X, then follows outbound edges from each neighbor. Shows the subgraph reachable in 2 hops.
SELECT DISTINCT ?aLabel ?p1 ?bLabel ?p2 ?cLabel WHERE {
?t1 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?a ;
devkg:triplePredicateLabel ?p1 ;
devkg:tripleObject ?b .
?a rdfs:label ?aLabel .
?b rdfs:label ?bLabel .
FILTER(LANG(?aLabel) = "" && LANG(?bLabel) = "")
FILTER(CONTAINS(LCASE(?aLabel), "ENTITY_LOWER"))
OPTIONAL {
?t2 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?b ;
devkg:triplePredicateLabel ?p2 ;
devkg:tripleObject ?c .
?c rdfs:label ?cLabel .
FILTER(LANG(?cLabel) = "")
}
}
ORDER BY ?bLabel ?cLabel
LIMIT 40
For bidirectional 2-hop (also follows inbound edges), add a second UNION branch that reverses subject/object in each hop.
12. Hub Detection — "What are the most connected entities?"
SELECT ?label (COUNT(DISTINCT ?triple) AS ?degree) WHERE {
{
?triple a devkg:KnowledgeTriple ;
devkg:tripleSubject ?e .
?e rdfs:label ?label .
FILTER(LANG(?label) = "")
} UNION {
?triple a devkg:KnowledgeTriple ;
devkg:tripleObject ?e .
?e rdfs:label ?label .
FILTER(LANG(?label) = "")
}
}
GROUP BY ?label
ORDER BY DESC(?degree)
LIMIT 20
13. Cross-Session Entity Overlap — "What sessions share knowledge?"
SELECT ?s1File ?s2File
(COUNT(DISTINCT ?label) AS ?shared)
(GROUP_CONCAT(DISTINCT ?label; separator=", ") AS ?sharedEntities)
WHERE {
?t1 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?e1 ;
devkg:extractedInSession ?sess1 .
?t2 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?e2 ;
devkg:extractedInSession ?sess2 .
?e1 rdfs:label ?label .
?e2 rdfs:label ?label .
FILTER(LANG(?label) = "")
FILTER(STR(?sess1) < STR(?sess2))
OPTIONAL { ?sess1 devkg:hasSourceFile ?s1File }
OPTIONAL { ?sess2 devkg:hasSourceFile ?s2File }
}
GROUP BY ?s1File ?s2File
HAVING(COUNT(DISTINCT ?label) > 2)
ORDER BY DESC(?shared)
LIMIT 10
14. Path Discovery — "How does X connect to Y?" (via intermediate entities)
SELECT DISTINCT ?p1 ?midLabel ?p2 WHERE {
{
?t1 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?a ;
devkg:triplePredicateLabel ?p1 ;
devkg:tripleObject ?mid .
?t2 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?mid ;
devkg:triplePredicateLabel ?p2 ;
devkg:tripleObject ?b .
} UNION {
?t1 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?mid ;
devkg:triplePredicateLabel ?p1 ;
devkg:tripleObject ?a .
?t2 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?mid ;
devkg:triplePredicateLabel ?p2 ;
devkg:tripleObject ?b .
} UNION {
?t1 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?a ;
devkg:triplePredicateLabel ?p1 ;
devkg:tripleObject ?mid .
?t2 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?b ;
devkg:triplePredicateLabel ?p2 ;
devkg:tripleObject ?mid .
}
?a rdfs:label ?aLabel .
?b rdfs:label ?bLabel .
?mid rdfs:label ?midLabel .
FILTER(LANG(?aLabel) = "" && LANG(?bLabel) = "" && LANG(?midLabel) = "")
FILTER(CONTAINS(LCASE(?aLabel), "ENTITY_X"))
FILTER(CONTAINS(LCASE(?bLabel), "ENTITY_Y"))
FILTER(?a != ?b && ?a != ?mid && ?mid != ?b)
}
LIMIT 20
Present as: ENTITY_X --p1--> intermediate --p2--> ENTITY_Y
15. Project Knowledge Map — "What does project X know about?"
SELECT ?label (COUNT(DISTINCT ?triple) AS ?mentions) WHERE {
?session devkg:belongsToProject ?project .
?project rdfs:label ?projectLabel .
FILTER(CONTAINS(LCASE(?projectLabel), "PROJECT_LOWER"))
?triple a devkg:KnowledgeTriple ;
devkg:extractedInSession ?session .
{ ?triple devkg:tripleSubject ?e . ?e rdfs:label ?label . }
UNION
{ ?triple devkg:tripleObject ?e . ?e rdfs:label ?label . }
FILTER(LANG(?label) = "")
}
GROUP BY ?label
ORDER BY DESC(?mentions)
LIMIT 30
16. Sibling Entities — "What else uses/requires/enables the same thing as X?"
SELECT DISTINCT ?siblingLabel ?predicate ?sharedLabel WHERE {
?t1 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?x ;
devkg:triplePredicateLabel ?predicate ;
devkg:tripleObject ?shared .
?t2 a devkg:KnowledgeTriple ;
devkg:tripleSubject ?sibling ;
devkg:triplePredicateLabel ?predicate ;
devkg:tripleObject ?shared .
?x rdfs:label ?xLabel .
?sibling rdfs:label ?siblingLabel .
?shared rdfs:label ?sharedLabel .
FILTER(LANG(?xLabel) = "" && LANG(?siblingLabel) = "" && LANG(?sharedLabel) = "")
FILTER(CONTAINS(LCASE(?xLabel), "ENTITY_LOWER"))
FILTER(?x != ?sibling)
}
ORDER BY ?predicate ?sharedLabel
LIMIT 40
Wikidata Graph Traversal
Many entities in the local graph have owl:sameAs links to Wikidata QIDs. You can cross into Wikidata's public SPARQL endpoint to discover knowledge that doesn't exist locally — drug classes, software ecosystems, related technologies, disambiguation, etc.
Wikidata endpoint: https://query.wikidata.org/sparql
Execution pattern (same as local, but different URL + requires User-Agent header):
curl -s -X POST 'https://query.wikidata.org/sparql' \
-H 'Accept: application/sparql-results+json' \
-H 'User-Agent: DevKG/1.0' \
--data-urlencode "query=YOUR_SPARQL_HERE" \
| jq -r '...'
Rate limits: Wikidata allows ~60 requests/minute for anonymous users. Add 1-second delay between queries if doing batch lookups.
Workflow: Local → Wikidata → Back to Local
- Start local: Use Template 1 to find what you know about entity X
- Get QID: Use Template 8 to retrieve the
owl:sameAs Wikidata URI
- Cross to Wikidata: Use the QID in Wikidata templates below to discover new knowledge
- Come back: Use what you learned to ask better local queries (e.g., discovered a peer → check if it exists locally)
W1. Entity Properties — "What does Wikidata know about QID?"
Returns all direct properties with human-readable labels. Use this first to understand what's available.
SELECT ?propLabel ?valLabel WHERE {
wd:QID ?p ?val .
?prop wikibase:directClaim ?p .
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
} LIMIT 30
Key properties to look for:
instance of (P31) — what kind of thing it is
subclass of (P279) — broader category
has use (P366) — what it's used for
programmed in (P277) — implementation language (software)
uses (P2283) — technologies it depends on
part of (P361) — larger system it belongs to
ATC code (P267) — drug classification (medications)
route of administration (P636) — how a drug is taken
W2. Peer Discovery — "What else is the same kind of thing as X?"
Given a QID, finds its instance of class, then finds all other instances of that class. Discovers alternatives and competitors.
SELECT ?peerLabel ?peerDescription WHERE {
wd:QID wdt:P31 ?class .
?peer wdt:P31 ?class .
FILTER(?peer != wd:QID)
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
} LIMIT 20
Examples:
- Neo4j (Q1628290) →
instance of: graph database management system → finds ArangoDB, JanusGraph, Amazon Neptune, Dgraph, etc.
- Fosfomycin (Q183554) →
instance of: type of chemical entity → (too broad, use P2868 "subject has role" or ATC code instead)
W3. Disambiguation — "Is this the right entity?"
When an entity label is ambiguous, fetch the Wikidata description to verify. Use this before trusting an owl:sameAs link.
SELECT ?label ?description WHERE {
wd:QID rdfs:label ?label .
wd:QID schema:description ?description .
FILTER(LANG(?label) = "en")
FILTER(LANG(?description) = "en")
}
W4. Broader Categories — "What category tree does X belong to?"
Traverses subclass of (P279) upward to find the classification hierarchy.
SELECT ?classLabel ?superClassLabel WHERE {
wd:QID wdt:P31 ?class .
?class wdt:P279* ?superClass .
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
} LIMIT 20
W5. Relationship Bridge — "How do two entities connect in Wikidata?"
When two local entities have Wikidata links but no direct local connection, check if Wikidata knows a relationship.
SELECT ?propLabel WHERE {
wd:QID_X ?p wd:QID_Y .
?prop wikibase:directClaim ?p .
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
}
If no direct link, try 2-hop:
SELECT ?propLabel1 ?midLabel ?propLabel2 WHERE {
wd:QID_X ?p1 ?mid .
?mid ?p2 wd:QID_Y .
?prop1 wikibase:directClaim ?p1 .
?prop2 wikibase:directClaim ?p2 .
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
} LIMIT 10
W6. Batch QID Lookup — "Enrich all linked entities at once"
First get all QIDs from the local graph, then query Wikidata for their classes in one request.
Step 1 (local): Extract QIDs
SELECT ?label (REPLACE(STR(?wikidata), "http://www.wikidata.org/entity/", "") AS ?qid) WHERE {
?e a devkg:Entity ; rdfs:label ?label ; owl:sameAs ?wikidata .
FILTER(LANG(?label) = "")
FILTER(STRSTARTS(STR(?wikidata), "http://www.wikidata.org"))
}
Step 2 (Wikidata): Get classes for multiple QIDs at once (use VALUES clause):
SELECT ?item ?itemLabel ?classLabel WHERE {
VALUES ?item { wd:Q1628290 wd:Q183554 wd:Q28865 }
?item wdt:P31 ?class .
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
}
When to Use Wikidata Traversal
| Question | Local enough? | Use Wikidata? |
|---|
| "What does X integrate with?" | Yes (Template 1) | No |
| "What kind of thing is X?" | Maybe (if isTypeOf exists) | Yes (W1, W4) |
| "What are alternatives to X?" | Maybe (if alternativeTo exists) | Yes (W2) |
| "Is this the right entity?" | No | Yes (W3) |
| "How does X relate to Y globally?" | No | Yes (W5) |
| "What drug class is X in?" | No | Yes (W1 → ATC code, P2868) |
| "What language is X written in?" | Maybe | Yes (W1 → P277) |
Tips
- Session discovery ("where / which sessions"): always start with Template 5 (topic + intent + provenance). Do not start with label-only Template 6 or grep.
- Always use
DISTINCT — duplicate triples exist from lang-tagged vs untagged literals.
- Always use
FILTER(LANG(?label) = "") to avoid duplicate rows from lang-tagged literals.
- Entity labels are lowercase in the graph. Always use
LCASE() in FILTER for safety.
- Multi-signal filters beat single keywords: topic (
linkedin) and intent (profile, career, roberto).
- For "What integrates with X?" questions, use Template 1 (bidirectional) — the relationship may be stored in either direction.
KnowledgeTriple nodes carry provenance: extractedFrom → source message, extractedInSession → session. Always project these when the user needs where.
sioc:content is capped at ~2000 chars — enough to locate and lightly reason; open JSONL only for full fidelity.
- When following
hasSourceFile, normalize the path first. If PRUNED, reconstruct from triples — do not grep.
- If Fuseki returned provenance hits, do not fall back to grep.
- Combine templates: e.g., Template 5 → Template 10 (session insight) → Template 8 (Wikidata) as needed.
- Start with Template 12 (hubs) when exploring an unfamiliar graph.
- Use Template 14 (path discovery) before concluding two concepts are unrelated.
- Use Template 16 (siblings) to discover alternatives and peers.
- Prefer relationship predicates (
uses, dependsOn, solves, …) over treating the graph as a tag cloud of labels.