Ontology suggestions (experimental)
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.
What a suggestion holds
Section titled “What a suggestion holds”- 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.
Review in the console
Section titled “Review in the console”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.
File a suggestion
Section titled “File a suggestion”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().
From an agent
Section titled “From an agent”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.
From an integration
Section titled “From an integration”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.
Identities
Section titled “Identities”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);changemay be empty whenfactsis set.- The facts are part of the proposal. Filing other facts under the same
keyrevises 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 recordsfacts_commit:commit_seq,request_id,no_op(the facts were in the graph already) andowned(whatreplace_ownedremoved). - 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.
Accept some of the facts
Section titled “Accept some of the facts”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.
Routes
Section titled “Routes”| 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.
Decide a pattern at once
Section titled “Decide a pattern at once”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.