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 type with metadata.
  • Concepts that map cleanly to POPE. A Team is just an Organization.
  • Transient state. The ontology is for durable schema; transient state goes in workspace metadata.

Creating a custom entity type

Web UI

Open the Ontology ViewerEntity 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

  1. Start minimal. Add only the properties you'll actually query. You can always add more.
  2. Name consistently. Prefer nouns for types (contract, not contracting). Prefer verbs for relationships (authored, not author_of).
  3. Use POPE where it fits. A customer is just an organization — don't create a parallel type.
  4. Explicit is better than implicit. Prefer typed properties (number) over free-form strings.
  5. Document the "why." Use the Viewer's description field to capture motivation — future Analysts will thank you.
  6. 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

  1. Add the property as optional first.
  2. Backfill old records: brrain docs reingest --type contract.
  3. Once backfill is complete, mark the property required.

Renaming a property

  1. Add the new property as optional.
  2. Backfill: copy values from the old property.
  3. Deprecate the old property (records still have it but schema hides it).
  4. Remove the old property in a major version bump.

Removing an entity type

  1. Subsidize the type in the Viewer (hides from queries without deleting underlying records).
  2. Observe for a retention window (default 90 days) to catch surprises.
  3. 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 ViewerVersions → 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.