Skip to content

Build a workflow

View Markdown

A workflow does work on your records after each published commit. The one on this page reads product data sheets. It fills four properties: the width, the depth, the material and the color. Each value comes with the words of the text that state it.

You test the workflow before it writes. A test runs it on two records and writes nothing to the graph. You read each value with its quote, correct one, and keep your answers as a dataset. Then you create the workflow, and it writes the values.

The workflow comes from values.extract, one of LBB’s templates. LBB runs it, so you need no worker of your own.

In the console, open your stack’s Connect view and copy its endpoint and an API key. To run on your machine instead, run npm run dev:stack in the LBB repository. Its endpoint is http://127.0.0.1:7400, and its key is lbb_sk_test_local.

Set both in your terminal. The TypeScript and Python examples read them from there. The cURL steps use jq to read fields of the answers.

In cURL, RUN holds the time you start. It makes this run’s idempotency keys unique. A request sent again with the same key does its work once. Each cURL step also sends Lbb-Version: 2026-10-21, the API version of this page. The SDKs send the version and a key for each write.

Terminal window
export LBB_URL="http://127.0.0.1:7400"
export LBB_API_KEY="lbb_sk_test_local"
export RUN="$(date +%s)"

Each example on this page adds to the one before it. In TypeScript and Python, keep them in one file.

A workflow fills properties that the graph declares. Create the graph main first. If it exists, the create answers 409, and you go on. Declare the class Product, its text, and the four properties to fill. A number property names its unit. The workflow then converts a number in another unit: 1.6 m is 160 for width_cm.

Then write two records from the template’s samples. They are the data sheets of a conference table and of a drawer unit. The second one is in German. A test reads published records, so wait until the commit is published.

Terminal window
curl -sS -X POST "$LBB_URL/v1/graph/create?graph=main" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21'
curl -sS -X POST "$LBB_URL/v1/ontology/evolve?graph=main" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' \
-H 'Content-Type: application/json' \
--data '{"ops": [{"op": "add_entity_type", "name": "Product"}, {"op": "add_property", "name": "text", "value_type": "text"}, {"op": "add_property", "name": "width_cm", "value_type": "f64", "unit": "cm"}, {"op": "add_property", "name": "depth_cm", "value_type": "f64", "unit": "cm"}, {"op": "add_property", "name": "material", "value_type": "text"}, {"op": "add_property", "name": "color", "value_type": "keyword"}]}'
curl -sS -X POST "$LBB_URL/v1/graph/commit?graph=main" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' \
-H 'Content-Type: application/json' -H "Idempotency-Key: product-records-$RUN" \
--data '{"entity_properties": [{"type": "Product", "name": "T-160", "properties": {"text": "T-160 Conference table of oak veneer, width 1.6 m, depth 800 mm. Accessory: a cable tray of steel under the top."}}, {"type": "Product", "name": "R-3", "properties": {"text": "R-3 Rollcontainer aus Stahlblech, Breite 43, Höhe 60 cm, mit drei Schubladen. Farbe: lichtgrau."}}]}'
for i in $(seq 120); do
[ "$(curl -sS "$LBB_URL/v1/graph/publication-status?graph=main" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' | jq -r '.state')" = current ] && break
[ "$i" = 120 ] && echo "The commit is not published after 2 minutes." >&2
sleep 1
done
R3=$(curl -sS "$LBB_URL/v1/graph/entity?graph=main&type=Product&name=R-3" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' | jq -r '.entity.id')

The loop asks every second if the commit is published, for at most two minutes. The last command keeps the id of record R-3 for step 4.

A model that reads values from text can read a wrong one. It can read the size of an accessory, or a height as a depth. The test shows what the workflow would write.

Send the workflow as you will create it, in install. It names the template and the workflow’s name, product. It names the class, the field to watch and the properties to fill. sample: 2 runs it on the first two records of the class. The test goes from queued to running, then ends done.

Terminal window
TEST=$(curl -sS -X POST "$LBB_URL/v1/workflows/named/tests?graph=main" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' \
-H 'Content-Type: application/json' -H "Idempotency-Key: product-test-$RUN" \
--data '{"install": {"starter": "values.extract", "name": "product", "watch": {"class": "Product", "fields": ["text"]}, "params": {"properties": ["width_cm", "depth_cm", "material", "color"]}}, "sample": 2}' | jq -r '.id')
for i in $(seq 300); do
[ "$(curl -sS "$LBB_URL/v1/workflows/named/tests/get?graph=main&id=$TEST" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' | jq -r '.status')" = done ] && break
[ "$i" = 300 ] && echo "The test is not done after 5 minutes. Read its status and error." >&2
sleep 1
done
curl -sS "$LBB_URL/v1/workflows/named/tests/get?graph=main&id=$TEST" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21'

For each record, the test keeps the text it read and the values. Each value has a quote and a support. The support says how well the quote supports the value, from 0 to 1. Its outcome says what the workflow does with it. written means the workflow writes the value. review means it asks a person first.

On the local stack, T-160 gets three values. width_cm is 160 from width 1.6 m, and depth_cm is 80 from depth 800 mm. Its material is oak veneer, and the steel of the cable tray is left out. The text names no color, so color gets no value.

R-3 also gets three values, and no depth: Höhe 60 cm is its height. Its width_cm 43 quotes Breite 43, which states no unit. So its outcome is review: a person confirms the unit before the value is written.

Your answers turn the test into examples. A dataset of examples lets you compare a later version of the workflow with this one.

Say your product list is in English and names the material of R-3 sheet steel. Correct the value. The decisions accept and reject take the same route. An answer writes nothing to the graph.

Then save the answers as the test’s dataset. Each record you answered becomes an example. It holds the text, the values a correct run writes and the values it leaves out.

A record’s IRI is https://littlebigbrain.com/e/ followed by its id. In the bodies below, '"$TEST"' ends the single quotes. The shell then puts in the value of TEST.

Terminal window
curl -sS -X POST "$LBB_URL/v1/workflows/named/tests/answer?graph=main" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' \
-H 'Content-Type: application/json' -H "Idempotency-Key: product-answer-$RUN" \
--data '{"id": "'"$TEST"'", "iri": "https://littlebigbrain.com/e/'"$R3"'", "property": "material", "answer": {"decision": "correct", "value": "sheet steel"}}'
curl -sS -X POST "$LBB_URL/v1/evals/datasets?graph=main" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' \
-H 'Content-Type: application/json' -H "Idempotency-Key: product-dataset-$RUN" \
--data '{"name": "product answers", "kind": "values", "source": {"kind": "test", "id": "'"$TEST"'"}}'

The dataset holds one example, R-3. Its material is sheet steel, and Stahlblech is a value to leave out. Answer more values and save again. A test has one dataset, and each save brings it up to date.

Create the workflow with the same body you sent as install. This is how you create a workflow from one of LBB’s templates.

The workflow first reads every record of the class. After that, it reads each record that a published commit adds or changes. Each step it takes is a run, with its outcome, its model calls and their cost. The list of runs has the list fields data, has_more and next_cursor, newest first.

The workflow writes what the model reads. Your answers stay in the test and its dataset, so R-3 gets the material Stahlblech. The width of R-3 is not written: it waits for a person, as in the test.

Terminal window
curl -sS -X PUT "$LBB_URL/v1/workflows/triggered?graph=main" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' \
-H 'Content-Type: application/json' \
--data '{"starter": "values.extract", "name": "product", "watch": {"class": "Product", "fields": ["text"]}, "params": {"properties": ["width_cm", "depth_cm", "material", "color"]}}'
for i in $(seq 300); do
[ "$(curl -sS "$LBB_URL/v1/workflows/named/runs?graph=main&name=product" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' | jq -r '.data[0].outcome')" = done ] && break
[ "$i" = 300 ] && echo "The workflow has no done run after 5 minutes. Read its runs." >&2
sleep 1
done
curl -sS "$LBB_URL/v1/workflows/named/runs?graph=main&name=product&limit=5" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21'
curl -sS "$LBB_URL/v1/workflows/named?graph=main&name=product" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21'
for i in $(seq 120); do
[ "$(curl -sS "$LBB_URL/v1/graph/publication-status?graph=main" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21' | jq -r '.state')" = current ] && break
[ "$i" = 120 ] && echo "The values are not published after 2 minutes." >&2
sleep 1
done
curl -sS "$LBB_URL/v1/graph/entity?graph=main&type=Product&name=R-3" \
-H "Authorization: Bearer $LBB_API_KEY" -H 'Lbb-Version: 2026-10-21'

The workflow’s status.counters count its work. instances counts the records it read: 2. written counts the values it wrote: 5. review counts the values it sent to a person: 1, the width of R-3.

The record’s attributes hold the values. Each value also has a link HAS_VALUE_SOURCE to its source. The link’s evidence is the quote. Add a third record, and the workflow reads it after the commit is published.

  • Review. A value goes to review when its support is from 0.5 up to the gate. A number whose quote states no unit goes to review too. The gate is 0.8 unless you set gate. A person confirms or corrects the value, and the answer is written. GET /v1/reviews?workflow=product lists the items: here, the width of R-3. See Ask a person.
  • Spend and Quality. In the console, open Operate → Workflows and the workflow. Spend shows its model calls this month and its budget. Quality shows its gate, the model checks of its uses and its open review items.
  • Your own code. To change the prompt or the rules, run the template’s code in your own worker. See Workflow templates and Run a worker in Python.
  • More on tests. Every field is in Test a workflow before it writes, Datasets of examples and Starter workflows.
What you see What to do
400 workflow_param_invalid: the class has no published instances The commit is not published yet. Wait for it, then start the test again.
400 workflow_param_invalid that names a property The graph lacks the property, or its type. Declare it as in step 2.
409 conflict when you start a test A test of this workflow is still open. Wait for it to end, or cancel it.
A test that ends failed Its error says why. The records it read keep their results.
A wait that prints its message The step did not end in time. Read the test’s status and error, or the workflow’s runs.
429 workflow_budget_exceeded The test may cost more than the monthly workflow budget has left.
503 workflows_disabled The server runs without workflows. On the local stack, use npm run dev:stack.