Ontology and Schema Design for Knowledge Graphs
A knowledge graph without a schema is just a graph — nodes and edges with no shared meaning. The ontology is what makes it a knowledge graph: the formal definition of what entity types exist, what properties they carry, and what relationship types are valid between them.
Getting the schema right is the most consequential design decision in a knowledge graph project. A good schema compounds in value over time. A poor schema makes every downstream AI application harder to build and trust.
What an Ontology Is
An ontology is a formal specification of:
- ▸Entity types (classes): what kinds of things exist —
Person,Company,Contract,Policy,Product - ▸Properties: what attributes each entity type has — a
Personhasname,role,clearance_level - ▸Relationship types: what connections are valid between entity types — a
Personworks_foraCompany, aContractgoverned_byaPolicy - ▸Constraints: cardinality, domain, range —
works_forconnectsPerson → Company, notContract → Person
flowchart TD
subgraph ONT["Ontology — the schema layer"]
P["Class: Person\n[name, role, clearance_level]"]
C["Class: Company\n[name, industry, regulatory_status]"]
CT["Class: Contract\n[id, value, status, created_at]"]
PL["Class: Policy\n[name, applies_to, required_action]"]
P -- "works_for (1..1 Company)" --> C
CT -- "involves (1..* Company)" --> C
CT -- "governed_by (0..* Policy)" --> PL
P -- "has_authority_over (0..* Contract)" --> CT
endThe ontology is the contract between the knowledge graph and everything that uses it — AI agents, search systems, analytics tools, audit logs.
Two Schema Styles: RDF and Property Graph
There are two main paradigms for knowledge graph schemas, each with different trade-offs:
RDF / OWL
RDF (Resource Description Framework) models everything as triples: subject → predicate → object. OWL (Web Ontology Language) adds formal reasoning capabilities.
:Acme_Corp rdf:type :Company .
:Acme_Corp :industry "regulated" .
:Alice rdf:type :Person .
:Alice :works_for :Acme_Corp .
:Contract_4421 :involves :Acme_Corp .
:Contract_4421 :governed_by :VendorCompliancePolicy .
Best for: linked data, semantic web integration, formal reasoning, cross-organization knowledge sharing.
Property Graph
A property graph allows nodes and edges to carry arbitrary key-value properties directly. More flexible for operational use cases.
flowchart LR A["(Person)\nname: Alice\nrole: procurement\nclearance: L2"] -- "works_for\nsince: 2023-01" --> B["(Company)\nname: Acme Corp\nindustry: regulated\nstatus: active"] B -- "involved_in\nvalue: $400k" --> C["(Contract)\nid: C-4421\nstatus: pending"] C -- "governed_by\nmandatory: true" --> D["(Policy)\nname: VendorCompliancePolicy"]
Best for: operational AI applications, agent memory, enterprise knowledge graphs, GraphRAG systems.
| Dimension | RDF / OWL | Property Graph |
|---|---|---|
| Reasoning | Formal, automated | Manual via queries |
| Flexibility | High (open world) | High (arbitrary properties) |
| Query language | SPARQL | Cypher / Gremlin / GQL |
| Performance | Slower for large traversals | Fast for pattern matching |
| Ecosystem | Semantic web, academia | Enterprise databases, AI platforms |
Designing Entity Types
The most important design decision is what counts as an entity vs what is a property.
Rule of thumb: if something has relationships of its own, it is an entity. If it is just a fact about something else, it is a property.
flowchart TD
subgraph WRONG["❌ Address as property only"]
P1["Person: Alice\naddress: '42 Main St, London'"]
P2["Company: Acme Corp\naddress: '42 Main St, London'"]
P1 -.->|no link — same address not discoverable| P2
end
subgraph RIGHT["✅ Address as entity"]
P3["Person: Alice"] -- lives_at --> ADDR["Address: 42 Main St, London"]
P4["Company: Acme Corp"] -- registered_at --> ADDR
ADDR -.->|now queryable: who shares this address?| ADDR
endAddress-as-entity makes fraud detection, compliance, and customer 360 queries trivially expressible. Address-as-property makes them impossible without full-text scan.
Relationship Types: The Most Important Design Decisions
Relationship types carry the semantics of the graph. Generic relationship names like related_to or connected_with destroy the value of the schema — they tell the AI and the query system nothing.
flowchart TD
subgraph BAD["❌ Generic relationships"]
A1[Person] -- related_to --> B1[Company]
A1 -- related_to --> C1[Contract]
B1 -- related_to --> C1
end
subgraph GOOD["✅ Typed relationships"]
A2[Person] -- works_for --> B2[Company]
A2 -- has_approval_authority_over --> C2[Contract]
B2 -- is_vendor_on --> C2
B2 -- subject_to --> D2[Policy]
endEach typed relationship answers a specific business question directly. has_approval_authority_over tells an AI agent exactly who can approve a contract. related_to tells it nothing.
[Definition] Relationship typing is the single biggest driver of knowledge graph quality for AI applications. A GraphRAG system traverses relationships by type to answer questions — if the types are vague, the traversal cannot be constrained and returns noise alongside signal.
Entity Resolution: One Entity, One Node
The most common and most damaging knowledge graph quality problem is duplicate entities — the same real-world entity represented as multiple nodes because it comes from different source systems with different IDs.
flowchart TD
subgraph BEFORE["Before entity resolution"]
CRM["CRM: Acme Corp\ncustomer_id: 4421"]
ERP["ERP: ACME CORPORATION\naccount: C-99-4421"]
SUPP["Support: Acme corp.\nref: cust_4421_en"]
RISK["Risk: Acme Corp Ltd\nregistry: 0291847"]
end
subgraph AFTER["After entity resolution"]
CANON["Canonical: Acme Corp\n[id: company_0291847]"]
CANON --> CRM2[CRM record]
CANON --> ERP2[ERP account]
CANON --> SUPP2[Support history]
CANON --> RISK2[Risk profile]
endWithout entity resolution, a question like "show me all compliance issues for Acme Corp" returns results for only one source system. With resolution, it returns the full picture across all four.
Entity resolution strategies:
- ▸Deterministic: match on canonical identifiers (company registry number, ISIN, ISBN)
- ▸Rule-based: if name is similar AND postcode matches → same entity
- ▸ML-based: train a classifier on known matches across systems
Schema Evolution: Planning for Change
Knowledge graph schemas evolve as the business and the AI applications built on them evolve. Design for change from the start:
flowchart LR V1["Schema v1\nPerson, Company, Contract"] --> V2["Schema v2\n+ Policy, Regulation\n(additive — no breakage)"] V2 --> V3["Schema v3\nContract.value split\ninto Contract.base_value + Contract.total_value\n(careful migration required)"]
Additive changes (new entity types, new relationship types, new optional properties) are safe — existing queries still work.
Breaking changes (renaming types, changing relationship direction, splitting properties) require coordinated migration with all downstream consumers.
[Key Insight] Design your initial schema to be intentionally minimal. It is far easier to add entity types and relationships later than to rename or restructure them once AI applications depend on them. Start with the 5–8 entity types most central to your highest-value use case.
A Practical Schema Starter for Enterprise AI
For most enterprise AI applications, this 6-entity starter schema covers the majority of grounding and agent memory needs:
flowchart TD USER["User\n[id, name, role, permissions]"] ENTITY["BusinessEntity\n[id, name, type, status]"] DOC["Document\n[id, title, type, created_at]"] EVENT["Event\n[id, type, timestamp, severity]"] POLICY["Policy\n[id, name, applies_to, action_required]"] TASK["Task\n[id, description, status, assigned_to]"] USER -- has_authority_over --> TASK USER -- created --> DOC ENTITY -- subject_to --> POLICY ENTITY -- involved_in --> EVENT DOC -- governs --> ENTITY TASK -- references --> ENTITY EVENT -- triggers --> POLICY
This schema supports: agent memory (Tasks), business grounding (BusinessEntity + Policy), document retrieval (Document), compliance reasoning (Event + Policy), and permission checks (User) — all with typed relationships that GraphRAG can traverse meaningfully.