01 · The ideaNothing stores how your data connects

Ask a question inside one system, like "what's on order 8812?", and you get an answer in seconds. Ask one that crosses systems, like "this supplier just slipped a week, which customers do I need to call?", and someone loses an afternoon opening four tools and rebuilding the chain from memory.

That second question is why ontologies exist. Your systems record things: parts, orders, shipments. Almost none record how those things connect, so the connections live in the heads of the five people who've been there a decade, and nowhere a machine can read. An ontology is where you write them down.

Forget Gruber's textbook definition, "an explicit specification of a conceptualization". Here's a concrete one instead. Meet Meridian, a mid-size maker of industrial equipment; this is its ontology:

supplies used_in contains placed_by subpart_of Supplier Part Product Order Customer
Supplier Part Product Order Customer

Boring is the point. Everyone in the company now means the same thing by "part" and "order", and a machine can follow those arrows without guessing.

That's the whole idea. The rest of this post is what goes inside one, how it relates to a knowledge graph, where it pays for itself, how to build one with an LLM, and how to actually use it, which is the part most write-ups skip.

02 · The obvious objectionIt's not just a fancier ER diagram

If you've ever designed a database, that picture looks familiar: boxes for entities, labelled lines for relationships, a note about cardinality. That's an ER diagram, drawn since Peter Chen's 1976 paper.

The resemblance isn't a coincidence: both came out of the same 1970s push to model meaning rather than storage. "Entity type / relationship type / cardinality" and "class / object property / cardinality restriction" are the same three ideas in different vocabulary. And honestly, plenty of shipped enterprise ontologies really are ER diagrams living in a graph database, with the logic thin, and they still pay off.

But three differences are genuine, and they matter later in this post:

What an ontology has that an ER diagram doesn't

1 · Domain, not system. An ER diagram in practice describes one system's storage: its tables, its keys. An ontology describes the business domain across every system, independent of any database. Prescriptive for one app's tables, descriptive of what's true anywhere. Changing an ER schema is a migration; extending an ontology is usually additive.

2 · Axioms infer; constraints reject. "Every Part has exactly one primary Supplier" as a UNIQUE foreign key rejects a second one as a database constraint. But as an axiom, the same sentence makes the reasoner conclude the two names denote the same supplier. Same English, opposite behaviour.

3 · There's a reasoner. Transitivity, subclass inheritance, property chains: an ER diagram has no runtime, but an ontology computes them. The §07 exposure walk is a recursive query you'd hand-write per question in SQL; here it falls out of one axiom.

The one-line version: an ontology is an ER diagram plus a reasoner. Strip that away and you have an ER diagram, which, again, is a perfectly respectable thing to ship.

03 · The anatomyWhat's actually in ontology, and what it's written in

So if you were handed an ontology file tomorrow, what would be in it? Six things. The first two are the ones everybody draws; the last four are what separate a real ontology from a picture.

1ClassesThe kinds of things that exist: Supplier, Part, Product, Order, Customer. Concepts that span every system you own, not rows in one table. Usually arranged in a subclass hierarchy: a CastingPart is a Part.
2RelationshipsNamed, directed links between classes: supplies, used_in, placed_by. Each declares a domain (what it goes from) and a range (what it goes to). This is where an ontology earns its keep; a class list alone is a glossary.
3AttributesThe plain values a thing carries: Part.lead_time_days, Order.value, Customer.tier. Called data properties, with a datatype and often an allowed range.
4Axioms & constraintsThe rules: "every Part has at least one Supplier", "subpart_of is transitive", "a Part is never a Product". Two flavours worth separating: axioms that let a reasoner derive new facts, and shapes that validate incoming data and reject what doesn't fit.
5IdentifiersEvery class, relationship and instance gets a globally unique ID: an IRI, which just looks like a URL. Boring but load-bearing: it's what lets two departments (or two companies) merge models without name collisions.
6Mappings & metadataHow each class ties back to real source data (this table, that API field), plus who owns it, when it changed, and which version this is.

And how it's written down

The most widely used way to write one in practice is the vendor object model, and Palantir's is the reference implementation:

Palantir's object model
object type Part
  key sku · from erp.parts_tbl
link type   usedIn
  Part → Product
action type notifyCustomer
  applies to Order
// "tell them it'll be late"

Three kinds of types: objects (things that exist, like Part), links (relationships like usedIn), and actions (verbs like notifyCustomer). Each object carries a key mapping back to real source data, here sku from erp.parts_tbl. You get mappings, permissions and tooling out of the box, in exchange for living on their platform.

One thing to notice: the model records an action type. notifyCustomer is about as simple as one gets: tell a customer their order is going to be late. A verb, so the model captures what you can do, not just what exists. It comes back in §05.

04 · The pairOntology is the blueprint; the knowledge graph is the building

The most common point of confusion, so let's kill it cleanly. The ontology is the schema: classes, relationships and rules. Fill it with actual suppliers and actual orders and you get a knowledge graph: the instances. The ontology is a few hundred lines a human can hold in their head and it changes slowly; the knowledge graph is millions of nodes that change constantly. Keeping them separate lets you validate a churning dataset against a small, trusted model.

SCHEMA → GRAPH · AND TWO WAYS TO FILL IT
the ontology
types and rules only
Supplier  supplies→  Part
Part  used_in→  Product
Order  contains→  Product
Order  placed_by→  Customer
 
rule: subpart_of is transitive
rule: every Part needs a Supplier
~200 lines · changes yearly
not a single
real thing
in here yet

1The ontology on its own is completely empty of data. It says a Supplier can supply a Part, but knows no supplier's name. This is the artifact a human writes and signs off on.

the ontology
types
Supplier suppliesPart
Part used_inProduct
populate
the knowledge graph
real instances, conforming to the types
Kessler-GmbH supplies Bearing-A40
Bearing-A40 used_in Pump-X9
Bearing-A40 used_in Pump-X7
Order-8812 contains Pump-X9
Order-8812 placed_by Halden-Marine
… 4.8M more edges
millions of rows · changes hourly

2Now pour in real instances and you have a knowledge graph. Every edge conforms to a relationship the ontology declared: that's the contract, and it's checkable.

route A · materialize
ERP · WMS · CRM · PDFs
↓  ETL + extraction  ↓
a real graph store, physically holding the triples
+ fast traversal, real reasoning
− a copy to keep in sync
route B · virtualize
ERP · WMS · CRM (left in place)
↓  declarative mappings  ↓
a virtual graph: queries rewritten to SQL at runtime
+ zero copies, always current
− limited reasoning, joins can hurt

3Two ways to fill it, and most write-ups only mention the first. Materialize and you build a real graph. Virtualize and no data moves: the ontology is a live view over your existing databases, and graph queries get rewritten into SQL on the fly.

That third step is a whole discipline: ontology-based data access. You declare mappings (the standard is R2RML) that say "the parts table's sku column is a :Part", and the query engine turns SPARQL over the ontology into SQL over the real tables. Nothing is copied, nothing goes stale; you just inherit your source databases' performance. Plenty of production systems run a hybrid: virtualize the transactional stuff, materialize the extracted-from-documents stuff.

05 · The payoffFour places it actually earns its keep

Enough anatomy. What does it get you? Four wins, and they build on each other.

1 · Integration by mapping instead of by project

Today, "customer" in the CRM isn't "account" in billing, and joining them is a bespoke project every single time: n systems means point-to-point integrations. With an ontology, each source maps once to shared concepts, and a new system plugs into a model that already exists.

2 · Inference: facts nobody wrote down

This is the one a pile of tables genuinely cannot do. Because relationships are typed and rules are declared, a machine derives conclusions that were never stored anywhere: "which customers are exposed if Kessler-GmbH misses a shipment?" is four hops and a transitivity rule away. Full walk in §07.

3 · One definition, enforced

"Active customer" gets defined once, as a machine-readable rule, instead of ten times across ten dashboards that quietly disagree. A validator then flags the violations (an ownerless part, an order with no customer) instead of waiting for a human to notice.

4 · Grounding for AI

Point an agent at raw tables and it guesses what things mean, then confidently invents relationships. Point it at a typed graph and it traverses real, named edges, and can show you the path it took. §08 has the numbers.

The Palantir wrinkle: model the verbs too. Most ontology writing stops at nouns. Palantir's framing splits the model into semantic (the objects, properties and links), kinetic (the actions people and systems can take) and dynamic (the logic and models bound to them). Their point: a warehouse records what happened, never what was decided. Putting notifyCustomer next to Order means an agent gets a governed verb, not just a read-only view. It's the most useful idea in their write-up, and it's why §03's model has an action type.

And when not to bother

Being even-handed: one application, one clean schema, nobody asking cross-system questions, and an ontology is pure overhead; a database already models that fine. The failure mode has a name: boiling the ocean, modelling everything perfectly before anything ships. A tiny ontology in production beats a magnificent one in a slide deck.

06 · The buildTwo ways to make one with an LLM

Approach 1
Schema-first: you write the model, the LLM only fills it

The core idea: a human authors the ontology (it's small) and the LLM is never allowed to invent a type. It reads documents and emits instances, constrained to your declared classes and relationships, and everything it produces is validated against the schema before it lands. It's the lowest-hallucination option and the one I'd default to.

SCHEMA-FIRST · A MESSY EMAIL BECOMES TYPED FACTS
the ontology · written by a human
allowed vocabulary
classes: Supplier, Part,
  Product, Order, Customer
 
edges: supplies, subpart_of,
  used_in, contains, placed_by
+
raw source · one supplier email
RE: A40 bearing delay
Hi — heads up, Kessler can't ship the A40 bearings until the 14th. That's the part in the X9 pump assembly, and I think the X7 uses it too. Halden Marine's order is the one at risk.

1Two inputs. Left, the small ontology a person wrote and owns. Right, a real email: free text, names scattered through a paragraph. Multiply the right side by a few hundred thousand and you see why humans never finished tagging these.

the email
free text
LLM
Extract entities and relationships using only the classes and edges in the ontology below. If something doesn't fit, return it under unmapped: do not invent a type. Emit triples.
extracted triples
subject · predicate · object
Kessler-GmbH supplies Bearing-A40
Bearing-A40 used_in Pump-X9
Bearing-A40 used_in Pump-X7
Order-8812 placed_by Halden-Marine
 
unmapped: "until the 14th"

2The model emits typed triples, constrained by the ontology, so it can only use types that exist. That stops it inventing a "bearing situation" class. Anything that doesn't fit goes to unmapped rather than getting forced into the nearest type.

validate every triple against the schema
✓ 4 triples match declared edges
⚑ "Kessler-GmbH": new Supplier, unseen
⚑ "until the 14th": no matching property
outcome
4 edges into the graph
2 items held for a human
the flagged date is a signal:
the ontology may need a
promised_date property

3A validator checks each triple before it lands. Clean ones flow in; a new supplier and an unmappable date get held. The model proposes, the schema and a human dispose, and the flagged leftovers show what the ontology is still missing.

Strong option, and the one with the best evidence behind it: it's essentially what Text2KGBench measures and what ontology-grounded RAG assumes. The limitation is in the name: it can only find what you thought to model. No concept of "regulatory hold"? Every mention becomes unmapped noise. Which is what the next approach is for.

Approach 2
Schema-free: let the LLM invent types, then abstract them

Flip it around: give the LLM no vocabulary and let it name whatever it sees, document by document. You get a sprawling mess of near-duplicate types (supplier, vendor, parts supplier), then a second pass clusters them into an actual schema. AutoSchemaKG is the clearest recent example. GraphRAG sits nearby but isn't quite the same: it ships generic default entity types you're expected to replace, so it's loosely typed rather than schema-free.

pass 1 · extract with no schema
whatever the LLM sees
Kessler is_vendor_for A40
Kessler supplies A40
Kessler ships bearings
Bosch provider_of seals
… 340 relation names
cluster +
generalise
pass 2 · induced schema
collapsed into types
Supplier suppliesPart
  ← is_vendor_for, ships,
    provider_of, sources
 
Part used_inProduct
  ← goes_into, component_of

The appeal is genuine discovery: it surfaces concepts nobody thought to model, which a schema-first pipeline is blind to. The costs: quality rests entirely on clustering, there's no validation gate, and open-domain runs over-fragment, leaving hundreds of entity types where a few dozen would do. Useful, but not for an unsupervised production run.

These two aren't rivals. The practical pattern is to run schema-free first, on a sample, to discover what's in your corpus, then hand-curate that into a real ontology and switch to schema-first for the production run. Discovery mode and production mode, not competing philosophies.

So what would I actually do?

The one rule that keeps this safe. LLMs propose; the ontology and a human ratify. The schema is small and high-stakes, so a person signs off on changes. The data is huge, so extraction is automated, but every triple is validated on the way in. Skip that gate and you get a confident hallucination hardening into an authoritative-looking fact that every later query inherits.

07 · Putting it to workThe four kinds of question it answers

You've built the thing. What do you point at it? Four types of questions, answered by genuinely different machinery. People often build an ontology expecting it to help with type 3, and are surprised when it doesn't on its own.

Type 1 · Lookup

Follow the edges

"Who supplies the A40 bearing, and what's the lead time?"

A direct traversal, one or two hops from a known node: what a graph database is fastest at. Honestly, a normal SQL join would do it too.

→ SPARQL traversal

Type 2 · Inference

Derive what nobody stored

"If Kessler slips a week, which customers are exposed?"

Multi-hop, needs the rules: transitivity means the chain has no fixed length. The one an ontology is genuinely uniquely good at.

→ reasoner + traversal

Type 3 · Aggregation

Compute a number

"What's our total exposed order value this quarter?"

Sums and group-bys. The graph doesn't magically do arithmetic, but the ontology gives the query generator correct joins and one agreed definition of "exposed".

→ query generation over the model

Type 4 · Action

Change something

"Reallocate stock from the X7 line to cover Order-8812."

Not a question at all. It's a governed write, back into the source systems. Only exists if your model includes verbs, per §05.

→ action type / writeback

Type 2, in full: the one that justifies the whole exercise

Let's walk the inference case properly, because it's the one you can't fake with a join. A supplier emails to say a shipment slips; nobody has ever written down which customers that affects. Watch it get computed:

INFERENCE · WHICH CUSTOMERS ARE ACTUALLY EXPOSED?
what's actually stored
asserted facts
Kessler-GmbH supplies Bearing-A40
Bearing-A40 subpart_of Rotor-Assy
Rotor-Assy used_in Pump-X9
Order-8812 contains Pump-X9
Order-8812 placed_by Halden-Marine
plus
one
rule
rule in the ontology
subpart_of is transitive if a Part is delayed and that Part is used_in a Product, then every Order containing it is at_risk

1Five local facts and one general rule. Nowhere does it say Halden Marine is at risk, and nobody maintains a "what-breaks-if" spreadsheet.

supplies used_in contains placed_by subpart_of * Kessler delayed Bearing-A40 → Rotor-Assy Pump-X9 blocked Order-8812 at risk Halden Marine exposed
the * on subpart_of is the transitivity; the chain has no fixed length

2The reasoner walks it: because subpart_of is transitive, the delay ripples through however many layers of sub-assembly exist, four deep or one, same rule. This is inference: new true facts, computed rather than stored.

the question
"Kessler just slipped a week. Who does that hit, and how badly?"
derived answer · none of it hand-written
inferred
Blocked products: Pump-X9, Pump-X7
Orders at risk: 8812, 8840, 8851
Customers exposed: Halden Marine,
  Nordvik AS (both tier-1)
Exposed order value: €1.24M

3Blast radius, customers, and the euro figure, all from typed edges and one rule. Crucially, the system can show its path, so a human checks the reasoning instead of trusting a number.

One line to keep: a database tells you what you stored; an ontology tells you what follows from it.

Type 3, honestly: where the data-query question gets interesting

The case people ask about most, where I want to be careful not to oversell. "What's our total exposed order value this quarter?" is arithmetic; a graph doesn't do arithmetic better than a database. So what's the ontology contributing?

It's not helping the computer compute; it's helping the LLM write a correct query. Ask a model to write SQL against 200 raw tables (ord_hdr_t, cust_mstr) and it has to guess which join is right and what "exposed" means. Give it a model where Order placed_by Customer is declared and "exposed" is a defined rule, and most of the guessing disappears.

without a model · guess the joins
LLM sees raw schema
ord_hdr_t (id, cust_ref, dt, amt)
cust_mstr (cno, nm, seg, flag_a)
ord_ln (oid, sku, qty)
 
is cust_ref = cno? probably.
is flag_a "active"? no idea.
what counts as "exposed"? guess.
plausible SQL, quietly wrong answer
vs
with the ontology · joins are declared
LLM sees the model
Order placed_by Customer
Order.value : money
Customer.tier : {1,2,3}
 
"exposed" is a defined rule:
  Order at_risk ∧ status ≠ shipped
the join is given, not inferred

This distinction has been measured, and the numbers are the strongest argument in this whole post, which is where §08 goes.

08 · The AI partHow it plugs into agents and RAG

Two separate stories, often muddled together: retrieval (feeding an LLM better context) and querying (getting a correct number out of a database). The ontology helps with both, differently.

For RAG: retrieval that follows relationships

Vanilla RAG embeds your question, grabs the nearest chunks, and hopes the answer is in there. It has no idea that Bearing-A40 and Pump-X9 are related: relationships aren't something embeddings represent well. Graph retrieval lands on the right node and traverses typed edges into the connected neighbourhood, which chunk similarity can't do. (Mechanics in the GraphRAG post.) The ontology's contribution: the graph has meaningful, validated types instead of whatever the extractor invented.

Does the grounding help? OG-RAG (EMNLP 2025) anchors retrieval in a domain ontology and reports +55% fact recall and +40% response correctness over standard RAG across four LLMs, plus faster attribution. Caution about self-reported gains applies, but the direction matches everything else here.

For agents: the ontology becomes the tool surface

The more interesting shift. An enterprise agent doesn't want raw table access; that's both dangerous and useless. What it wants is a small set of typed tools over a model it can understand, and the ontology is that surface: objects to search, links to traverse, rules to check, and (if you modelled the verbs) actions to invoke, each inheriting the same permissions a human would have.

ONE AGENT LOOP, OVER THE ONTOLOGY
loaded up front
the prompt
system: you answer questions about
  the supply chain. Use the tools.
ontology: 5 classes, 5 links,
  12 rules. the whole schema
tools: search_objects, traverse,
  aggregate, notifyCustomer
user: "Kessler slipped a week.
  What's at risk, can we cover it?"
the agent's context window
contentsapprox
system prompt0.2k
the ontology, all of it1.5k
4 tool definitions0.3k
the question30
total~2.0k tokens
the graph itself (1.2M nodes,
4.8M edges) is not in here

1Start with what's in the prompt: the entire ontology fits in a page of text, and the knowledge graph never enters context. That asymmetry is the whole trick: the agent reasons over the model and queries the data.

call 1 · resolve the name
search_objects( type: "Supplier", match: "Kessler" )
it knows Supplier is real:
that came from the
schema in step 1
returns · a typed object
1 match
{ id: "supplier/kessler-gmbh",
  type: "Supplier",
  name: "Kessler GmbH",
  supplies: 14 Parts }
+
context now
prompt + schema + tools2.0k
1 resolved Supplier40
total~2.0k

2First move is always resolve, never guess: "Kessler" becomes a real object with a real ID, and every later call keys off it. Forty tokens back, one object, not a page of search results.

call 2 · walk the model
traverse( from: "supplier/kessler-gmbh", path: "supplies / subpart_of* / used_in / contains", return: ["Order", "Customer"] )
the path is named edges from
the ontology
; the agent can
only ask for links that exist
what the store actually runs
compiled query
SELECT ?order ?customer WHERE {
 :kessler-gmbh :supplies ?part .
 ?part :subpart_of* ?assy .
 ?assy :used_in ?product .
 ?order :contains ?product ;
       :placed_by ?customer . }
* = any depth, so a bearing
four sub-assemblies deep still
reaches its order

3Here's what traverse really is: the agent names a path through the ontology, and the engine compiles it into a graph query. It can write the whole path in one call because the model is tiny and all of it is in context. That subpart_of* is the transitivity rule from §07, so the agent never has to know how deep the tree goes.

returns · endpoints + the path taken
traverse result
{ orders: [8812, 8840, 8851],
  customers: [
   {id:"cust/halden-marine", tier:1},
   {id:"cust/nordvik-as", tier:1}],
  via: "supplies → subpart_of*
    → used_in → contains" }
4.8M edges scanned in the store ·
5 objects come back
+
context now
prompt + schema + tools2.0k
1 resolved Supplier40
3 Orders, 2 Customers, + path90
total~2.1k
the via string is the
receipt: the answer is auditable

4Only the endpoints come back, plus the path that produced them. Millions of edges walked inside the store, ninety tokens crossed into the model. And because the traversal is recorded, every claim downstream can be traced to it.

call 3 · get the number
aggregate( over: [8812, 8840, 8851], metric: "sum(Order.value)", where: "status != shipped" )
returns
{ sum: 1240000, currency: "EUR",
  n: 3, definition: "exposed" }
the point
the store did the arithmetic
the model did not add anything up
same figure every run; "exposed"
means exactly what the ontology
says it means

5The euro figure is computed, not generated: an LLM asked to total three values will usually get it right and occasionally won't, a store never misses. The §07 aggregation case, and why a defined metric beats a clever prompt.

everything the model has
contentsapprox
system prompt0.2k
the ontology1.5k
tool definitions + question0.3k
1 Supplier40
3 Orders, 2 Customers, + path90
1 aggregate20
total~2.2k tokens
vs
what plain RAG would have loaded
contentsapprox
~60 chunks of tickets, emails, specs45k
total~45k tokens
…with no guarantee the Kessler to
Halden Marine chain is spelled out
in any single chunk

6Pause here: this is the argument. Three tool calls put six typed facts and one number in front of the model, no prose, nothing to misread. The agent has never read a document.

call 4 · the write
notifyCustomer( order: 8812, reason: "supplier delay" )
Gate
✓ valid for Order
✓ caller may message
⚑ tier-1 → sign-off
final answer, grounded
to the user
Three orders are exposed:
8812, 8840, 8851, worth
€1.24M, hitting Halden Marine
and Nordvik AS, both tier-1.
A delay notice for 8812 is
drafted and waiting on you.
via supplies → subpart_of*
  → used_in → contains

7The write is checked against the schema, the caller's own permissions, and a policy, then staged, not fired. The answer carries its traversal path, so anyone can check the reasoning. The agent proposes; the model and a human dispose.

About that traverse call. The signature is illustrative (there is no standard traverse API), though the path string is real: supplies/subpart_of*/used_in/contains is SPARQL property-path syntax, where / means "then" and * means "any number of times". The common alternative, a plain depth argument ("everything within three hops"), is simpler but drags every unrelated neighbour in; naming the path and return types keeps the answer to five objects. And it rarely composes in one call once a model has hundreds of classes. Expect instead: the agent walking a hop at a time, asking the schema what links out of Supplier before planning, or, most common in production, calling impactOf(supplier), a traversal some human authored once. That last one is the least glamorous and by far the most reliable.

That staged-write pattern is the core of what Palantir calls connecting agents to decisions: proposed changes packaged into a sandboxed scenario, with the same row- and column-level policies applied to an agent as to a person. You don't need their platform to copy the shape: typed tools, permissions inherited from the caller, writes staged not executed.

The data-query numbers

Here's the evidence I find most persuasive. Sequeda and colleagues built a benchmark on a real insurance schema (13 gnarly tables) and asked GPT-4 enterprise questions two ways: SQL straight against the tables, or SPARQL over a knowledge graph with an OWL ontology on the same data. A follow-up added a checker that catches semantically wrong queries and lets the LLM repair them in a loop:

SQL, straight at the tables16%
over a knowledge graph + ontology54%
+ ontology query check & repair72%

The questions, data, and model were identical across all three runs. The last bar includes 8% that the system answered with an honest "I don't know" rather than a guess.

Two things stand out. First, 16% to 54% is the difference between unusable and useful, purely from giving the model semantics instead of raw column names. Second, that 8%: the checker's real contribution is converting wrong answers into admitted ignorance, which in an enterprise is worth more than the accuracy points suggest.

A caveat: single-benchmark results on one insurance schema, and frontier models have improved since. dbt re-ran a similar comparison in 2026: raw text-to-SQL now lands at 84 to 90%, a governed semantic layer at 98 to 100%. The gap narrowed, but how the two fail is what matters: the semantic layer refuses out-of-scope questions, while text-to-SQL returns a confident, plausible, wrong number. And dbt's layer is a metrics model, not an OWL ontology; a weaker artifact, which makes the direction more telling.

Where this genuinely doesn't help. An ontology only covers the questions it was built for: ask something outside its scope and a well-built system says it can't answer, correct but still a "no". Wide-open exploration across unmodelled data? You're back to text-to-SQL and its confident guesses. Model the questions you actually have.

09 · Wrapping upShould you build one?

Ontologies are a 1990s idea that spent two decades being technically correct and practically ignored, because building and populating one cost more than it returned. Two things changed at once: LLMs made them far cheaper to build and populate, and, for the first time, gave us a consumer desperate to use one. Supply and demand for structure flipped together.

If I compress it to one path: write down the questions first, model only the classes those questions need, let an LLM fill the graph but validate every triple on the way in, keep a human on the axioms, and put it in front of a real query before it's pretty. The teams that succeed treat the ontology as a product with an owner, not a project with an end date, and start absurdly small.

Model the questions you have, not the world you imagine.

These are living notes. I'll keep updating as the pattern matures. Thanks for reading.