# Reasoning & inference

Source: https://docs.littlebigbrain.com/guides/reasoning/

A query can return the facts you wrote and the facts that follow from your
schema. Say the schema states that an active study site is a study site. A query
for study sites can then include the active ones.

You choose this per query with the `entailment` option. Stored data does not
change. This page uses the `recall` graph from
[Build with JSON records](https://docs.littlebigbrain.com/guides/without-rdf/). The RDF words are explained in
[core concepts](https://docs.littlebigbrain.com/start/concepts/#rdf-under-the-hood).

## What `entailment` does

RDFS and OWL are W3C standards for statements about types and links.
`rdfs:subClassOf`, for example, states that every record of one type also has
another type.

The `entailment` option selects which of these statements a query applies:

| `entailment` | The query also returns | Statements it reads |
| --- | --- | --- |
| `none` (default) | Nothing extra: the facts as written. | None. |
| `subclass` | Records of every subtype. A query for study sites matches active study sites. | `rdfs:subClassOf` |
| `rdfs` | Everything `subclass` returns. Values written under a more specific link or field. Types that follow from a link's declared source or target type. | `rdfs:subClassOf`, `rdfs:subPropertyOf`, `rdfs:domain`, `rdfs:range` |
| `owl` | Everything `rdfs` returns. Equal types, links and records, reverse links, symmetric and transitive links, link chains, and class rules. | The [OWL statements](#supported-owl-statements) below. |

In RDF, a type is a **class** and a link or field is a **property**. A
"subproperty" is a more specific link or field.

Reasoning follows these rules:

- It runs when the query runs. No fact is copied, and no index is rebuilt.
- It reads the schema statements of the commit the query reads. The same query
  at the same commit returns the same rows.
- A new schema statement counts from the commit that writes it. A strong read
  applies it at once. An eventual read applies it once that commit is
  published.
- With `as_of_commit_seq`, a query reasons over the data and the schema of that
  earlier commit.
- A `super_types` link is part of the JSON schema, which changes without a
  commit. A strong read applies a new link at once. An eventual read applies
  it after the graph republishes. A read of an earlier commit can apply links
  added later.

### Where you set it

| Surface | How to pass it |
| --- | --- |
| `POST /v1/query/sparql-text` | Body field `"entailment": "subclass"` |
| `GET` or `POST /sparql` | URL parameter `entailment=subclass` |
| TypeScript SDK | `lbb.sparqlText({ query, entailment })` or `lbb.sparqlRows({ query, entailment })` |
| Python SDK | `lbb.sparql(query, entailment="subclass")` |
| MCP | `lbb_query` with `mode: "sparql"` and `entailment` |
| Console | **Knowledge graph → Ontology → Inferred facts**, see [In the console](#in-the-console) |

The structured JSON query, `POST /v1/query/sparql`, has no `entailment` field.
It matches the facts as written. An unknown `entailment` value returns HTTP 400
`invalid_request`.

## Add a type hierarchy to JSON records

A JSON schema can list `super_types` for a type. The graph stores each super
type as an `rdfs:subClassOf` statement. A query can then reason over it. The
`recall` graph declares `ActiveStudySite` with `super_types: ["StudySite"]`, so
the graph holds this statement:

```text
<https://littlebigbrain.com/class/activestudysite> <http://www.w3.org/2000/01/rdf-schema#subClassOf> <https://littlebigbrain.com/class/studysite> .
```

Each type you declare in JSON has a global name, an IRI. It is the type name in
lowercase after `https://littlebigbrain.com/class/`.

To give an existing type a parent, call `evolve` with `add_super_types`.
Repeating a link does nothing. A strong read applies a new link at once. An
eventual read applies it once the graph republishes, usually within seconds.

The code below adds the link and waits for publication. Then it runs one
query twice, without and with `entailment`. Add each snippet on this page to the
end of one file, as in the [quickstart](https://docs.littlebigbrain.com/start/quickstart/).

### TypeScript

```ts
import { LbbClient } from "@littlebigbrain/client";

const lbb = new LbbClient({
  baseUrl: process.env.LBB_URL!,
  apiKey: process.env.LBB_API_KEY!,
  graph: "recall",
});

await lbb.graph("recall").ontology.evolve({
  ops: [{ op: "add_super_types", entity_type: "ActiveStudySite", super_types: ["StudySite"] }],
});
const { head_seq } = await lbb.publicationStatus();
await lbb.waitForPublished(head_seq);

const sites = `
  PREFIX dclass: <https://littlebigbrain.com/class/>
  PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
  SELECT ?site WHERE { ?s a dclass:studysite ; rdfs:label ?site } ORDER BY ?site`;

const asWritten = await lbb.sparqlRows({ query: sites });
const withSubtypes = await lbb.sparqlRows({ query: sites, entailment: "subclass" });
console.log(asWritten.rows, withSubtypes.rows);
```

### Python

```python
import os

from lbb import LbbClient

lbb = LbbClient(os.environ["LBB_URL"], api_key=os.environ["LBB_API_KEY"], graph="recall")

lbb.ontology.evolve({
    "ops": [{"op": "add_super_types", "entity_type": "ActiveStudySite", "super_types": ["StudySite"]}],
})
lbb.wait_for_published(lbb.publication_status()["head_seq"])

sites = """
PREFIX dclass: <https://littlebigbrain.com/class/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?site WHERE { ?s a dclass:studysite ; rdfs:label ?site } ORDER BY ?site"""

print(lbb.sparql(sites).rows())
print(lbb.sparql(sites, entailment="subclass").rows())
```

### cURL

```sh
curl --fail-with-body -sS -X POST "$LBB_URL/v1/ontology/evolve?graph=recall" \
  -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \
  -d '{"ops": [{"op": "add_super_types", "entity_type": "ActiveStudySite", "super_types": ["StudySite"]}]}'

until curl -sS "$LBB_URL/v1/graph/publication-status?graph=recall" \
  -H "Authorization: Bearer $LBB_API_KEY" | grep -q '"state": *"current"'; do sleep 1; done

for mode in none subclass; do
  curl --fail-with-body -sS -X POST "$LBB_URL/v1/query/sparql-text?graph=recall" \
    -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \
    -d "{\"entailment\": \"$mode\", \"query\": \"PREFIX dclass: <https://littlebigbrain.com/class/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?site WHERE { ?s a dclass:studysite ; rdfs:label ?site } ORDER BY ?site\"}" \
    | jq -r '.results | fromjson | .results.bindings[].site.value'
done
```

The two queries return different rows from the same records:

| `entailment` | Rows |
| --- | --- |
| `none` | Oslo Cardiology Study Site |
| `subclass` | Berlin Cardiology Study Site, Oslo Cardiology Study Site |

Berlin is an `ActiveStudySite`. The hierarchy makes it a study site as well.
No record changed.

An agent adds the same link with the MCP tool `evolve_ontology`. See
[MCP for agents](https://docs.littlebigbrain.com/sdks/mcp/#schema-calls). For a class outside the JSON
schema, write the same statement as RDF. Load it with
[RDF import](https://docs.littlebigbrain.com/guides/load-rdf/) or SPARQL Update.

## Answer a question with OWL

OWL statements describe links as well as types. Three statements let one plain
pattern answer the recall question. Which released batches are exposed to the
recall?

```turtle
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix dr: <https://littlebigbrain.com/r/> .
@prefix ex: <https://example.org/recall/> .

dr:produced_from a owl:TransitiveProperty .
ex:recalledBy owl:inverseOf dr:recalls .
ex:exposedTo owl:propertyChainAxiom ( dr:produced_from ex:recalledBy ) .
```

- `owl:TransitiveProperty` makes a chain of `PRODUCED_FROM` links count as one
  link. A batch is then produced from every lot upstream of it.
- `owl:inverseOf` declares `ex:recalledBy` as the reverse of `RECALLS`.
- `owl:propertyChainAxiom` defines `ex:exposedTo` as `PRODUCED_FROM`, then
  `recalledBy`. A batch is exposed to each notice that recalls a lot it came
  from.

The JSON schema already names `RECALLED_BY` as the inverse of `RECALLS`. That
name works as a query alias, `dr:recalled_by`. OWL statements do not see
aliases, so the document declares the reverse link with `owl:inverseOf`.

The code below loads the document with the RDF import. The import adds
`Resource`, `RdfLiteral` and `RDF_TRIPLE` to the graph's schema. After that,
running the guide's `define` call again fails with
`400 ontology_identity_breaking_change`. Use `evolve` for later schema changes.

### TypeScript

```ts
const axioms = `
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix dr: <https://littlebigbrain.com/r/> .
@prefix ex: <https://example.org/recall/> .

dr:produced_from a owl:TransitiveProperty .
ex:recalledBy owl:inverseOf dr:recalls .
ex:exposedTo owl:propertyChainAxiom ( dr:produced_from ex:recalledBy ) .
`;
const loaded = await lbb.graph("recall").facts.importRdf(axioms, {
  format: "turtle",
  idempotencyKey: "recall-axioms-1",
});
await lbb.waitForPublished(loaded.committed_commit_seq!);

const exposed = await lbb.sparqlRows({
  query: `
    PREFIX dclass: <https://littlebigbrain.com/class/>
    PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
    PREFIX ex: <https://example.org/recall/>
    SELECT ?batch WHERE {
      ?b a dclass:releasedbatch ; ex:exposedTo ?notice ; rdfs:label ?batch .
      ?notice rdfs:label "FDA valsartan recall" .
    }`,
  entailment: "owl",
});
console.log(exposed.rows);
```

### Python

```python
axioms = """
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix dr: <https://littlebigbrain.com/r/> .
@prefix ex: <https://example.org/recall/> .

dr:produced_from a owl:TransitiveProperty .
ex:recalledBy owl:inverseOf dr:recalls .
ex:exposedTo owl:propertyChainAxiom ( dr:produced_from ex:recalledBy ) .
"""
loaded = lbb.graph("recall").facts.import_rdf(
    axioms, format="turtle", idempotency_key="recall-axioms-1"
)
lbb.wait_for_published(loaded["committed_commit_seq"])

exposed = lbb.sparql("""
PREFIX dclass: <https://littlebigbrain.com/class/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX ex: <https://example.org/recall/>
SELECT ?batch WHERE {
  ?b a dclass:releasedbatch ; ex:exposedTo ?notice ; rdfs:label ?batch .
  ?notice rdfs:label "FDA valsartan recall" .
}""", entailment="owl")
print(exposed.rows())
```

### cURL

Save the three statements as `axioms.ttl`, then:

```sh
curl --fail-with-body -sS -X POST "$LBB_URL/v1/graph/import/rdf?graph=recall&format=turtle" \
  -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: text/turtle" \
  -H "Idempotency-Key: recall-axioms-1" --data-binary @axioms.ttl

until curl -sS "$LBB_URL/v1/graph/publication-status?graph=recall" \
  -H "Authorization: Bearer $LBB_API_KEY" | grep -q '"state": *"current"'; do sleep 1; done

curl --fail-with-body -sS -X POST "$LBB_URL/v1/query/sparql-text?graph=recall" \
  -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \
  -d '{"entailment": "owl", "query": "PREFIX dclass: <https://littlebigbrain.com/class/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> PREFIX ex: <https://example.org/recall/> SELECT ?batch WHERE { ?b a dclass:releasedbatch ; ex:exposedTo ?notice ; rdfs:label ?batch . ?notice rdfs:label \"FDA valsartan recall\" . }"}' \
  | jq -r '.results | fromjson | .results.bindings[].batch.value'
```

The query returns one row, `Cardiovex batch CVX-A17`. The batch reaches the
recalled lot through the granulation lot. With `entailment: "none"`, the same
query returns no rows, because no `exposedTo` fact is stored.

## Supported OWL statements

`entailment: "owl"` applies everything `rdfs` applies. On top of that, it reads
these OWL statements from the schema:

| Group | Statements | What a query returns |
| --- | --- | --- |
| Equivalence | `owl:equivalentClass`, `owl:equivalentProperty` | Equal types share members, and equal links share values, in both directions. |
| Link rules | `owl:inverseOf` | A fact answers from either direction. This includes facts on a subproperty of either side: `x directlyOwns y` answers `y ownedBy x` when `directlyOwns` is a subproperty of `owns`. |
| | `owl:SymmetricProperty` | The link matches in both orders. |
| | `owl:TransitiveProperty` | Chains of the link and its subproperties count as one link. |
| | `owl:propertyChainAxiom` | A chain of links derives the head link. A chain may use another chain's head, up to 8 levels and 256 expanded path terms. Chains must not be recursive. |
| Class rules | `owl:unionOf` | Each member type is a subtype of the union. |
| | `owl:intersectionOf` | The intersection is a subtype of each part, and a record in every part is a member. |
| | `owl:oneOf` | The listed records are members of the type. |
| Restrictions | `owl:hasValue` | A record with the value is a member, and a member has the value. |
| | `owl:someValuesFrom` | A record linked to a record of the filler type is a member. |
| | `owl:allValuesFrom` | A record reached from a member through the restricted link gets the filler type. |
| Identity | `owl:sameAs` | Records in one `sameAs` group are one record. A fixed IRI matches through any member, and a variable binds every member. |

The restriction rules combine with inverse, symmetric, transitive,
subproperty, chain and `hasValue` facts. A restriction names its link with
`owl:onProperty`.

## Limits

Some schemas are outside what the regimes can answer exactly. The query then
fails with HTTP 400 `invalid_request`. It never returns a partial answer. The
message starts with `OWL entailment is unavailable` or
`RDFS entailment is unavailable`.

| Limit | Value | Regimes refused |
| --- | --- | --- |
| Schema closure (type pairs, link pairs and domain and range pairs) | 1,000,000 pairs | `subclass`, `rdfs`, `owl` |
| RDF list cells behind `unionOf`, `intersectionOf`, `oneOf` and chains | 100,000 | `owl` |
| Property chain composition | 8 levels, 256 expanded terms, no recursion | `owl` |
| `owl:sameAs` statements | 100,000 pairs | `owl` |
| One `owl:sameAs` group | 256 records | `owl` |

A query without `entailment` keeps working on the same graph.

These are not inferred:

- Existential superclasses and facts derived from cardinality.
- Documents named by `owl:imports`. Import each document instead.
- Axiomatic RDF triples, container membership, and rules about `rdfs:Resource`
  or `rdfs:Datatype`.
- Schema statements whose subject or object is a blank node. OWL restriction
  classes are the exception.

Cardinality declarations are checked as validation rules. They appear in the
[published conformance report](https://docs.littlebigbrain.com/guides/sparql-and-shacl/).

Entailment does not apply inside a property path, such as
`?b ex:exposedTo/rdfs:label ?x` or `dr:produced_from+`. Write each step as its
own pattern. A path over the stored statements walks a type hierarchy without
entailment: `?s a/rdfs:subClassOf* dclass:studysite`.

## Performance

- The schema rules are computed once per published commit and schema version.
  The first query that uses `entailment` computes them. Later queries reuse
  them.
- A query pattern that no schema statement mentions runs the same plan with and
  without entailment.
- Transitive links and chains are read as index traversals from the bound end
  of the pattern. The normal query time limit applies.
- An aggregate over a large type with subtypes reads every member. The answer
  is exact, and the cost grows with the member count.

## Rules at query time

A query cannot run custom inference rules. A request with `reason: true`
returns HTTP 400 `invalid_request`. The message names the `entailment` regimes
to use instead. `reason=True` in the Python SDK's `sparql()` returns the same
error.

## In the console

**Knowledge graph → Ontology** always lists a class with its subclasses' records. The
**Inferred facts** switch adds what the ontology implies, by OWL reasoning, to
instance lists and records. Inferred results are marked in amber. Query errors
appear inline, so you can retry or turn the switch off.

**OWL axioms** shows the schema statements as diagrams, grouped by type and
link. The legend explains three marks:

- A solid arrow is a link in your data.
- An amber dashed arrow is what reasoning adds.
- A **Validation** badge marks a rule that is checked and never inferred.

For example, the path `memberOf` then `owns` is drawn above a dashed `worksOn`
arrow. Both connect the same two records. A link with a declared source and
target type starts with a **Connects** diagram.

More behavior of the **OWL axioms** view:

- Hover or tap the ⓘ next to a rule title to read it as a sentence.
- Search, or filter by **Types** and **Relationships**.
- `owl:sameAs` links between individuals appear on the individual's card in
  **Ontology**.
- The header arrow opens the entity. **OWL details** shows the full IRI,
  predicates and expressions such as `worksOn some Project`.
- Lettered nodes stand for any record. They are not query results.
- Diagrams show stored schema statements. They do not change with the
  reasoning mode.
- Cards come in pages of 24. The view reads at most 20,000 statements and labels
  a partial result.
- Imported ontology metadata stored outside the RDF graph does not appear here.

**Knowledge graph → Ontology** shows one class or entity at a time. An instance opens as
a record: its values, and its relationships beside them. **Map** shows it among
its links. Click a linked item to move to it. The trail above the view leads
back.

A class card shows its definition. **Browse instances** pages through every
member, with an exact count and a label filter. With **Inferred facts** on,
members added by reasoning are marked **inferred**.

## Why reasoning helps an agent

- **The same answer each run.** Transitive links, type hierarchies and
  classifications follow from the schema. An LLM that derives them itself can
  answer differently each call, and it spends tokens.
- **Broader answers without more writes.** A query for a general type returns
  the records of its subtypes. The agent does not list every subtype.
- **One record under many names.** With `owl:sameAs`, an agent writes facts
  under the IRI it has. It reads the facts written under every equal IRI.

## Related

- [Build with JSON records](https://docs.littlebigbrain.com/guides/without-rdf/): the `recall` graph this page
  uses.
- [Load RDF data](https://docs.littlebigbrain.com/guides/load-rdf/): import RDF and OWL documents.
- [Query and validate](https://docs.littlebigbrain.com/guides/sparql-and-shacl/): the query surfaces that
  reasoning applies to.
- [Replay an answer](https://docs.littlebigbrain.com/guides/time-travel-audit/): pin a query to an earlier
  commit.
- [Core concepts](https://docs.littlebigbrain.com/start/concepts/)
