KG Builder
A knowledge graph is a product with a schema, not a pile of triples. Quality comes from pipeline order: model the domain before extracting, validate during extraction, fuse before storing, and attach provenance to every fact from the first write.
This skill covers the full build — value test, ontology, extraction, quality gate, entity resolution, serving, and maintenance — plus the boundary question that decides whether the result is trustworthy: which stages are deterministic code and which are LLM judgment.
Scope note. This is about knowledge graphs — what an agent remembers. It is not about task graphs, agent orchestration, or multi-agent topology.
Reference Files
| File |
Contents |
Load When |
references/ontology-design.md |
Competency questions, entity/relation types, domain/range, storage choice |
Phase 1 |
references/extraction.md |
Source routing, NER/RE/EE prompt patterns, validation, failure modes |
Phase 2 |
references/fusion.md |
Blocking, matching layers, merge policy, threshold bands |
Phase 3 |
references/serving.md |
GraphRAG retrieval, path queries, community summaries, query layer |
Phase 4 |
references/provenance-and-supersession.md |
Claim model, append-only updates, contradiction handling, audit trail |
Phase 1 and Phase 4 |
The deterministic / LLM boundary
Decide this before writing code. Code owns control flow, identity, validation, and merges. The model gets contained judgments behind a typed interface, each with a measured baseline.
| Stage |
Deterministic (code) |
LLM judgment (measure it) |
| Source routing |
format detection, structured mapping |
— |
| Entity extraction |
span capture, type validation, dictionary matching |
"what entities are in this text" |
| Relation extraction |
domain/range enforcement, endpoint checks |
"which relation does this sentence assert" |
| Quality gate |
sampling, scoring, thresholds |
— |
| Blocking |
key generation, candidate pairing |
— |
| Matching |
string/attribute/structure scoring |
ambiguous middle band only |
| Merge |
canonical selection, edge union, lineage |
— (never let a model own a merge) |
| Serving |
traversal, subgraph selection, serialization |
the agent's own reasoning |
Measure every LLM surface against a prompt-only baseline before trusting it. This is not theoretical caution. In a pre-registered real-model evaluation of an LLM-adjudicated dedup and contradiction loop, the loop trailed a plain prompt-only baseline by 0.28–0.33 on detection and safety across every provider cell tested. Adjudication that is not measured is decoration.
Workflow
Phase 1 — Design (do not skip)
- Value test. A graph pays off when queries are multi-hop ("who worked with X on projects
using Y"), when entities recur across documents, or when the relationships are the data. If every query is a single-hop lookup or an aggregation, use a table and stop here. Write the kill criterion down before continuing.
- Competency questions. Write the 10–20 questions the graph must answer. These are the
ontology's spec and its test suite. Anything you cannot path through the finished schema is a missing type or relation.
- Ontology. 5–15 entity types, 10–30 relation types, each relation with explicit domain
and range. Precise verb names (ACQUIRED, DEPENDSON) — never RELATEDTO. Keep it in ontology.yaml as the single source of truth; every extraction prompt embeds it verbatim.
- Storage and identity. Choose property graph (default), RDF/OWL (interop, description-logic
reasoning), or typed edges in SQLite (<50K nodes). Decide now how time and provenance attach to every fact — retrofitting provenance after fusion is effectively impossible.
Validate the schema before extracting anything:
uv run scripts/validate_ontology.py ontology.yaml
Phase 2 — Extract
- Route by source type. Structured sources (databases, CSVs, APIs) map column → type in
deterministic code with no model involved. Semi-structured sources (HTML tables, infoboxes) get per-layout parsers. Only unstructured text enters the LLM pipeline. Running NLP over already-structured data is the classic waste.
- Entities. Dictionaries and exact rules first for closed vocabularies — free,
deterministic, perfect precision. LLM extraction for open text, with the ontology in the prompt. Always capture surface form, canonical guess, type, source pointer, and confidence.
- Relations. Extract only between entities that already passed step 6; never let relation
extraction invent endpoints. Constrain output to the ontology's relation list and validate domain/range in code. Require a verbatim evidence quote that asserts the relation — co-occurrence is not assertion ("Musk discussed Twitter" is not OWNS).
- Events. For dynamic domains, extract events as first-class nodes (trigger + typed
arguments + time), never flattened into pairwise edges — flattening loses which acquisition happened at which price.
Phase 3 — Consolidate
- Quality gate. Sample 50 items and score entity precision and relation precision before
fusing anything. Target ≥90% precision. Fix the prompt or the rules, then re-run — never hand-patch the output. Recall improves with more passes; bad precision poisons the graph permanently.
- Fusion. Blocking → matching → merge. Blocking avoids O(n²) comparison; matching scores
string, attribute, and structural evidence (two J. Smith nodes sharing three coauthors and an affiliation are one person; identical names with disjoint neighborhoods are not); merge policy is deterministic code that keeps the canonical name, unions aliases and edges, preserves conflicting values with provenance, and records merged_from for undo. An erroneous merge is far more damaging than a missed one — it silently fuses two entities' entire edge sets. Auto-merge only above the high band; queue the middle for review.
Check the blocking strategy against labeled pairs before running it at scale:
uv run scripts/blocking_report.py candidates.jsonl --labels matches.jsonl
Phase 4 — Serve and maintain
- Serving. Entity-link the query, expand 1–2 hops, serialize the subgraph as compact
(head)-[REL {time, source}]->(tail) lines grouped by head. For multi-hop questions retrieve paths between the query's entities, not neighborhoods around each — the path is the answer skeleton. Cluster and pre-summarize for "what are the themes" questions.
- Maintenance. New facts supersede rather than overwrite: keep the prior claim with
status, supersedes, and validity interval. When a new fact contradicts a stored one, keep both with time and provenance and prefer the newer at retrieval. Re-run fusion periodically — unmaintained memory graphs rot exactly like unfused extractions.
Output
Deliver these artifacts, in this order:
| Artifact |
Contents |
competency.md |
The 10–20 questions, each marked answerable or blocked |
ontology.yaml |
Entity types, relation types with domain/range, event argument schemas |
extraction/ |
Per-source-type prompts and deterministic mappings |
quality-report.md |
Sampled entity and relation precision, with sample size and method |
fusion-report.md |
Blocking reduction ratio, pair recall, merge counts per band |
| The graph |
Nodes and edges, every one carrying source, extracted_at, confidence |
Report precision as a sampled estimate with its sample size. A precision number without a stated sampling method is a vibe.
Working rules
- Schema first, always. Extraction without an ontology produces a word cloud with arrows.
If the user resists schema design, induce a minimal 5-type ontology from three sample documents and show it for approval — never skip to extraction.
- Provenance on every fact.
source, extracted_at, confidence. Non-negotiable; fusion and
trust both depend on it.
- Pilot before scale. Run 10 documents through all four phases first. The pilot exposes
ontology gaps at 1% of the cost.
- Never auto-accept an induced schema. LLM-proposed ontologies overfit their sample
documents. Prune to the minimal set that answers the competency questions.
- The LLM is stage machinery, not the pipeline. It slots into extraction and the ambiguous
matching band. The surrounding schema, validation, and merge policy are what make the output a knowledge graph rather than a transcript.
Errors and troubleshooting
| Symptom |
Cause |
Fix |
Graph full of Concept/Thing nodes |
Extracted without an ontology |
Phase 1 first, then re-extract |
| Same person appears as four nodes |
No canonical-form rule; fusion skipped |
Define the rule in ontology.yaml; run Phase 3 |
| Confident but wrong relations |
Co-occurrence treated as assertion |
Require evidence quotes; enforce domain/range in code |
| Events flattened into edge soup |
No event argument schema |
Promote events to first-class nodes with typed arguments |
| Precision collapses as sources grow |
One prompt drifting across document types |
Per-source-type prompts; run the quality gate per source |
| Fusion merges two real entities |
Threshold too low; no structural layer |
Raise the auto-merge band; add neighborhood comparison; undo via merged_from |
| Multi-hop answers are wrong but fluent |
Unfused duplicates break paths |
Re-run fusion; paths cannot cross duplicate boundaries |
| GraphRAG returns noise |
Hop expansion too wide |
Cap at 2 hops, or re-rank; retrieve paths, not neighborhoods |
| Retrieval is stale after updates |
Facts overwritten instead of superseded |
Adopt the claim model in references/provenance-and-supersession.md |