Ontologies
Status: design draft for review · decided, not built
A vocabulary is a governed set of shared words: terms with permanent identifiers, a hierarchy, a
deprecation discipline and pinned releases, so that two crews citing biz:churn-risk provably mean
the same thing. OML hosts vocabularies as claims — a term is proposed, held, reviewed by an
identity that did not write it, and promoted, exactly like any memory — because an ontology
service, reduced to essentials, is a governed memory provider for vocabulary. Nothing on this page
is a second subsystem; it is the one pipeline plus identifier discipline, three artifact kinds, one
scanner plugin and a thin read surface. The decision record is
ADR-0021.
Two stances govern everything below, stated once here. AI-first with humans in the loop: every
role is designed to be held by an agent, human principals enter through the same roles and the same
pipeline, and the human loop is placed by policy (human_required_classes,
admission), never by a parallel mechanism. On top of the provider floor: every
vocabulary feature defaults off, and with all of them off OML is a full memory provider — the
features extend the base, they never tax it. A plain memory-provider tenant pays exactly one
default cost for this page existing: one extra MCP tool in tools/list
(mcp-plugin), on the order of a hundred tokens.
Nothing below exists as code. The v1 slice is FR-19; every unmeasured claim on this page names the roadmap row that will measure it.
A vocabulary is a workarea with a prefix
A vocabulary is a dedicated workarea subtree (workareas) created with one extra,
optional field: a prefix, registered in a journalled, tenant-scoped registry
(vocab_prefixes, UNIQUE per tenant — never globally, because a global unique would leak
namespace existence across tenants). Workarea creation is admin-only, and that is the whole brake
on vocabulary sprawl: no new ceremony, no issuer-ceiling clone. Because a prefix registration is
irreversible in meaning, the CLI confirms permanence before the act, per the
preview discipline of cli/reference.md, which extends its
preview-every-mass-act rule to irreversible single acts like this one.
Everything inside the vocabulary rides the ordinary machinery. Grants are ordinary: the vocabulary
workarea grants writer to the crews it serves, dreamer to its curators
(roles-and-keys). Status is ordinary: trunk vocabulary lives at committed;
validated (frozen) is vocabulary retirement — a freeze, not a revocation: terms stay
resolvable forever, and no past verdict is retro-invalidated, because every scan verdict recorded
the release id and scanner version it was taken against. The oml: prefix is reserved in every
tenant's namespace for the provider-published upper vocabulary (is_a, part_of, replaced_by,
consider, …) — shipped product data versioned with the release, not journal content
(RS-10).
The term lifecycle
A term is a claim of kind term whose claim_key is its CURIE — PREFIX:localname — and whose
term_status is a structural payload field, never body text, so the deprecation marker survives
crypto-shredding and is deterministically readable.
stateDiagram-v2
proposed : proposed (held)
active : active (promoted tip)
deprecated : deprecated (promoted tip with successor)
[*] --> proposed : writer submits kind term —<br/>held like any claim
proposed --> active : first promotion —<br/>the CURIE is minted here
proposed --> [*] : rejected or dead-lettered —<br/>localname released, journal trail kept
active --> active : edit-as-proposal ratified —<br/>new version, same CURIE
active --> deprecated : deprecation version ratified —<br/>successor edge rides the same submission
deprecated --> [*] : resolvable forever —<br/>never deleted, never re-minted
Deprecation is supersession, never deletion. The successor pointer is a ratified claim edge —
oml:replaced_by (exactly one; safe for automated rewiring) or oml:consider (one or more;
requires judgment) — and it is declared in the deprecating submission, the way derives_from
is: one serve and one verdict cover the version and its successor edge together, so a deprecation
without its successor can never promote as a dangling half-act.
CURIEs and permanence
The identifier rule, stated at ADR level and enforced in the domain core: a CURIE is minted at
first promotion, and once minted it is permanent. The definition body may be crypto-shredded
(audit-and-lineage), but the term's lineage row and claim_key are never
fully erased, and the CURIE is never re-mintable with a different meaning — the one failure an
identifier system exists to prevent. A proposal that never promotes releases its localname; its
journal trail persists under the artifact id. Two writers racing to mint the same PREFIX:name
resolve first-promoted-wins: the loser gets a typed claim_key conflict at capture and has two
usable paths — edit-as-proposal on the winner, or a different localname plus an intra-vocabulary
mapping. Flood-proposing localnames to squat them buys nothing (nothing is minted before
promotion) and is bounded by what already exists: journalled rate and quota grant attributes,
held-item expiry, and queue depth in oml status and oml doctor.
One consequence is worth its own sentence: simulations cannot mint vocabulary. Vocabulary
workareas refuse provenance=simulation submissions of the vocabulary kinds — otherwise a demoted
simulation row would squat a permanent CURIE forever (simulations).
Releases and the materialized closure
A release is a claim of kind vocab_release: a journalled pin, never a copy. Its body carries
the vocabulary workarea id, the pinned journal seq, and a Merkle root over the then-active term
set whose leaves cover definition bytes, not just identity (the construction is RS-4's one
specification, shared with ADR-0020). Releases accumulate
as claims; nothing ever cuts one automatically — a release is always an explicit, ratified act,
and that is load-bearing for the zero-human blessing below.
At release ratification, the is_a closure over ratified edges is materialized into a
per-release closure table: ancestor and descendant queries, and recall expansion, become lookups
over precomputed rows with journalled provenance — the serialize-don't-compute contract holds. The
cost is storage, O(terms × depth) per release, priced and bounded by release cadence.
A human cannot judge a Merkle root, so the serve of a release is a diff against the previous
release: terms added, deprecations with their successors, is_a edges added and removed, counts.
The diff artifacts enter the served digest set (admission) — the evidentiary
contract records what the reviewer actually saw, not a hash they could only wave through.
Hierarchy and mappings
Hierarchy and relations are ratified claim edges typed from oml:. Cycle refusal is a domain
invariant at ratification — a deterministic transaction-time check over the ratified is_a edges
plus the candidate, refusing with the typed error vocab.cycle_refused
(errors) — and the same invariant covers replaced_by, so a replacement loop
cannot form. Supersession chains over time are legitimate; ratifying a deprecation whose named
successor is itself already deprecated is surfaced as a scanner finding
(vocab.deprecated_successor) rather than refused, because that is a judgment call, not a law.
Mappings between vocabularies are SSSOM-shaped claims of kind term_mapping: subject CURIE,
object CURIE, predicate (exactMatch, closeMatch, broadMatch, narrowMatch, relatedMatch),
justification (human-curated, lexical, logical), note. A mapping is never a merge — both terms
persist, and the mapping is revocable by supersession. Because a ratified mapping becomes shared
structure of two vocabularies, its ratification requires dreamer(review) on both endpoints'
vocabularies with ratifier ≠ proposer — the same argument workareas makes for
workarea edges, stated as an extension of that rule. Cross-tenant mappings are
RS-3, future.
Where the humans sit
Volume is AI work. Any writer key proposes terms — mid-task, at agent speed — and dreamer crews
drain the review queue the way they drain every other queue; provenance distinguishes agent from
human authors without privileging either. The human loop is placed by human_required_classes
exactly where a mistake is expensive and judgment-shaped: by shipped default, a deprecation with
replaced_by (automation rewires against it), an exactMatch mapping (shared structure of
two vocabularies), and a release pin (served as a diff a person can actually judge). Everything
else — new terms, edits, the other mapping predicates, hierarchy ratified in batch at release —
drains through AI dreamers. The full defaults table is in
ADR-0021 d7, every row tenant-tunable.
The routing is deterministic, not ceremonial: vocab_release routes by kind, and the two
body-field classes route through the vocab scanner's finding classes
(vocab.deprecation_replaced_by, vocab.mapping_exactMatch), which the shipped default policy
names in human_required_classes. The stated coupling: that gate is only as strong as the scanner
being configured — scanner selection is journalled, so an auditor can always answer "was the human
gate active". Auditable, not unmissable.
What "human" means is itself an honesty boundary. A human verdict rests on a journalled,
issuance-time attestation — a key attribute held_by: human, attested by the issuing admin and
shown in evidence exports (roles-and-keys). The server verifies the
attestation, never the humanness. oml doctor warns when a policy names human-required classes
and no attested-human dreamer key is in scope — the queue would otherwise fill silently.
The zero-human configuration is blessed. A 100%-agent tenant may tune every human-required
class off and run vocabularies end to end with zero humans — explicitly supported, under four
named conditions (ADR-0021 d12): the replaced_by cycle
invariant (unconditional), no auto-cut releases (a release is always an explicit ratified act),
collusion statistics that cover edge ratifications and slice by kind, and the doctor orphan and
churn signals active. Under those four, a bad vocabulary act degrades to visible, auditable and
reversible — the same posture ordinary claims already hold.
Validation: the policy and the scanner
Claims gain an optional terms: [CURIE, …] field — clear, non-erasable annotation metadata that
enters the signed bytes and keeps labelling a claim even after its body is erased. Whether anyone
checks those citations is a per-workarea choice: vocabulary_policy {vocabulary, release,
enforcement: require | annotate | off}, default off (admission owns the
policy objects). The scanner is a plugin, deliberately not in the default scanner set — a
tenant that never registers a vocabulary never runs it.
The scanner checks that cited CURIEs exist and are active against the pinned release, and the
scan verdict records that release id alongside the scanner version — determinism is pinned, so no
later vocabulary change retro-invalidates a past verdict. enforcement: require is refused
without a pinned release; trunk-current checking is annotate-only. The two modes have intended
users:
- require — stable, pinned vocabularies: citing an unknown or inactive CURIE holds or vetoes
the submission, with typed errors (
vocab.unknown_curie,vocab.inactive_curie). - annotate — evolving crews: a citation of a not-yet-active term records the finding
vocab.term_pending(the CURIE exists but is held — distinct from unknown) and never blocks. Submit now, re-cite after promotion.
CURIE citations fail closed within the writer's effective read scope, exactly as derives_from
does; the scanner can hold or veto, never promote (plugin law,
ADR-0019).
The agent loop, end to end
The read surface is thin and reader-ceiling, failing closed as absence: GET /v2/vocab/suggest
(deterministic prefix+label typeahead), GET /v2/vocab/terms/{curie},
GET /v2/vocab/terms/{curie}/hierarchy?direction=parents|children|ancestors|descendants (closure
lookups), GET /v2/vocab/mappings?term= (http-api); over MCP, one tool,
oml_vocab, with an action field. Recall gains a terms filter and
expand_terms: none | descendants — expansion resolves against the release pinned by the
vocabulary that owns the prefix and widens a filter before retrieval; steering order, k and
max_tokens are untouched (retrieval).
A working agent's loop, with no human in sight:
- Cite. Before writing,
oml_vocab suggestoffers matching terms; the claim carriesterms: [biz:churn-risk]and, underannotate, nothing ever blocks. - Propose mid-task. No term fits, so the agent submits one —
oml_remember, kindterm— and keeps working. Underannotate, citing the held CURIE recordsvocab.term_pending; after a curator-crew dreamer promotes the term, the agent re-cites and the finding class goes quiet. - Self-heal on deprecation. A later citation of a deprecated term returns
vocab.inactive_curiecarrying the successor CURIE(s) and relation in its details; forreplaced_bythe rewrite is a mechanical one-hop, andoml_vocab getreturnsterm_statusand successor pointers for the same fix at read time.considersuccessors are a judgment call, which is why they are a list and not a rewrite rule.
Vocabulary rows themselves stay out of the shared FTS and vector indexes by default — reference data must not drown organic memories in recall (retrieval); a vocabulary workarea's policy may opt in, mirroring ADR-0020 d11.
What this is not
- No annotation or NER.
suggestis deterministic matching; interpreting claim content to propose terms is client-side crew work. The provider runs no model, here as everywhere (admission). - No reasoner. No OWL semantics — only the materialized transitive closure of ratified
is_a. Queries agents actually ask are answered as lookups; inference that cannot be priced or explained in an evidence export is declined. - No auto-import. An external ontology enters as an
ADR-0020 dataset import whose mapping spec declares
kind=term— post-v1, profiled as RS-11. Until then, external terms are proposed like any others.
Curation economics
A junk-drawer vocabulary — registered, half-filled, unreviewed — is the designed-against failure
mode, and the defenses are structural, not exhortative. A vocabulary workarea requires a named
curator crew at creation. oml doctor flags a vocabulary with no active dreamer grant as
orphaned and names "propose freeze" as the next action; retirement is the ordinary two-party
status ceremony, and a frozen vocabulary keeps serving reads forever. The prefix permanence
confirmation at creation makes the one irreversible act deliberate.
The honest load figure: with shipped defaults, an active vocabulary generates ≈7 human verdicts
per week — a design estimate, not a measurement; review load lands on
FR-2's measured throughput, and expansion and mapping quality are
FR-20's to measure. Mapping campaigns that would swamp that budget take
the campaign path: a journalled temporary policy change, reverted after. And one blast radius is
stated plainly: a wrong exactMatch in v1 misleads only clients that chose to follow it —
expansion is descendants-only — yet it stays human-required, because a ratified mapping is shared
structure of two vocabularies and future features will build on it.
Decision record
This page's decisions are registered as
ADR-0021 (decision register). The
policy objects and scanner context are owned by admission; roles and the
held_by: human attestation by roles-and-keys; recall semantics by
retrieval; the import path by
ADR-0020.