How it fits together
The format reference tells you what each field means. This page answers the question underneath it: what are the moving parts, and which power does each one hold?
The clearest way to see it is not as an architecture diagram but as five things running at the same time during the demo.
| Part | What it concretely is | What it may do |
|---|---|---|
| The data | ecommerce.duckdb — tables customers, orders | Exist. Ontologiq never writes to them |
| The ontology | objects/*.yml, compiled into SQL views | Say what a row means |
| The AI agent | Claude, connected to ontologiq serve over MCP | Read, traverse, propose |
| The human | you, running ontologiq approvals in a different terminal | The only one who may approve — and may also propose, via ontologiq propose |
| The operational system | ops/webhook_listener.py in the demo; your OMS in production | The only one that performs the change |
Four actors, four different powers, and none of them holds another’s. That sentence is the whole design.
The sequence, on one order
Follow order 102 through the demo.
1. First there is only a row
In the orders table: order_id=102, fulfilled_at=NULL, dispute_opened_at=NULL. It means nothing yet. It is a row.
2. The ontology gives it meaning
order.yml declares that the row is an order object, identified by
order_id, and that it is in state open when fulfilled_at is null. The
compiler turns that into a CASE WHEN inside a SQL view. Order 102 now is
open — it does not merely “have a null column”.
That looks cosmetic. It is not, and step 4 is why.
3. The agent sees only what the ontology declares
Start the server with ontologiq serve --role support and the agent
receives seven tools: get_order, list_customer,
traverse_order_customer, … and propose_order_cancel. It does not
receive “run arbitrary SQL”.
It never sees email either: that property is sensitive: true, so the
column is not even selected by the query. The value never leaves the
database — this is withholding, not masking.
Start the same server with no --role and the propose_ tools are not
listed at all. An agent cannot call what it cannot see.
The ontology is the agent’s entire universe. Whatever you did not declare does not exist for it.
4. The agent proposes, and stops
A user asks to cancel order 102. The agent calls propose_order_cancel, and
three things happen:
- the role is checked — is
supportallowed to call this? - the precondition is evaluated against live data — is order 102
actually
openright now? - if both hold, a proposal is written to a durable local queue
And the agent gets back:
{
"outcome": "pending_approval",
"proposal_id": "35e91e17-db45-402a-a3a8-f6c8f5c7043a",
"note": "A human must approve this proposal before anything happens. You cannot approve it yourself; report the proposal id to the user."
}
Nothing has happened. The operational system has not been touched. And there is no approve tool anywhere on the MCP surface, so there is no sequence of calls that gets the agent any further. That is not a rule in a prompt the model may decide to ignore — the channel does not exist.
This is where step 2 pays for itself. requires: state == 'open' is
expressible only because state is a defined concept computed from live
data. Without the entity model you could not write that line at all, and the
action would be a webhook with a form attached.
5. A human decides, in a different process
In a separate terminal, one the model has no way to address:
ontologiq approvals list
35e91e17-db45-402a-a3a8-f6c8f5c7043a
order.cancel {"order_id": "102"}
params: {"reason": "customer changed mind"}
proposed by support (fernandodenitto) at 2026-08-01T12:12:36Z
You see exactly what was proposed, by which role, with which parameters. You approve — and here is the part that matters most:
The precondition is evaluated a second time. Hours can pass between a proposal and a signature, and records move. If the order shipped in the meantime:
the precondition 'state == 'open'' no longer holds (state is now 'fulfilled')
— nothing was executed
That gap between deciding and acting is where governance usually leaks. Closing it is most of why the runtime exists.
6. The operational system does the work
Only now does the webhook fire, at your system:
POST /orders/102/cancel
Idempotency-Key: 35e91e17-db45-402a-a3a8-f6c8f5c7043a
{
"ontologiq": {
"object": "order",
"action": "cancel",
"actor_role": "support",
"actor_user": "fernandodenitto",
"proposal_id": "35e91e17-db45-402a-a3a8-f6c8f5c7043a",
"ontology_digest": "c0ecb26df9953592"
},
"identity": { "order_id": "102" },
"params": { "reason": "customer changed mind" }
}
Ontologiq does not cancel the order. It tells your order-management system that someone authorised decided to, and attaches who proposed, who approved, and which version of the ontology governed the decision. Your system performs the cancellation.
The value, by subtraction
The honest way to judge each piece is to ask what breaks without it.
Without the entity and state model, you cannot write
requires: state == 'open'. The rule “only cancel what has not shipped”
ends up hardcoded inside the application — rewritten in every app and every
agent, with no single place that says which version is correct.
Without the propose/approve split, you are back to handing the agent a write token and putting “do not cancel without asking” in the prompt. That is soft enforcement: it depends on the model choosing to comply, and under task momentum models demonstrably do not.
Without re-evaluating the precondition at execution, the agent proposes at 09:00, the order ships at 11:00, you sign at 14:00, and you have just cancelled a shipped order. This is the classic failure of approval systems.
Without the webhook boundary, Ontologiq would need write credentials to your operational systems — and nobody would install it. Needing write access to nothing is what makes it adoptable.
Why the data layer and the action layer are one tool
They look like two products: a semantic layer and a governance layer. They ship together for a precise technical reason.
The safety of an action depends on the model of the data. The precondition is a query against live data, evaluated twice, in the same logical step that fetches the record. If these were two separate systems, “is order 102 still open?” would become a question one service asks another over a network — and at that moment you have reopened exactly the gap you were trying to close.
Next
- Concepts — the same model, field by field.
- Security model — what is enforced, and what is only declared.
- Demo — run the sequence above yourself.