Skip to content

Ontology suggestions (experimental)

View Markdown

A suggestion is a proposed change to your ontology that waits for a person. Integrations, agents and people all file the same kind of suggestion. You read it, discuss it, and accept or dismiss it.

Accepting applies the change to the ontology as it is now. Dismissing keeps the ontology as it is and records your reason.

To get suggestions from the text of your instances (transcripts, documents), see Suggest ontology changes from text.

  • A title and a rationale, for example “Add class Deal”.
  • An anchor: the class, property or relation it is about.
  • An origin: the integration, agent or person that filed it.
  • The change: the same operations as POST /v1/ontology/evolve.
  • Identities: records from different sources that describe one thing.
  • Facts: graph facts (links and field values) that accepting commits.
  • Evidence: record counts, fill rate, source fields and sample records.
  • The impact: a dry run against the current ontology.
  • A discussion: comments, and the events of its review.

Open Review in the sidebar. It is the one inbox of questions for a person, and its badge counts the open items. The inbox lists open suggestions first. To see one kind at a time, pick a type: Facts (the items of the review workflow to check), Links (the link reviews of documents.link) or Ontology (a new class, relation or property, and other changes). Each type shows its count. Status and Source are menus above the list: the status of the suggestions shown (open by default), and who filed them (integrations, agents or people). The address keeps the type (&kind=links), so a link to the page keeps it. Each row shows the start of its rationale, or the document, page and confidence its evidence comes from. Select one to see the change as a diff, the evidence and the impact. An evidence sample that names a line of a page shows that line, the model’s answer, and View on page.

  • Accept applies the change.
  • Edit, then accept lets you rename a class or change a value type first.
  • Dismiss asks for a reason.

A suggestion with facts shows them in a table: one row per link, with its confidence and its evidence on one line. A suggestion with facts and no change has no diff and no dry run. Accepting it shows the commit that wrote the facts.

A link review of documents.link opens with where the link goes From (each mention, its span marked in its line, with its document and page) and To (the entry, its title and its document), and one sentence with the model’s answer: “The model found no entry (68%). Best candidate: Item 212 at 31%.” Link to Item 212 accepts it; Not a link dismisses it.

When the ontology changed since the dry run, Check again runs it again.

With more than 20 open suggestions, the tab opens on Patterns: one card per group of suggestions that ask one decision, for example “Link “#.#.#” in manual.pdf to Section #.#.#“. A card names the workflow, the count and the confidence range, and shows three samples that open in full. Leave a sample out with its box, or Show all to leave out any of them. Then Accept all or Dismiss all decides the rest, after a confirmation that names the count and the workflow. The tab says what was accepted and what was skipped, and why. Items lists the suggestions one by one.

Send the change with a stable key. Filing again with the same key revises the suggestion instead of adding another one.

import { LbbClient } from "@littlebigbrain/client";
const client = new LbbClient({
baseUrl: process.env.LBB_URL!,
apiKey: process.env.LBB_API_KEY!,
});
await client.ontology.suggestions.create({
key: "crm/annual-revenue",
title: "Add annual_revenue to Organization",
rationale: "99% of CRM companies carry a yearly revenue figure.",
anchor: { kind: "class", name: "Organization" },
origin: { kind: "person", id: "ana@example.com" },
change: [{ op: "add_property", name: "annual_revenue", value_type: "f64" }],
evidence: { records: 1204, fill_rate: 0.99 },
});

List and decide:

const { suggestions, counts } = await client.ontology.suggestions.list({
status: "open",
});
await client.ontology.suggestions.accept(suggestions[0].suggestion_id, {
author: "ana@example.com",
});

Python has the same calls on client.ontology: suggestions(), suggestion_create(), suggestion_accept() and suggestion_dismiss().

The MCP server lets an agent propose a change but not accept it:

{
"action": "suggest_ontology_change",
"title": "Make Contractor a subclass of Person",
"rationale": "12 tickets name contractors as assignees",
"change": [
{ "op": "add_super_types", "entity_type": "Contractor", "super_types": ["Person"] }
]
}

Send it to lbb_configure. lbb_inspect with action: "ontology_suggestions" lists the suggestions and their state, with fact_count for each one. An agent can also send facts (see Facts); change may then be empty.

A new integration reviews first. It files one suggestion per class that needs a change. Streams that already fit keep syncing. After you accept a suggestion, the next sync writes the records that waited. If you rename a class or a property before you accept, the integration uses your name.

Two sources can feed one class. Then one person can exist twice, once per source. A suggestion can list such records as identities. Each identity holds 2 to 10 records, a reason and a confidence.

{
"members": [
{ "type": "Person", "name": "Ada Lovelace", "key": "hubspot-main:contacts:1" },
{ "type": "Person", "name": "Ada Lovelace", "key": "linear-main:users:9" }
],
"reason": "email ada@acme.test",
"confidence": 1
}

A record is named by its class and its import key. The first record is the canonical one.

Accepting links each later record to the first with a SAME_AS edge. In SPARQL it is https://littlebigbrain.com/r/same_as; follow it to read the records together.

The edge keeps each record’s current name, even after a rename at the source. A record that is not in the graph is refused, so accept never creates one.

The ontology needs a SAME_AS relation. When it has none, the change must add one. Otherwise the accept fails and writes nothing.

To link only some records, send the kept ones in identities on accept. Each kept identity must be part of one proposed identity. The server keeps the original list in proposed_identities.

A producer that writes graph facts can ask a person first. A developer workflow files the links below its confidence bar this way, with origin {kind: "agent", id: "starter:<name>"}. facts holds what accepting commits:

{
"triplets": [{
"source": { "type": "Room", "name": "R 1.04" },
"relation": "HAS_COMPONENT",
"target": { "type": "Component", "name": "Fire door T30" },
"confidence": 0.62,
"evidence": { "text": "Tür T30 RS", "source_id": "doc:plan-104" }
}],
"entity_properties": [{ "type": "Room", "name": "R 1.04", "properties": { "area": 18.5 } }],
"replace_owned": { "subjects": [{ "type": "Room", "name": "R 1.04" }], "relations": ["HAS_COMPONENT"] },
"summary": "One component of room R 1.04"
}

triplets and entity_properties take the shapes of POST /v1/graph/commit. replace_owned replaces the producer’s owned links and fields on its subjects in the same commit (see Write records as JSON).

await client.ontology.suggestions.create({
key: "starter:rooms/r-1-04",
title: "Link room R 1.04",
origin: { kind: "agent", id: "starter:rooms" },
change: [],
facts,
});
const accepted = await client.ontology.suggestions.accept(id);
console.log(accepted.facts_commit?.commit_seq);
  • change may be empty when facts is set.
  • The facts are part of the proposal. Filing other facts under the same key revises the suggestion.
  • Accepting commits the facts after the change and after any identity links, with the idempotency key ontology-suggestion:<id>:facts:r<revision>, so a repeated accept writes nothing new. The suggestion records facts_commit: commit_seq, request_id, no_op (the facts were in the graph already) and owned (what replace_owned removed).
  • The commit checks the facts against the ontology after the change, so they can use a class that the change adds. A refused commit leaves the suggestion open.
  • At most 200 links and field rows, 64 KiB of JSON, and a summary of 500 characters.
  • On an RDF-native graph the accept writes the facts as RDF: the statements a records graph shows for them, under the same IRIs.
  • On an RDF-native graph the accept links identities the same way. It reads each member from the graph’s exact view, which includes commits that are not published yet, so a member written a moment ago is found. A member the graph lacks still refuses the accept, and nothing is written.
  • Dismissing writes nothing to the graph.

A person can leave facts out. Send facts with the accept: the triplets and rows to keep, each exactly as the suggestion holds it. A fact that the suggestion does not hold is refused with 400, so an accept never adds a fact. facts.required names rows that the other facts need, such as the class of a new instance. Leave one out and the accept is refused; dismiss the suggestion instead.

const item = await lbb.ontology.suggestions.get(id);
const kept = item.facts!.entity_properties!.filter((_, row) => row !== 2);
await lbb.ontology.suggestions.accept(id, { facts: { entity_properties: kept } });

The suggestion keeps the proposed facts in proposed_facts. An accept of a superseded suggestion fails with 409 suggestion_superseded.

The review workflow files one item per instance, and writes the kept facts after the accept: see Review proposed facts.

Route Does
POST /v1/ontology/suggestions file or revise by key, then dry-run
GET /v1/ontology/suggestions list, newest first, with counts per status
GET /v1/ontology/suggestions/detail?suggestion_id= one suggestion in full
POST /v1/ontology/suggestions/validate?suggestion_id= dry-run again
POST /v1/ontology/suggestions/accept?suggestion_id= apply the change, or an edited one, then link the identities and commit the facts
POST /v1/ontology/suggestions/dismiss?suggestion_id= decline, with a reason
POST /v1/ontology/suggestions/supersede?suggestion_id= the producer withdraws it
POST /v1/ontology/suggestions/comment?suggestion_id= add a comment
GET /v1/ontology/suggestions/patterns the suggestions grouped by pattern, the largest first, with samples and every id
POST /v1/ontology/suggestions/accept-batch accept up to 500 suggestions, each as one accept does
POST /v1/ontology/suggestions/dismiss-batch dismiss up to 500 suggestions, each as one dismiss does

Accept is safe to repeat. A second accept returns the accepted suggestion.

A workflow that reads documents files many suggestions that ask the same question: link the span 1.2.3 to the heading Section 1.2.3, set the maximum pressure of a pump from “Operating pressure | ≤ 3.0 bar”. The server groups them by pattern: the same producer, document, kind of fact and shape of text, with each run of digits written as #. You check a few samples and decide the whole pattern.

const { patterns } = await client.ontology.suggestions.patterns({ status: "open" });
const [largest] = patterns;
// For example: Link "#.#.#" in manual.pdf to Section #.#.# (REFERS_TO)
console.log(largest.pattern.label, largest.count, largest.confidence);
const result = await client.ontology.suggestions.acceptBatch({
suggestion_ids: largest.suggestion_ids,
shown: largest.samples.map((sample) => sample.suggestion_id),
pattern: largest.pattern.key,
});
console.log(result.accepted, result.skipped);

Each suggestion is decided as one accept or dismiss decides it. A suggestion its producer replaced in the meantime is skipped, and the result names the one in its place. An accept of many writes their facts in one commit (up to 1,000 facts each) and their changes in one evolve. Each decided suggestion records the batch, and which samples you saw.