Extending the ontology — bRRAIn Docs
Create custom entity types, properties, and relationships; inherit from POPE; migrate data.
Extending the ontology
bRRAIn's POPE ontology is a thin, universal base. Every real deployment extends it with domain-specific entities and relationships. This page covers the full workflow.
When to extend
Add a custom entity type when:
- Your domain has a recurring concept distinct from POPE (e.g.,
Contract,Patient,Transaction). - You query across this concept consistently, not just once.
- You want the Handler to classify and route records of this type automatically.
Do not extend the ontology for:
- One-off data. Use a generic record
typewith metadata. - Concepts that map cleanly to POPE. A
Teamis just anOrganization. - Transient state. The ontology is for durable schema; transient state goes in workspace metadata.
Creating a custom entity type
Web UI
Open the Ontology Viewer → Entity Types → New. Fill in:
- Name (canonical,
snake_case):contract - Display label:
Contract - Description: Any legal contract tracked by the system
- Inherits from: (optional) another entity type
- Properties: list each with name, type, required flag
CLI
brrain ontology add-type contract \
--property title:string:required \
--property effective_date:date:required \
--property amount:number
API
_, err := client.Ontology.AddEntityType(ctx, &sdk.AddEntityTypeInput{
Name: "contract",
DisplayName: "Contract",
Properties: []sdk.PropertyDef{
{Name: "title", Type: "string", Required: true},
{Name: "effective_date", Type: "date", Required: true},
{Name: "amount", Type: "number"},
},
})
All three paths produce the same ontology version bump.
Adding custom properties
Evolve an existing entity type by adding properties:
brrain ontology add-property contract jurisdiction:string
Adding is safe — existing records see the new property as null until backfilled. Removing is heavier (see Migrating data below).
Defining new relationships
brrain ontology add-relationship \
--name involves \
--source contract \
--target organization \
--properties role:string
Or via API:
client.Ontology.AddRelationship(ctx, &sdk.AddRelationshipInput{
Name: "involves",
Source: "contract",
Target: "organization",
Properties: []sdk.PropertyDef{{Name: "role", Type: "string"}},
})
Custom type inheritance
Entity types can inherit from another type. The child gets all parent properties plus its own:
brrain ontology add-type insurance_policy \
--inherits contract \
--property coverage_limit:number:required \
--property insurer:organization:required
Queries over contract automatically include insurance_policy records (polymorphic retrieval).
Validation rules
Attach validators to entity types:
# ontology/validators/contract.yaml
entity: contract
rules:
- name: effective_date_before_expiry
applies_to: [effective_date, expires_on]
expression: "effective_date < expires_on"
severity: error
- name: amount_positive
applies_to: [amount]
expression: "amount > 0"
severity: warning
Validators run before Gate 1. Errors block writes; warnings surface in the audit log.
Best practices
- Start minimal. Add only the properties you'll actually query. You can always add more.
- Name consistently. Prefer nouns for types (
contract, notcontracting). Prefer verbs for relationships (authored, notauthor_of). - Use POPE where it fits. A
customeris just anorganization— don't create a parallel type. - Explicit is better than implicit. Prefer typed properties (
number) over free-form strings. - Document the "why." Use the Viewer's description field to capture motivation — future Analysts will thank you.
- Let the Handler help. Turn on Handler ontology suggestions in staging; review its proposals weekly; promote the good ones.
Migrating data with ontology changes
When you add or modify an ontology, existing records don't automatically comply. The migration workflow:
Adding a required property
- Add the property as optional first.
- Backfill old records:
brrain docs reingest --type contract. - Once backfill is complete, mark the property required.
Renaming a property
- Add the new property as optional.
- Backfill: copy values from the old property.
- Deprecate the old property (records still have it but schema hides it).
- Remove the old property in a major version bump.
Removing an entity type
- Subsidize the type in the Viewer (hides from queries without deleting underlying records).
- Observe for a retention window (default 90 days) to catch surprises.
- If safe, delete the type — this is a major version change and requires dual approval.
Rolling back
If a change goes wrong, roll back via the Ontology Viewer → Versions → Rollback. The old schema is reinstated atomically; records that were created against the newer schema are preserved but marked as "future-schema" until you re-forward the ontology.
Rollbacks are rare but important — they're why every Handler proposal is versioned and why the Viewer dashboard is the single source of truth for ontology governance.