Engine changelog
Updates to the database engine, query APIs, and developer tools. For the hosted console, accounts, and billing, see the Cloud changelog.
Unreleased
Section titled “Unreleased”Questions get 60 seconds
Section titled “Questions get 60 seconds”POST /v1/query/ask now stops at a 60-second read deadline. Other reads keep
25 seconds. A question that needs several model turns and queries now has time
to answer.
While the model writes a turn, the question holds no server slot. Other
requests can run in that time. If the server stays full after a turn, the loop
stops and returns the best rows it read. Its error says the server stayed
busy. No client change is needed. The SDKs already wait up to 120 seconds.
Keep a generated file
Section titled “Keep a generated file”DELETE /v1/files/expiry?sha256= removes the expires_at of a generated file,
one that a workflow run wrote. The file then stays until a person deletes it,
and GET /v1/files lists it. A repeat answers the same record. An upload of
those bytes answers 409 file_generated. The bytes are not a source. No route
writes a generated file yet.
Commits stay inside what a workflow owns
Section titled “Commits stay inside what a workflow owns”A commit under a workflow turn must write only the properties its workflow
lists in owns.properties. A property outside them answers
403 write_outside_owns with the IRIs in details, and nothing is written. A
ValueSource entity may carry property, value and support whoever the
owner is. A commit with property_merge: "replace" is refused too. Replace
erases fields of the entity that the workflow may not own.
At first the server writes such a commit as before. It only counts what it would refuse. No client change is needed.
Check proposed values against their files
Section titled “Check proposed values against their files”POST /v1/workflows/activities/ground checks values that a model proposes.
Each item names a file, a place in it, a property, a value and a quote. The
server answers one verdict per item, in order, with no model call.
- Checks. The file is live and its upload is the one the item read. The place exists. The quote is in the text there. The property is in the ontology. The value takes its type, and a number comes in the unit of its property, converted with a fixed factor.
- Places.
page=Nfor a parsed PDF,line=a,bfor text and Markdown, androw=a-bfor CSV and TSV. - Evidence. A grounded item carries the typed value and the evidence of its quote, with the box of the lines on a PDF page.
- Bounds. A request holds 16 items from 8 files. It scans 4 MiB, and an
item scans 1 MiB of its file. A PDF’s stored pages may hold 16 MiB. A place
past a bound gets
scan_budget. A request past 10 seconds answers503 ground_timeoutwith no verdict. - Values. A value is at most 100 bytes and a unit 24 bytes. Only a number is read apart from its unit. A text keeps its words.
- Open to all. The route calls no managed model, so Free accounts use it.
Rust source convenience wrappers
Section titled “Rust source convenience wrappers”Remove the uncalled Rust helpers lbb_fts::search_bm25 and
GraphStore::warm_object. This is a downstream Rust source-API break for Git,
path or other crate consumers; external use is unknown. The retained
Bm25Index::build(documents)?.search(query, top_k) performs the same legacy
BM25 operation. Current operator warming keeps its bounded graph plan; it is
not equivalent to an arbitrary-key whole-object read.
No HTTP/SDK route, package version, persisted format, current RDF/class embedding search or shared ANN/reranking/Ask behavior changes. This source notice does not announce package publication, deployment or completion of a retirement window.
October 2026
Section titled “October 2026”28 October: Hosted workers parse files in a parse slot
Section titled “28 October: Hosted workers parse files in a parse slot”On a hosted cell the workers no longer start pdftotext and pdfimages.
Each worker process has a parse slot: a container with its own user, its own
process view, no network, a read-only root and a 16 MiB work directory. The
slot runs the program with an empty environment, no capability, and limits on
memory, CPU time, file size, open files and processes. It kills the program at
its wall limit and empties the work directory after every file.
- Checked result. The worker checks what the slot returns before it stores a page: valid UTF-8, at most 2,000 pages in order, a page size that is a number of points, line ids and texts that match their words, and boxes from 0 to 1. A result that fails a check is a parse failure and stores nothing.
- Output cap. A file whose text output passes 128 MiB now fails with
the parser wrote more than 134217728 bytes. The cap was 256 MiB. - New settings.
LBB_PARSE_MODEandLBB_PARSE_SLOT_DIRSfor the worker.LBB_SERVING_PARSEfor the server:offmakes the serving, ingest and maintenance processes start no Poppler. It defaults toon. - Roll order. Roll the pool: the slots start first, then the workers.
Turn
LBB_SERVING_PARSEoff only after the workers parse.roll-cell.shrefuses a deploy that does not setLBB_WORKER_WORKFLOWSto includedocuments.parse. - Not covered yet. The XLSX reader still runs in the serving process.
27 October: Read XLSX workbooks by sheet and cell
Section titled “27 October: Read XLSX workbooks by sheet and cell”GET /v1/files/sheets serves an XLSX workbook. It gives the outline of the
sheets, or the rows of one sheet by A1 row. See
Spreadsheets. A cell has the
value its file stores and its formula apart. LBB calculates nothing.
- Bounded read. The first read parses the workbook in a child process. The child has an empty environment.
- Hard limits. A limit refuses the whole workbook with
xlsx_refused. The limits cover the file, its expansion and its XML elements. They cover sheets, rows, columns, cells and cell text. They also cover the output and the CPU time. - Few parses at a time. A node parses two workbooks at once, and a tenant one. The parse ends with the read that asks for it.
- New codes:
xlsx_refused,xlsx_busy,file_not_spreadsheetandsheet_not_found. - Places. Evidence names a place in a sheet with
…#Sales!B2:D9. The console opens the file at it. Therowstemplate reads XLSX files and writes#Sales!A5:F5for a row. - Server setting.
LBB_PYTHON3names the interpreter. The server image installspython3. - SDKs.
files.sheetsin TypeScript and Python. Helpers write and read the fragment of a place.
27 October: Grade the values no label judges
Section titled “27 October: Grade the values no label judges”Sometimes the arms of an experiment differ on a value that the dataset does not label. A person can now grade that value in the Review inbox. The answer becomes a label of the dataset (Grade the values no label judges). Little Big Brain files these items only when the operator turns the feature on.
- New review origin
evals:datasets, kindgrade, optionsrightandwrong.ReviewEvalOriginhas the kinddatasetand the fieldsitem,property,valueandstated.eval_idfilters by dataset. - New field
sourceon a label of a dataset item:{kind, id, at}. Thekindistestorreview. The latest answer of a person wins. A label without asourceis never replaced. - Changed scoring. A property with only
leave_outrows of values judges those values. Every other value stays unlabeled. Before, it waswrong. A report scored before this release reads as stale, becauselabels_etagnow ends in#g2. The next score turn scores it again. - New error code
eval_dataset_items_too_large(400): a label would make a dataset’s items larger than 8 MiB. - New flag
LBB_REVIEW_EVAL_DATASETS(offby default; needsLBB_REVIEW_EVAL_ITEMS). It gates filing, never an answer. - New metrics
lbb_review_eval_datasets_enabled{mode}andlbb_review_eval_items_waiting{origin="dataset", reason}. The counterlbb_review_eval_items_totalhas the origindataset. - Roll first: an earlier node reads a dataset item as
unknown. It answers the item with no label and dropssourcefrom labels. Roll every node before you turn the flag on.
27 October: Scanned pages are flagged
Section titled “27 October: Scanned pages are flagged”A scanned PDF page is a picture of text. Its text layer is empty or nearly
so. The documents.parse workflow now flags such a page ocr_required: true.
A page gets the flag when two things hold. It has fewer than 16 characters of text. Its images cover at least 40% of it.
- New fields:
ocr_requiredon a page ofGET /v1/files/pages. Alsoocr_pagesin theparseof a file and on itsDocument. - Parser id: pages with the flag are stored under
poppler-<version>-scan1. Pages parsed before keep their id and no flag. - Console: a page that needs OCR shows a warning. The file list shows how many pages need OCR.
- Run results: with the Rust workflow, a file of which every page needs OCR is a result with no value. OCR itself is not built.
- New setting:
LBB_PDFIMAGESnames the Poppler program that lists the images. The server image and the worker image already contain it. - One file cannot hold the queue. A file whose parse stops goes to the
back. At its third stop it is stored as
failed, with the reason. - Roll order: roll the server first, then the workers. An old server
drops the flag and stores pages without it under the new id. The new
server then answers
409for the same pages with the flag. - Parse programs:
pdftotextandpdfimagesnow run with an empty environment and a fixedPATH. No key or token of the server or the worker reaches them. They also run with limits on memory, CPU time and file size. Before, they inherited the environment of their host. The server set no limit.
27 October: Retry a failed reading once
Section titled “27 October: Retry a failed reading once”values.extract takes the parameter repair. With repair: 1, an input whose
first answer fails a check gets one more attempt. The retry sends the same text
and the failed checks. See
Retry a failed reading once.
repair: 0 is the default and changes nothing.
- New parameter
repair(0or1). It is part of the settings revision. A test on a named revision reads it from there. - New fields:
attempton a logged call and on a call list row (2on the retry).attempton a value of a test or run result.settings.repairon a test.attempton areasonactivity request. - New counter
retriedin the workflow’s status. - Roll first: an earlier node refuses
repairon a PUT. It also refusesattempton areasonrequest.
27 October: See whether a revision was tested
Section titled “27 October: See whether a revision was tested”A revision of a values.extract workflow shows whether a workflow test read
inputs with exactly its settings
(See whether a revision was tested).
- New field
last_testedonGET /v1/workflows/named/revisionsand…/revisions/get:{test, finished, counts}for the newest test that counts,nullwhen no test is recorded for these settings, and no key for a workflow without tests. An experiment’s arm is such a test and addsexperiment(id,arm). It shows what happened and gives no verdict. Activation is never refused. - A test counts when it ended
doneand the model read at least one input. Tests from before this release do not count. In a model experiment only arm A records a fact, because B runs another model than the workflow’s. - The console marks each row of the history and warns before it uses settings with no recorded test.
26 October: Compare two versions of a workflow
Section titled “26 October: Compare two versions of a workflow”An experiment runs a values.extract workflow twice on the items of a
dataset: as it is (A) and with one change (B). B is the model of one step
(extract or decide) or a kept settings revision. Each output is checked
against the dataset’s values. The report gives the difference with a 95%
interval (Compare two versions of a workflow).
- New routes
POST /v1/evals/experiments,POST /v1/evals/experiments/estimate,GET /v1/evals/experiments,GET /v1/evals/experiments/getandPOST /v1/evals/experiments/cancel. - New fields
experimentonPUT /v1/models/choicesand on a revision activation. The write applies the experiment’s B, and the server refuses it withexperiment_outdatedwhen the workflow no longer runs A. - New codes
experiment_arms_mixed,experiment_outdatedandexperiments_instance_taken. - An experiment writes nothing to the graph and changes no workflow. Its model calls count in the environment’s monthly workflow budget, as a test’s do.
22 October: Choose the model of each workflow step
Section titled “22 October: Choose the model of each workflow step”The AI steps of workflows can run another model, for the whole environment
or for one workflow
(Choose the model of a workflow step).
extract is the reasoning model (values.extract reads values, the
reason activity), and decide the decision model (the support of a
value, the decide activity).
- New routes
GET,PUTandDELETE /v1/models/choices, with the codesmodel_choice_unsupportedandmodel_choice_unavailable. - New fields: a logged call’s
choice(why it ran its model),modelson a test create to test other models, and a test’smodels. - A run’s fingerprint names its models, so runs before and after a change have different fingerprints.
22 October: A workflow’s settings keep their history
Section titled “22 October: A workflow’s settings keep their history”Each change of a workflow’s settings is a revision, named by a hash of the settings: the same settings always get the same id. A workflow’s history lists who changed its settings, when, and what changed, and an earlier revision can be used again (Settings history of a workflow).
- New routes
GET /v1/workflows/named/revisions,GET /v1/workflows/named/revisions/getandPOST /v1/workflows/named/revisions/activate, with the codesworkflow_revision_conflict,workflow_revision_staleandworkflow_revision_not_found. - New fields: a workflow’s
active_revision, a run summary’s and a test’srevision, andrevisionon a test create, to test earlier settings without using them. PUT /v1/workflows/namedandPUT /v1/workflows/triggeredkeep their bodies and answers.
21 October: Every error code has an entry, and a lost turn lease has its own code
Section titled “21 October: Every error code has an entry, and a lost turn lease has its own code”Each error body’s doc_url now links to the code’s entry on
Error codes: its status, its type, what it means and what
to do. Each code the data plane sends has one entry, and CI checks it.
What changes in the error bodies:
- New code
turn_lease_lost(409,conflict_error,retryable: false,param: "token"). A/v1/workflows/turns/*route answers it when the token no longer holds the turn: another attempt holds it, its lease ended, it waits after a sleep or signal checkpoint, or it finished. Before, these answered409 conflict. The SDK workers stop on both codes. A worker that calls these routes itself and stops onconflictmust stop onturn_lease_losttoo. - A 402 without a code of its own answers
quota_exceeded(quota_error). Before, its code wasapi_error, the name of a type. paramnames the field to change on more errors: a request body’s missing, unknown or doubled field, a missing query parameter of the workflow, starter and evals routes, the review list’scursor, and the review answer’sIdempotency-Key.request_idis in every error body, and theX-Request-Idheader has the same value, also on a request refused before it reaches a route (a bad header, a busy server). A path that no route serves answers404 not_foundin the error shape, not as text.
No code changed its type: a code keeps the type it was first sent with.
5 October: Questions get one route, POST /v1/query/ask
Section titled “5 October: Questions get one route, POST /v1/query/ask”POST /v1/query/ask answers a question in plain words. A model runs
queries in a short loop, reads the rows, and answers with the records it
cites.
curl -X POST "$LBB_URL/v1/query/ask?graph=services" \ -H "Authorization: Bearer $LBB_API_KEY" -H 'Content-Type: application/json' \ --data '{"question": "How many services does each team run?"}'This change breaks clients of POST /v1/query/rewrite:
POST /v1/query/rewriteis gone. It answers 404 and names the new route.- The one-shot mode is gone:
mode: "rewrite",runandpreviousanswer 400. The loop always runs its queries and corrects them itself. mode: "route"stays: the route alone, in about 0.3 seconds.- The stream events
query,run,rowsandrepairare gone. The stream sendsstepper tool call, thenanswer. history.beforeandhistory.afterare gone. A comparison fillshistory.added,history.removedandhistory.changed.- The loop’s steps include
compare, for “what changed” questions, and adescribethat shows how many records hold each property and the values of small classes.
The SDKs change with it. TypeScript has query.ask and query.askStream,
Python query.ask and query.ask_stream. query.rewrite and the rewrite
streams are gone. MCP lbb_query mode=question always answers. To write
queries in your own loop, use
the tools.
5 October: Rerank inside a SPARQL query
Section titled “5 October: Rerank inside a SPARQL query”A SPARQL search can now rerank its hits. Add search:rerank true to the
search triples.
- The rerank model reads the best hits with the query text. The query keeps the hits that answer it best.
search:relevance ?rbinds the model’s answer, from 0 to 1. Order by it withORDER BY DESC(?r).- A query reranks only when it asks. The graph’s rerank setting applies to
POST /v1/searchonly. - See Rerank inside a query.
4 October: Rerank search hits
Section titled “4 October: Rerank search hits”A search can now rerank its best hits. Jev, a decision model, reads each hit with your words. The hits that answer them come first.
- Turn it on for a graph with
PUT /v1/search/settingsand{"rerank": true}. One search can setrerankfor itself. - Each hit gets
relevance, the probability that it answers the words.scorestays the similarity. - On BEIR SciFact, nDCG@10 rose from 0.713 to 0.797 over 300 test queries. A rerank adds about 0.3 s.
- A rerank never fails a search. Without an answer in time, the hits keep the similarity order.
- The SDKs add
searchSettings()andsetSearchSettings()(search_settings()andset_search_settings()in Python). MCP’slbb_query mode=searchtakesrerank.
See Search by meaning.
4 October: Questions in plain words become SPARQL queries
Section titled “4 October: Questions in plain words become SPARQL queries”POST /v1/query/rewrite turns a question into one checked SPARQL query. With
run: true, it also runs the query and returns the rows.
curl -X POST "$LBB_URL/v1/query/rewrite?graph=services" \ -H "Authorization: Bearer $LBB_API_KEY" -H 'Content-Type: application/json' \ --data '{"question": "How many services does each team run?", "run": true}'- The server describes your graph: classes, properties, schema statements and the classes you can search by meaning.
- TypeSafe’s Jev picks the kind of query: lookup, aggregate, search, history, schema or unanswerable. Claude Sonnet 5.5 writes the query.
- The server checks the query and adds missing
PREFIXlines. A query that fails is corrected once. - A run records an eval trace with the question as its request.
previouscarries earlier attempts,contextcarries your app’s notes, andmode: "route"returns the kind of query only.- TypeScript and Python add
query.askandquery.rewrite. MCPlbb_queryaddsmode: "question".
LBB runs both models with its own keys. The question, your notes and a description of the graph, with short example values, go to the model providers. See Answer questions in plain words.
3 October: Search by meaning inside SPARQL
Section titled “3 October: Search by meaning inside SPARQL”A SPARQL query can now search by meaning. Any other pattern on the hit filters it: a link, a value, a date or a type.
PREFIX search: <https://littlebigbrain.com/search#>PREFIX dr: <https://littlebigbrain.com/r/>PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>SELECT ?x ?score WHERE { ?x search:similarTo "card payments" ; search:score ?score . ?x dr:calls ?callee . ?callee rdfs:label "payment-service" .}ORDER BY DESC(?score) LIMIT 5- The query is a text, a record (“more like this”) or your own vector.
- The engine picks a plan by cost. A narrow filter is read first, and only its records are scored. A broad filter is applied to the nearest part of the index.
- The response’s
searchobject names the plan, both costs and the time of each phase. as_of_commit_seqreads the graph at an older commit.FILTER(?score > 0.8)keeps only close hits.
The syntax is standard SPARQL, so existing clients send these queries unchanged. See Search by meaning.
3 October: MCP 0.8.1
Section titled “3 October: MCP 0.8.1”suggest_ontology_change takes up to 128 operations in change, the same
limit as the server. MCP 0.8.0 refused more than 64.
3 October: EXISTS under aggregates and in OPTIONAL filters
Section titled “3 October: EXISTS under aggregates and in OPTIONAL filters”Under an aggregate, a BIND with EXISTS left its variable unbound. This
query returned 0. Now it counts the users that are not persons:
SELECT (SUM(?p) AS ?n) WHERE { ?x a ex:User BIND(IF(NOT EXISTS { ?x a ex:Person }, 1, 0) AS ?p)}Two other shapes returned an error. One is EXISTS inside a larger FILTER
under an aggregate. The other is EXISTS in the filter of an OPTIONAL. Both
shapes now run.
2 October: TypeScript and Python 0.17.0, MCP 0.8.0
Section titled “2 October: TypeScript and Python 0.17.0, MCP 0.8.0”TypeScript @littlebigbrain/client and Python littlebigbrain 0.17.0 add the
ontology starters. ontology.starters lists,
applies and updates crm, documents and work. Typed constants give each
starter term its SPARQL IRI.
A refusal now carries the reasons for each item in LbbError.details. An
example is the conflicts of 409 starter_conflict.
Version 0.16.0 came out the same day. It added these methods:
ontology.suggestionsreads and decides ontology change suggestions.query.sparqltakesprofileand returns how the query ran. See See how a query ran.plannerStatsreturns the statistics of the query planner.modelActivityreports what each managed model did in one month.workflows.deleteInstancedeletes a workflow instance and its history.
Python uses snake case names, such as planner_stats. Version 0.16.0 drops
ontology.induce, whose route the server no longer has.
The Python client now leaves unset optional fields out of a model request
body. It sent them as null, and the server refused some requests with
HTTP 400.
MCP 0.8.0 adds list_starters, apply_starter and suggest_ontology_change
to lbb_configure. lbb_inspect lists suggestions with
action: "ontology_suggestions".
2 October: xsd:dateTime() casts a string
Section titled “2 October: xsd:dateTime() casts a string”The cast xsd:dateTime("2026-10-01T00:00:00Z") left its variable unbound.
Now it returns "2026-10-01T00:00:00Z"^^xsd:dateTime, the same value as
STRDT. The casts xsd:date() and xsd:time() work too.
- A string casts to each of the three datatypes.
- An
xsd:dateTimecasts toxsd:dateandxsd:time. - An
xsd:datecasts toxsd:dateTime, at the start of that day. xsd:string()of a date or time returns its canonical form.- An invalid value, such as month 13, is a type error. A
BINDstays unbound.
Every cast now ignores spaces around a string, as XPath prescribes. So
xsd:integer(" 42 ") returns 42.
2 October: Entailment applies inside EXISTS and NOT EXISTS
Section titled “2 October: Entailment applies inside EXISTS and NOT EXISTS”With entailment=subclass, rdfs or owl, the pattern inside EXISTS matched
only stored statements. The main pattern and MINUS used the entailed graph.
Now every pattern in the query uses the entailed graph. This includes an
EXISTS in a FILTER, BIND, IF, projected expression or HAVING.
Here ex:User is a subclass of ex:Person. The query now returns no users,
the same answer as MINUS { ?x a ex:Person }:
SELECT ?x WHERE { ?x a ex:User FILTER NOT EXISTS { ?x a ex:Person } }2 October: A date with a very long year is stored
Section titled “2 October: A date with a very long year is stored”A date with a year past 9223372036854775807 failed the write or the query.
An example is "99999999999999999999-01-01"^^xsd:date. Now the write stores
the value unchanged, and a query reads it back. The value is out of range, so
a comparison or YEAR() treats it as a type error.
2 October: GROUP BY keeps a fixed subject or object
Section titled “2 October: GROUP BY keeps a fixed subject or object”A GROUP BY over one triple pattern ignored a fixed subject or object. This
query returned each subject that has a type:
SELECT ?x (COUNT(*) AS ?n) WHERE { ?x a ex:User } GROUP BY ?xNow it returns only the members of ex:User. The fix also applies to
?x ex:knows ?x, which repeats the grouped variable. It applies to a count
sorted with ORDER BY and LIMIT, and to two joined patterns.
September 2026
Section titled “September 2026”28 September: Search by meaning
Section titled “28 September: Search by meaning”Search by meaning is now available for every account. Declare an embedding on a class. The platform builds one text per record from the fields you choose, embeds it with the graph’s model, and keeps the vectors current after each published commit. A search embeds your question with the same model and returns the nearest records. Each hit is checked against one published commit, so a deleted or re-typed record does not come back.
POST /v1/embeddings/previewshows the text of sample records and the facts you can choose. It stores nothing and calls no model.PUT /v1/embeddingsdeclares an embedding and starts the embed job.GET /v1/embeddingslists the embeddings and their state, andDELETE /v1/embeddingsremoves one.POST /v1/searchsearches every searchable class. Afilterlimits the hits to a class and its subclasses, or to records linked to a named record. With"explain": true, the search returns its plan and calls no model.PUT /v1/embeddings/modelmoves every class of a graph to a new model. The graph switches when every class is ready, so a search never mixes models.
The TypeScript and Python SDKs add embeddings methods, including search.
The MCP server adds lbb_embeddings, lbb_embeddings_manage and
lbb_embeddings_delete, and lbb_query gets mode=search. Vectors count as
stored data; model tokens are not billed. See
Search by meaning.
27 September: BIND(EXISTS …) returns a value in a joined group
Section titled “27 September: BIND(EXISTS …) returns a value in a joined group”In a braced group joined with other patterns, BIND(EXISTS { … } AS ?e) left
?e unbound. Now ?e is true or false. The EXISTS uses the variables
that its own group binds. NOT EXISTS works the same way.
SELECT ?k ?e WHERE { ?k ex:memberOf ex:platform . { ?k a ?c BIND(EXISTS { ?k ex:knows ?o } AS ?e) }}26 September: owl:sameAs no longer makes a record a type
Section titled “26 September: owl:sameAs no longer makes a record a type”With entailment=owl, SELECT ?t WHERE { ex:alice a ?t } listed ex:alice
as a type. It also listed each owl:sameAs alias of ex:alice. The query now
lists only classes.
A BIND in a join could replace a value that another pattern bound. Now the
two values must be equal, or the row is dropped. In an OPTIONAL, a left row
with a different value keeps its value and stays unmatched.
26 September: JSON type hierarchies reach queries
Section titled “26 September: JSON type hierarchies reach queries”A type’s super_types in a JSON schema publish one rdfs:subClassOf statement
per parent. So does the add_super_types evolve operation. With
entailment=subclass, rdfs or owl, a query for a type also matches its
subtypes. SELECT ?c ?p WHERE { ?c rdfs:subClassOf ?p } lists the links.
A strong read applies a new parent at once. An eventual read applies it after the graph republishes. Republication starts when the schema change is applied. See Add a type hierarchy to JSON records.
25 September: Record-history routes removed
Section titled “25 September: Record-history routes removed”These read routes are removed:
GET /v1/graph/entity/metadataandGET /v1/graph/entity/neighborhoodGET /v1/graph/entities/sampleandGET /v1/graph/changesPOST /v1/query/state,/v1/query/history,/v1/query/transitions,/v1/query/whyand/v1/query/conflicts
They read data that hosted graphs do not publish. So they always answered
429 ingest_busy, and a client retried them forever. They now return 404.
The did_you_mean field of the error names the route to use.
Read one record with its fields and links with GET /v1/graph/entity. Read a
past commit with SPARQL and as_of_commit_seq.
Replay answers and history shows how.
The TypeScript and Python SDKs drop the matching methods. The MCP server drops
the state, history, transitions and why actions of lbb_inspect.
24 September: One history per graph
Section titled “24 September: One history per graph”Graphs have no branches. Each graph keeps one line of commits.
The branch fork, merge and delete routes are removed. So are the branch
query parameter and observe (POST /v1/memory/observe). The SDKs and the MCP
server drop their branch and observe methods and tools.
Items in GET /v1/graphs drop the branches field. Graph metadata drops
parent. The graph delete response drops deleted_branches.
A request with branch=main still works, and the server ignores the value.
Any other branch value returns 400 branch_not_supported.
6 September: OPTIONAL keeps its columns under entailment
Section titled “6 September: OPTIONAL keeps its columns under entailment”A SPARQL query with an OPTIONAL block, run with entailment=owl or
entailment=rdfs, returned rows missing variables bound by the required
pattern whenever the optional pattern matched an entailed triple, and those
rows collapsed into duplicates. The same happened, without entailment, for a
subquery inside OPTIONAL. Every row now carries the required pattern’s
bindings, and a subquery’s unprojected variables no longer take their values
from the outer row.
OPTIONAL under entailment=owl also read far more than it should on hosted
graphs whose ontology declares owl:sameAs identities: each row of the
required pattern re-read the optional pattern’s blocks, so a small graph
could exhaust its per-request read budget and answer 429 ingest_busy. The
optional side now folds once for the whole result, including an OPTIONAL over
an entailed-only property and the OPTIONAL { ?x ?p ?y } shape, and a query
that does exceed the read budget answers 400 scan_too_wide instead of a
retryable backlog code.
August 2026
Section titled “August 2026”31 August: Import typed ontology properties
Section titled “31 August: Import typed ontology properties”Ontology imports recognize owl:DatatypeProperty declarations as property
fields. Their declared ranges map to native text, numeric, boolean, and temporal
types. Object properties remain relationships between classes.
TypeScript @littlebigbrain/client 0.13.0 and Python littlebigbrain 0.13.0
add typed schemaSummary() and schema_summary() methods for inspecting a
graph’s schema.
28 August: Preserve imported class hierarchies
Section titled “28 August: Preserve imported class hierarchies”Additive ontology definitions preserve rdfs:subClassOf relationships. The
ontology and schema APIs expose direct superclass identifiers and canonical
query IRIs, including when imported vocabularies share local class names.
25 August: Query with OWL entailment
Section titled “25 August: Query with OWL entailment”SPARQL accepts entailment=owl for supported OWL reasoning, including equivalent
classes and properties, inverse and transitive relationships, and bounded
property chains. Reasoning applies when you query published data.
See Reasoning & inference for the supported rules and their limits.
24 August: RDFS reasoning and clearer validation
Section titled “24 August: RDFS reasoning and clearer validation”SPARQL accepts entailment=rdfs for subclass, subproperty, and domain/range
reasoning, or entailment=subclass for the class-only subset. Asserted facts
remain the default query mode.
SHACL shape publishing rejects unsupported or malformed constraints with clear errors. SPARQL constraints support declared prefixes, validation messages can include the offending values, and nested violations identify the constraint that failed.
TypeScript and Python 0.12.0 support publishing RDF SHACL shapes and reading ontology conformance reports. See Query & validate.
24 August: Clear errors for named-graph input
Section titled “24 August: Clear errors for named-graph input”RDF imports, SPARQL writes, and Graph Store requests report unsupported named-graph input. Each database graph serves one default RDF graph. Use separate database graphs and branches for isolation, and check import error counts or enable strict import validation.
See HTTP & SPARQL.
22 August: Reusable ontology definitions and publication waiters
Section titled “22 August: Reusable ontology definitions and publication waiters”New graphs start with an empty ontology. Reapplying the same definition is a
no-op; additive definitions evolve the ontology while incompatible changes
return an error. The starter vocabulary can be added explicitly with
merge_default: true.
TypeScript and Python 0.11.1, and MCP 0.4.1, improve publication waiting with dedicated deadlines and retries. Waiters finish when the requested data is queryable.
21 August: SPARQL APIs and large RDF imports
Section titled “21 August: SPARQL APIs and large RDF imports”The public query API is SPARQL, with native protocol endpoints and TypeScript, Python, and MCP clients. TypeScript and Python 0.11.0, and MCP 0.4.0, align with that query surface. See HTTP & SPARQL.
Large RDF imports can defer publication while a chunked load is in progress, then make the completed import queryable. The bulk loader supports resumable input. See Load RDF data.
Selective relationship queries and multi-hop SPARQL patterns also execute faster while preserving their results.
20 August: Reliable joins across RDF term orders
Section titled “20 August: Reliable joins across RDF term orders”Join results are independent of the order in which RDF terms entered a graph.
19 August: Extract years and months from temporal literals
Section titled “19 August: Extract years and months from temporal literals”YEAR() accepts xsd:gYear and xsd:gYearMonth; MONTH() accepts
xsd:gYearMonth. Aggregates can use these typed values directly. An aggregate
whose inputs all produce type errors returns an unbound result.
18 August: Faster filters and clearer temporal comparisons
Section titled “18 August: Faster filters and clearer temporal comparisons”Numeric, date, and string filters avoid scanning unrelated values. Comparisons between supported temporal values distinguish an undefined comparison from a valid comparison that matches no rows.
See FILTER comparisons and datatypes.
15 August: Faster schema summaries and statistics
Section titled “15 August: Faster schema summaries and statistics”Grouped statistics, property inventories, and schema summaries respond faster on large graphs.
14 August: Bounded query memory
Section titled “14 August: Bounded query memory”Large scans use bounded working memory, and queries report explicit errors when they exceed their resource or result limits.
13 August: Faster counting and entity lookups
Section titled “13 August: Faster counting and entity lookups”COUNT, COUNT(DISTINCT ...), and queries anchored at a known entity perform
less work on large graphs.
9 August: Resume interrupted index updates
Section titled “9 August: Resume interrupted index updates”Interrupted index updates resume their progress. Unused index data is reclaimed according to retention rules.
6 August: Incremental indexing and bounded neighborhoods
Section titled “6 August: Incremental indexing and bounded neighborhoods”Indexes incorporate updates incrementally. Entity neighborhood reads apply explicit limits when exploring highly connected data.
July 2026
Section titled “July 2026”30 July: Durable large imports
Section titled “30 July: Durable large imports”Large import jobs continue across restarts and report progress. TypeScript and Python 0.9.0 stream NDJSON request bodies to reduce client memory usage.
27 July: Validation as ontologies evolve
Section titled “27 July: Validation as ontologies evolve”Active SHACL shapes remain attached when an ontology evolves. Common minimum and maximum count constraints validate data on large graphs.
21 July: Selectable read consistency
Section titled “21 July: Selectable read consistency”Reads default to the published snapshot. Applications can request strong consistency and specify the write sequence that must be visible.
See Core concepts.
20 July: Graph fork and reload
Section titled “20 July: Graph fork and reload”The HTTP API and TypeScript and Python 0.7.0 clients support forking a graph and replacing its contents through a reload operation.
19 July: Faster SPARQL on large graphs
Section titled “19 July: Faster SPARQL on large graphs”SPARQL SELECT, ordered queries with limits, and repeated queries execute
more efficiently on large datasets.
18 July: Faster graph reports
Section titled “18 July: Faster graph reports”Graph summary, conformance, and metadata requests respond faster. Bounded exploration responses report the limits they applied.
15 July: Automatic retries under load
Section titled “15 July: Automatic retries under load”Overloaded requests return 429 with Retry-After. TypeScript and Python
0.6.0 retry these requests within a deadline.
11 July: Export graph data
Section titled “11 July: Export graph data”Graphs export as NDJSON for backup and transfer between stacks.
10 July: Query and validate persisted graphs
Section titled “10 July: Query and validate persisted graphs”SPARQL queries and SHACL validation operate on graphs stored in object storage. Set-valued properties support fields such as tags and categories.
6 July: Validated branch merges and historical reads
Section titled “6 July: Validated branch merges and historical reads”Branch merges validate proposed changes and report conflicts. Rejected writes identify the violated constraint so clients can correct their input.
Applications can pin reads to a retained commit for reproducible results. See History & time travel for supported selectors and retention boundaries.
June 2026
Section titled “June 2026”30 June: Faster bulk ingest
Section titled “30 June: Faster bulk ingest”Bulk ingest throughput improved by 3x over the previous implementation.
26 June: Aggregation and repeatable imports
Section titled “26 June: Aggregation and repeatable imports”SPARQL supports grouped COUNT, SUM, and AVG queries over typed values.
NDJSON imports can use stable external keys to update existing entities, and
retractions remove previously asserted facts.
25 June: Query class hierarchies
Section titled “25 June: Query class hierarchies”Subclass reasoning lets a query for a broad class include its subclasses.
24 June: Ontology conformance reports
Section titled “24 June: Ontology conformance reports”Conformance reports check published data against the active ontology and are available through the HTTP API and SDKs.
23 June: Additive ontology evolution
Section titled “23 June: Additive ontology evolution”Ontologies can add entity types and widen relationship domains or ranges while preserving existing records.
21 June: Typed literals
Section titled “21 June: Typed literals”Numeric and temporal literals support server-side filtering and aggregation.
20 June: Bounded inference rules
Section titled “20 June: Bounded inference rules”Supported SHACL rules derive additional facts for querying. See Reasoning & inference for configuration and limits.
11 June: Stable reads during writes
Section titled “11 June: Stable reads during writes”Graph snapshots keep query results consistent while new data arrives.