Quickstart: find it by meaning, prove it by path
This quickstart answers one question: which release fixed the login bug?
You write a few support tickets, bugs and releases as JSON. Search by meaning finds the right ticket, although the question uses other words.
A graph query then follows the ticket’s links to the release. Both reads name the same commit, so you can check the answer later.
"users cannot sign in after the upgrade" │ search by meaning ▼ticket 4821: login fails after 3.1 └─ REPORTED_AS → bug AUTH-112: session token expiry └─ FIXED_IN → Release 3.2It takes about ten minutes. You need no RDF or graph database experience.
To start from your own data, see Load your data. It loads a database and documents, with examples for five use cases.
1. Get your credentials
Section titled “1. Get your credentials”Open the console. If you are signed out, it opens the WorkOS sign-in page automatically. Choose Sign up there if you need an account.
Once signed in, create a stack and open Connect. Copy the complete endpoint and a stack API key. Then set them in your terminal:
export LBB_URL="https://<your-complete-stack-host>"export LBB_API_KEY="<your-stack-api-key>"Use the exact endpoint from Connect. It looks like
https://<tenant-short-id>--<stack-slug>.db.eu.littlebigbrain.com. Keep the key
on your server or your machine. Never put it in a browser bundle.
Install a client, or use cURL. The cURL steps also use jq to read fields from JSON responses.
npm install @littlebigbrain/clientCreate quickstart.mts. Add the snippet of each step to the end of the file.
Run it with npx tsx quickstart.mts after any step. Each call is safe to
repeat.
pip install littlebigbrainCreate quickstart.py. Add the snippet of each step to the end of the file.
Run it with python3 quickstart.py after any step. Each call is safe to
repeat.
Run each block in the terminal where you set your credentials. Each call is safe to repeat.
2. Describe your data
Section titled “2. Describe your data”A graph accepts the record types, links and fields you declare. This works like a table schema. A write that does not fit is refused, so bad data does not get in.
| Name | Kind | Meaning |
|---|---|---|
Ticket, Bug, Release |
Record types | The three kinds of records. |
REPORTED_AS |
Link | A ticket is reported as a bug. |
FIXED_IN |
Link | A bug is fixed in a release. |
description |
Text field | Free text on a record. |
The graph support is created by the first call. A graph is one dataset in
your stack, like a database or an index. Load your data
explains the schema and the other ways to load data.
import { LbbClient } from "@littlebigbrain/client";
const lbb = new LbbClient({ baseUrl: process.env.LBB_URL!, apiKey: process.env.LBB_API_KEY!, graph: "support",});
await lbb.ontology.define({ format: "spec", merge_default: false, source: JSON.stringify({ entity_types: ["Ticket", "Bug", "Release"], relation_types: [ { name: "REPORTED_AS", source: "Ticket", target: "Bug" }, { name: "FIXED_IN", source: "Bug", target: "Release" }, ], }),});await lbb.ontology.evolve({ ops: [{ op: "add_property", name: "description", value_type: "text" }],});import jsonimport osimport time
from lbb import LbbClient
lbb = LbbClient(os.environ["LBB_URL"], api_key=os.environ["LBB_API_KEY"], graph="support")
lbb.ontology.define({ "format": "spec", "merge_default": False, "source": json.dumps({ "entity_types": ["Ticket", "Bug", "Release"], "relation_types": [ {"name": "REPORTED_AS", "source": "Ticket", "target": "Bug"}, {"name": "FIXED_IN", "source": "Bug", "target": "Release"}, ], }),})lbb.ontology.evolve({ "ops": [{"op": "add_property", "name": "description", "value_type": "text"}],})curl --fail-with-body -sS -X POST "$LBB_URL/v1/ontology/define?graph=support" \ -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \ -d '{"format": "spec", "merge_default": false, "source": "{\"entity_types\": [\"Ticket\", \"Bug\", \"Release\"], \"relation_types\": [{\"name\": \"REPORTED_AS\", \"source\": \"Ticket\", \"target\": \"Bug\"}, {\"name\": \"FIXED_IN\", \"source\": \"Bug\", \"target\": \"Release\"}]}"}'
curl --fail-with-body -sS -X POST "$LBB_URL/v1/ontology/evolve?graph=support" \ -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \ -d '{"ops": [{"op": "add_property", "name": "description", "value_type": "text"}]}'3. Write records
Section titled “3. Write records”A write is a commit. It holds links between records (triplets) and field
values (entity_properties). A record is its type plus a key, and it has a
name. Each link can carry evidence, the reason it was recorded.
After the write, wait until it is published. Search reads published data. This usually takes a second or two.
const login = { type: "Ticket", key: "4821", name: "login fails after 3.1" };const invoice = { type: "Ticket", key: "4822", name: "invoice PDF is blank" };const csvExport = { type: "Ticket", key: "4823", name: "CSV export times out" };const auth = { type: "Bug", key: "AUTH-112", name: "session token expiry" };const pdf = { type: "Bug", key: "BILL-7", name: "PDF fonts missing" };const slowExport = { type: "Bug", key: "DATA-58", name: "export runs on one thread" };const r32 = { type: "Release", key: "3.2", name: "Release 3.2" };const r311 = { type: "Release", key: "3.1.1", name: "Release 3.1.1" };
const written = await lbb.graph("support").facts.create( { triplets: [ { source: login, relation: "REPORTED_AS", target: auth, evidence: "Support triage, 2 Sep" }, { source: invoice, relation: "REPORTED_AS", target: pdf, evidence: "Support triage, 3 Sep" }, { source: csvExport, relation: "REPORTED_AS", target: slowExport, evidence: "Support triage, 4 Sep" }, { source: auth, relation: "FIXED_IN", target: r32, evidence: "Release notes 3.2" }, { source: pdf, relation: "FIXED_IN", target: r311, evidence: "Release notes 3.1.1" }, ], entity_properties: [ { ...login, properties: { description: "Since 3.1, people are sent back to the login screen right after they type their password." } }, { ...invoice, properties: { description: "Downloaded invoices open as empty pages." } }, { ...csvExport, properties: { description: "Exports with more than 10,000 rows stop after 30 seconds." } }, ], }, { idempotencyKey: "quickstart-support-1" },);await lbb.waitForPublished(written.commit_seq);console.log("commit", written.commit_seq);login = {"type": "Ticket", "key": "4821", "name": "login fails after 3.1"}invoice = {"type": "Ticket", "key": "4822", "name": "invoice PDF is blank"}csv_export = {"type": "Ticket", "key": "4823", "name": "CSV export times out"}auth = {"type": "Bug", "key": "AUTH-112", "name": "session token expiry"}pdf = {"type": "Bug", "key": "BILL-7", "name": "PDF fonts missing"}slow_export = {"type": "Bug", "key": "DATA-58", "name": "export runs on one thread"}r32 = {"type": "Release", "key": "3.2", "name": "Release 3.2"}r311 = {"type": "Release", "key": "3.1.1", "name": "Release 3.1.1"}
written = lbb.graph("support").facts.create({ "triplets": [ {"source": login, "relation": "REPORTED_AS", "target": auth, "evidence": "Support triage, 2 Sep"}, {"source": invoice, "relation": "REPORTED_AS", "target": pdf, "evidence": "Support triage, 3 Sep"}, {"source": csv_export, "relation": "REPORTED_AS", "target": slow_export, "evidence": "Support triage, 4 Sep"}, {"source": auth, "relation": "FIXED_IN", "target": r32, "evidence": "Release notes 3.2"}, {"source": pdf, "relation": "FIXED_IN", "target": r311, "evidence": "Release notes 3.1.1"}, ], "entity_properties": [ {**login, "properties": {"description": "Since 3.1, people are sent back to the login screen right after they type their password."}}, {**invoice, "properties": {"description": "Downloaded invoices open as empty pages."}}, {**csv_export, "properties": {"description": "Exports with more than 10,000 rows stop after 30 seconds."}}, ],}, idempotency_key="quickstart-support-1")lbb.wait_for_published(written["commit_seq"])print("commit", written["commit_seq"])Save the records as records.json:
{ "triplets": [ {"source": {"type": "Ticket", "key": "4821", "name": "login fails after 3.1"}, "relation": "REPORTED_AS", "target": {"type": "Bug", "key": "AUTH-112", "name": "session token expiry"}, "evidence": "Support triage, 2 Sep"}, {"source": {"type": "Ticket", "key": "4822", "name": "invoice PDF is blank"}, "relation": "REPORTED_AS", "target": {"type": "Bug", "key": "BILL-7", "name": "PDF fonts missing"}, "evidence": "Support triage, 3 Sep"}, {"source": {"type": "Ticket", "key": "4823", "name": "CSV export times out"}, "relation": "REPORTED_AS", "target": {"type": "Bug", "key": "DATA-58", "name": "export runs on one thread"}, "evidence": "Support triage, 4 Sep"}, {"source": {"type": "Bug", "key": "AUTH-112", "name": "session token expiry"}, "relation": "FIXED_IN", "target": {"type": "Release", "key": "3.2", "name": "Release 3.2"}, "evidence": "Release notes 3.2"}, {"source": {"type": "Bug", "key": "BILL-7", "name": "PDF fonts missing"}, "relation": "FIXED_IN", "target": {"type": "Release", "key": "3.1.1", "name": "Release 3.1.1"}, "evidence": "Release notes 3.1.1"} ], "entity_properties": [ {"type": "Ticket", "key": "4821", "name": "login fails after 3.1", "properties": {"description": "Since 3.1, people are sent back to the login screen right after they type their password."}}, {"type": "Ticket", "key": "4822", "name": "invoice PDF is blank", "properties": {"description": "Downloaded invoices open as empty pages."}}, {"type": "Ticket", "key": "4823", "name": "CSV export times out", "properties": {"description": "Exports with more than 10,000 rows stop after 30 seconds."}} ]}Commit it, then wait until the graph reports "state": "current":
curl --fail-with-body -sS -X POST "$LBB_URL/v1/graph/commit?graph=support" \ -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-support-1" \ --data-binary @records.json
until curl -sS "$LBB_URL/v1/graph/publication-status?graph=support" \ -H "Authorization: Bearer $LBB_API_KEY" | grep -q '"state": *"current"'; do sleep 1; doneThe response reports commit_seq, the number of this commit. On a new graph
it is 1.
If you change the records, change the idempotency key too. A reused key
returns the first write with replayed: true and stores nothing new.
4. Turn on search for tickets
Section titled “4. Turn on search for tickets”Declare an embedding on the Ticket type and name the fields to embed. The
platform builds one text per ticket, embeds it and stores the vectors. When a
ticket changes, its vector is updated. You run no embedding model and no vector
store.
https://littlebigbrain.com/class/ticket is the global name of the Ticket
type. Each type you declare gets one: its name in lowercase after
https://littlebigbrain.com/class/. label is the record’s name.
The first build runs in the background. Wait until the embedding reports
serving.
const TICKET = "https://littlebigbrain.com/class/ticket";
await lbb.embeddings.declare({ class: TICKET, from: ["label", "description"] });while (!(await lbb.embeddings.get("ticket")).serving) { await new Promise((resolve) => setTimeout(resolve, 2_000));}TICKET = "https://littlebigbrain.com/class/ticket"
lbb.embeddings.declare(TICKET, from_=["label", "description"])while not lbb.embeddings.get("ticket").get("serving"): time.sleep(2)curl --fail-with-body -sS -X PUT "$LBB_URL/v1/embeddings?graph=support" \ -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \ -d '{"class": "https://littlebigbrain.com/class/ticket", "from": ["label", "description"]}'
until curl -sS "$LBB_URL/v1/embeddings?graph=support&name=ticket" \ -H "Authorization: Bearer $LBB_API_KEY" | grep -q '"serving": *{'; do sleep 2; done5. Search by meaning
Section titled “5. Search by meaning”Ask the question in your own words. The question says “sign in” and “upgrade”. The ticket says “login” and “3.1”.
const found = await lbb.embeddings.search({ text: "users cannot sign in after the upgrade", top_k: 3,});for (const hit of found.hits) console.log(hit.label);found = lbb.embeddings.search("users cannot sign in after the upgrade", top_k=3)for hit in found["hits"]: print(hit["label"])curl --fail-with-body -sS -X POST "$LBB_URL/v1/search?graph=support" \ -H "Authorization: Bearer $LBB_API_KEY" -H "Content-Type: application/json" \ -d '{"text": "users cannot sign in after the upgrade", "top_k": 3}' \ -o found.json
jq -r '.hits[].label' found.jsonThe login ticket comes first:
login fails after 3.1The other two tickets follow, in an order that depends on the model. Each hit
also has a score and an iri, the global name of that record. The response
field served_at_seq is the commit the search read.
6. Follow the path to the answer
Section titled “6. Follow the path to the answer”Similarity found a starting point. It does not prove which release fixed the bug. The stored links do. This query starts at the ticket the search found and follows two links.
The query language is SPARQL, the standard for graph queries. Each line in the braces is a pattern: a record, a link, then another record.
Names that start with ? are variables. The query returns the values that fit
all patterns at once. In Cypher, the same path reads
(t)-[:REPORTED_AS]->(b)-[:FIXED_IN]->(r).
dr:reported_asis yourREPORTED_ASlink.dr:is short forhttps://littlebigbrain.com/r/, where the links you declare live.rdfs:labelis thenameof a record.as_of_commit_seqruns the query at the commit the search read. Search and path then agree on one version of the data.
const ticket = found.hits[0].iri;const query = ` PREFIX dr: <https://littlebigbrain.com/r/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?bug ?release WHERE { <${ticket}> dr:reported_as ?b . ?b rdfs:label ?bug ; dr:fixed_in ?r . ?r rdfs:label ?release . }`;
const { rows, snapshot } = await lbb.sparqlRows({ query, as_of_commit_seq: found.served_at_seq });console.log(rows[0], "read at commit", snapshot?.served_at_seq);ticket = found["hits"][0]["iri"]query = f"""PREFIX dr: <https://littlebigbrain.com/r/>PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>SELECT ?bug ?release WHERE {{ <{ticket}> dr:reported_as ?b . ?b rdfs:label ?bug ; dr:fixed_in ?r . ?r rdfs:label ?release .}}"""
path = lbb.sparql(query, as_of_commit_seq=found["served_at_seq"])print(path.rows()[0], "read at commit", path.snapshot["served_at_seq"])TICKET=$(jq -r '.hits[0].iri' found.json)SEQ=$(jq -r '.served_at_seq' found.json)
curl --fail-with-body -sS -G "$LBB_URL/sparql?graph=support&as_of_commit_seq=$SEQ" \ -H "Authorization: Bearer $LBB_API_KEY" \ -H "Accept: application/sparql-results+json" \ --data-urlencode "query= PREFIX dr: <https://littlebigbrain.com/r/> PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> SELECT ?bug ?release WHERE { <$TICKET> dr:reported_as ?b . ?b rdfs:label ?bug ; dr:fixed_in ?r . ?r rdfs:label ?release . }"The TypeScript example prints this on a new graph:
{ bug: 'session token expiry', release: 'Release 3.2' } read at commit 1Python prints the same row. cURL returns it in SPARQL Results JSON, under
results.bindings.
7. Keep the evidence
Section titled “7. Keep the evidence”Your agent can now answer “Release 3.2 fixed it” and show why. Store these with the answer:
- the question and the search hits,
- the query and the rows it returned,
- the commit number,
served_at_seq.
Later writes do not change that commit. Run the same query with the same
as_of_commit_seq later. It returns the same rows while the commit is retained.
Replay an answer covers retention and history.
If something goes wrong
Section titled “If something goes wrong”| What you see | What to do |
|---|---|
401 |
The API key is missing, wrong or revoked. Export a current key in the same terminal. |
stack_endpoint_mismatch or stack_endpoint_required |
Copy the complete endpoint from the same stack’s Connect view. |
400 with no predicate named description when you declare the embedding |
The write is not published yet. Wait for it, then declare again. |
409 embedding_not_ready |
The first build is still running. Wait until the embedding reports serving. |
Edited records do not appear, and the commit reports replayed: true |
The idempotency key was reused. Use a new key for the changed records. |
A retryable 429 |
Wait for the time in Retry-After, then retry. |
Next steps
Section titled “Next steps”- Core concepts: the model, mapped from vector stores and graph databases.
- Search by meaning: filter hits by type and by link.
- Build with JSON records: validate writes and follow a chain of any length.
- Connect an agent: give an agent the same tools through MCP.
- Build a workflow: test a workflow on two records, then let it fill values from their text.