The write side of the ontology conversation: a shared domain model that not only lets you read your business but run it. Data Engineering Weekly walks a single e-commerce scenario - two legacy order systems left over from a merger - and assembles, scene by scene, an operational ontology: objects and links for reads, plus named, rule-carrying actions for writes.
The pattern's crux is an asymmetry. Reads traverse the model freely; writes are decisions, and decisions pass through a gate equipped with business rules and an audit trail. It persists state in three declared kinds - source-backed, ontology-owned, and derived - and writes accepted changes back to the systems of record that own them. The same gate governs human operators and AI agents, and the answer to "can you cancel an order from your semantic layer?" decides whether you have a read layer or an operational ontology.
| Dimension | AI context layer | Formal ontology (OWL/RDF) | Knowledge graph | Operational ontology (Foundry-style) | Philosophical ontology |
|---|---|---|---|---|---|
| Governed writes? | noRead-side grounding only. | noSemantic only - the kinetic half is absent. | writable as data, not as operationsSPARQL UPDATE can set status; there is no first-class named, audited, write-back action. | yesAction-gated, audited, write-back to the systems of record. | noNo state, no writes. |
| What it is | semantic grounding for AI answersGuards and grounds what an AI says, not what the business does. | machine-reasonable semanticsOWL/RDF model what things are and support reasoning, but a reasoner cannot cancel an order. | entities and relationshipsWritable as data, but not as operations. | business domain schema + rule-carrying actionsObjects and links plus named, preconditional actions that audit and write back. | the study of what existsAsks what exists, not how to operate on it. Purely descriptive; no governed writes. |
A company acquires a competitor and inherits two order systems with different schemas and status encodings - north.tbl_order stores status as integers, south.SALES_ORDER as text. Inside each system business runs fine; the trouble is the questions that cross systems, like "which orders contain this product?".
A few dozen lines of SQL and a small mapping integrate the two feeds into one table, aligning columns and unifying status encodings. On top, Customer, Order, and Product objects with links answer cross-system questions without caring which system each row came from. The model is materialized into the ontology's own datastore, indexed from the integrated table.
An operator spots a mistaken order but the dashboard has no cancel button - they must close the BI tool and log back into the legacy system. This is the boundary between read and write; the one-question test is whether you can cancel an order from your semantic layer.
There is no generic update path - absent by design. Every business decision gets a named action: cancelOrder targets the Order object, declares parameters, a precondition, and effects, and carries a write-back. Refusal is a first-class machine-readable result, and every attempt lands in the audit log.
Every piece of state is one of three kinds - source-backed, ontology-owned, or derived - and state with no declared owner is forbidden. Write-back runs before the local commit, failure direction is declared, and the overlay lets ontology-owned changes survive a re-index.
The operations an agent receives are generated by the model: query tools for reads, predefined actions for writes, no raw SQL. The same precondition that refuses a human refuses an agent with a machine-readable code the agent can read, recover from, and explain.
Entities, commands, guarded state transitions, and append-only audit logs are the classics of DDD and CQRS. What is new is the placement: a shared domain layer one level up, on top of data other systems own, shared by people, applications, and agents.
Do not start with a company-wide model. Choose a single decision - say, cancelling an order - and bound it to one object, one action, one precondition, an audit log, and a write-back. The write side starts there.
Define the business objects that carry the state. For each property declare who owns its truth: an order's status and total are source-backed; an assignee or note is ontology-owned (marked owned); aggregates are derived and never written.
Define the verb - cancelOrder - with a target object, a parameter set, preconditions that refuse domain-invariant violations with machine-readable codes, and an effects function that returns an edit plan and performs nothing itself.
Route the change to the originating system in its own encoding (integer codes for one, text statuses for the other) and run the adapter before the local commit, so if the source refuses, nothing changes locally.
A few dozen lines of SQL unify the two feeds; a small mapping normalizes status encoding. From that integrated table, index the base into the ontology's own datastore - a pipeline's job, a prerequisite of the pattern, not part of it.
Keep the source-derived base and the action-derived changes separate; reappraise ontology-owned changes onto each freshly indexed base. Refuse a re-index that would orphan an edit, and never make reconciliation decisions silently.
Generate the operation surface from the model - query tools for reads, predefined actions for writes, and no raw SQL - so humans and AI agents meet the identical gate, audit, and write-back. Declare failure semantics, authorization, and concurrency.
A shared domain model built on top of the data of systems you don't own - objects, links, and actions - where reads traverse the model and writes are gated by rule-carrying, audited actions that write back to the systems of record that own the state they change. A semantic layer lets you read your business; an operational ontology lets you run it.
The operations an agent receives are generated by the model - query tools for reads, predefined actions for writes, and no raw SQL. The same precondition that refuses a human refuses an agent with a machine-readable code the agent can read, recover from, and explain.
No. CRUD validation lives inside one application on tables that application owns. Here the model sits on data other systems own, is shared by every consumer, closes every write path except actions, audits every attempt, and writes accepted changes back to the systems of record.
Entities, commands, guarded state transitions, and append-only audit logs are those classics. What is new is the placement: the domain layer lifted out of a single application, put on top of other systems' data, and shared by many applications and agents.
If not, you have a read layer. If yes but no row in any system of record changes, you have a parallel database. If it also cancels already-shipped orders without complaint, you have a write API - the precondition is the whole difference.
No. It is a free-standing concept, and the reference implementation is a minimal TypeScript repository proving the pattern runs without any particular product. That is what makes it a portable decision criterion: ask whether a shipped "ontology" is semantic only or goes as far as kinetic.
A semantic layer answers queries; it has no governed write path. An operational ontology adds the kinetic half - named actions, preconditions, an audit trail, and write-back - so you can not only read the business but change it through a gate.
In Palantir Foundry's vocabulary, objects and links are the semantic elements and actions and functions are the kinetic elements. The operational ontology pattern packages both; the one property that changes what a layer can do is whether it accepts writes governed by business rules.
A generic write API lets anyone fire an UPDATE and nobody defends the business reality that a shipped order cannot be canceled. Removing it by design forces every business decision to be a named, rule-carrying action that is audited and written back.
A precondition checks a domain invariant, such as "a shipped order cannot be cancelled." On violation the action returns a machine-readable error code like SHIPPED_ORDER_CANNOT_BE_CANCELLED - a first-class result, not an exception.
Reverse ETL syncs modeled data downstream on a schedule with no notion of refusal. A write-back is one governed decision - rule-checked, audited, refusable - propagating one action's outcome to the system of record.
Every piece of state declares who owns its truth. Source-backed state is owned upstream and changes propagate back; ontology-owned state (an assignee or note) has its SSoT in the ontology's own store by declaration; derived state (aggregates) is computed and never written.
Write-back runs before the local commit, so if the source refuses, nothing changes locally. The reverse failure - write-back succeeded but the local commit failed - remains possible and must be declared in advance; for source-backed state the source wins and the next re-index reconverges.
Yes. Edits live in an overlay keyed by type and primary key that is reapplied over each freshly indexed base. A re-index that would orphan an ontology-owned edit is refused whole rather than partially applied.
A state change that occurs only through a named action with preconditions, effects, and write-back. There is no generic update path; a re-index only replays what sources already say.
An append-only record of every action attempt, applied or refused, committed with the action's edits. The one declared unscoped administrative view.
Stating in advance what happens when write-back and the local commit disagree, so divergence is an engineering problem rather than a production incident.
A state kind computed on the fly and never written - aggregates and counts. Computed reads are outside the pattern's governing half.
The part of an operational ontology that changes state: actions and functions. Foundry counts three - actions, functions, and dynamic security. The distinguishing half of the pattern.
A declared business operation with a target object, parameters, preconditions, and effects - the verb the dashboard's cancel button exists only once the model has.
A state kind no source system has a column for; by declaration the ontology's own datastore is its Single Source of Truth (e.g. an assignee or a triage note).
A shared domain model on top of data you don't own - objects, links, and actions - where reads traverse the model and writes are gated by audited, rule-carrying actions that write back to the systems of record.
The mechanism that keeps action-derived changes separate from the source-derived base and reapplies ontology-owned edits over each freshly indexed base.
A domain invariant checked at the action boundary, e.g. "a shipped order cannot be cancelled." It refuses violations with a machine-readable error and is validity, not permission.
Replaying the sources into the base. It is not a user API and not a decision: a re-index that would orphan an ontology-owned edit is refused whole.
Scheduled syncing of modeled data downstream, with no notion of refusal. The unit of measure differs from write-back, which is one governed decision propagating one action's outcome.
The declared owner of a piece of state: source-backed (upstream), ontology-owned (the ontology's store), or derived (computed, never written). State with no declared owner is forbidden.
A governed, queryable model of business entities and metrics for reading. A useful tool, but a different thing from an operational ontology because it has no governed write path.
A state kind whose SSoT lives in an upstream system; a change to it propagates back upstream through the governed path of rules and audit.
A governed, ordered side effect that propagates a change to source-backed state back to its system of record, written before the local commit.
Interactive graph visualization derived from the companion RDF. Click nodes to resolve, drag to explore. Graph data embedded from companion RDF at generation time.
Query this knowledge graph on URIBurner. The editor opens on the canonical SAMPLE entity-type summary (DAV named graph). Pick a recipe, edit freely, then run live or copy.
Reproduced verbatim from the companion RDF. Execute loads the query into the workbench below and runs it live.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
PREFIX oo: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology/ontology#>
PREFIX doc: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology#>
SELECT ?orderIri ?orderId ?status ?sourceSystem ?product ?productName WHERE {
?orderIri a oo:Object, oo:Order ;
schema:name ?orderId ;
oo:status ?status ;
oo:sourceSystem ?sourceSystem ;
oo:hasProduct ?product .
?product a oo:Object, oo:Product ; rdfs:label ?productName .
FILTER(?productName = "Keyboard"@en)
}
ORDER BY ?orderIdRead side: the link traversal answers a question the two legacy schemas could not answer with one query. It returns N-A-1001, N-A-1002, and S-SO-78 - three orders, two systems, one model.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
PREFIX oo: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology/ontology#>
PREFIX doc: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology#>
SELECT ?orderIri ?orderId ?status ?sourceSystem WHERE {
?orderIri a oo:Object, oo:Order ;
schema:name ?orderId ; oo:status ?status ; oo:sourceSystem ?sourceSystem .
FILTER(?status != "shipped"@en && ?status != "cancelled"@en)
}
ORDER BY ?orderIdThe precondition evaluated over the data: only non-shipped, non-cancelled orders pass the cancelOrder gate. N-A-1003, S-SO-77, and S-SO-79 are cancellable; N-A-1001, S-SO-78, and N-A-1002 are not.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
PREFIX oo: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology/ontology#>
PREFIX doc: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology#>
SELECT ?orderIri ?orderId ?status ?code WHERE {
{ ?orderIri a oo:Object, oo:Order ; schema:name ?orderId ; oo:status "shipped"@en .
BIND("SHIPPED_ORDER_CANNOT_BE_CANCELLED"@en AS ?code) }
UNION
{ ?orderIri a oo:Object, oo:Order ; schema:name ?orderId ; oo:status "cancelled"@en .
BIND("ORDER_ALREADY_CANCELLED"@en AS ?code) }
}
ORDER BY ?orderIdMachine-readable refusal as a first-class result, not an exception. Shipped orders map to SHIPPED_ORDER_CANNOT_BE_CANCELLED; already-cancelled orders map to ORDER_ALREADY_CANCELLED.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
PREFIX oo: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology/ontology#>
PREFIX doc: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology#>
SELECT ?property ?stateKind ?kindLabel ?ownerLabel WHERE {
?d a oo:AuthorityDeclaration ;
oo:aboutProperty ?property ;
oo:stateKind ?stateKind ;
oo:owner ?owner .
?stateKind rdfs:label ?kindLabel .
?owner rdfs:label ?ownerLabel .
}
ORDER BY ?propertyProperty 4, made queryable: status and total are source-backed by the upstream systems; the assignee is ontology-owned by the ontology store; aggregates are derived and never written.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
PREFIX oo: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology/ontology#>
PREFIX doc: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology#>
SELECT ?actionIri ?actionName ?writeback ?rule WHERE {
?actionIri a oo:ActionType ;
schema:name ?actionName ;
oo:targets oo:Order ;
oo:writeback ?writeback .
OPTIONAL { ?actionIri oo:precondition ?pre . ?pre oo:ruleText ?rule }
}
ORDER BY ?actionNameThe action surface derived from the model: cancelOrder (write-backed, with its shipped/cancelled preconditions), assignOrder (ontology-owned, only pending), and addOrderNote (creates ontology-owned state).
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
PREFIX oo: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology/ontology#>
PREFIX doc: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology#>
SELECT ?actIri ?actionName ?targetId ?outcomeLabel ?errorCode WHERE {
?actIri a oo:ActionInstance ;
oo:action ?action ; oo:target ?target ; oo:outcome ?outcome .
?action schema:name ?actionName .
?target schema:name ?targetId .
?outcome rdfs:label ?outcomeLabel .
OPTIONAL { ?actIri oo:errorCode ?errorCode }
}
ORDER BY ?targetIdEvery attempt is audited - applied AND refused. Note the refused cancel of N-A-1001 and the applied cancel of N-A-1002, plus the ORDER_ALREADY_CANCELLED refusal on the double-click retry.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
PREFIX oo: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology/ontology#>
PREFIX doc: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology#>
SELECT ?orderIri ?orderId ?assignee ?noteId ?noteText WHERE {
?orderIri a oo:Object, oo:Order ; schema:name ?orderId .
OPTIONAL { ?orderIri oo:assignee ?assignee }
OPTIONAL { ?orderIri oo:hasNote ?note . ?note schema:name ?noteId ; oo:text ?noteText }
}
ORDER BY ?orderIdOntology-owned state persists through a re-index via the overlay: the assignment on N-A-1003 and the notes on N-A-1001 and S-SO-79 survive while source-backed status refreshes from the systems of record.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
PREFIX oo: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology/ontology#>
PREFIX doc: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology#>
SELECT ?status (COUNT(?orderIri) AS ?orders) (SUM(?total) AS ?totalValue) (SAMPLE(?orderIri) AS ?sampleOrder) WHERE {
?orderIri a oo:Object, oo:Order ; oo:status ?status ; oo:total ?total .
}
GROUP BY ?status
ORDER BY ?statusDerived state is computed on demand and never stored - the aggregate is a query, not a column. This is the third state kind: derived, in contrast to source-backed and ontology-owned.
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX schema: <http://schema.org/>
PREFIX oo: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology/ontology#>
PREFIX doc: <https://www.dataengineeringweekly.com/p/building-an-operational-ontology#>
SELECT ?orderIri ?orderId ?sourceSystem ?sourceId WHERE {
?orderIri a oo:Object, oo:Order ;
schema:name ?orderId ;
oo:sourceSystem ?sourceSystem ;
oo:sourceId ?sourceId .
}
ORDER BY ?orderIdThe write-back route: each order names the system of record it came from, so the adapter writes an accepted cancel back to north.tbl_order or south.SALES_ORDER in that system's own encoding.