decision dataset kind (shown as Decision model) is where that gold set is made. Its rows are about
leads: a lead is a customer being followed up, such as someone asking about a property or a policyholder
whose insurance is due for renewal. A row is one decision point of a lead: the moment something had to be
decided about it. It holds:
- the lead’s journey: its past events, oldest first;
- the new call;
- optionally a logged decision: the action an engine took at that moment, with its propensity (the probability the engine gave that action) and its outcome (whether the lead then succeeded). The engine is whatever picks the next action for a lead: a policy, a set of rules, or the Decision API’s decision step.
- answers each question;
- marks the events that caused each answer;
- says how an answer would change without an event;
- reviews the logged decision.
How it differs from the language-model kind
Choose
decision when the model picks from fixed options: where a lead stands, whether price is still an
objection, which action to take next. Choose llm when the model writes.
The questions
The questions are System One questions: the same JSON the Decision API takes inquestions. Each one has an id, a type, the question in words and its options.
A questions file for a property sales lead:
question. instructions, the name the Decision API’s own examples use, is
read the same way, so a questions file you already send to the Decision API works as it is.
Write each description as the test a reviewer applies, not as a restatement of the key. Two reviewers who read
“asked to reserve, to pay a deposit, or for a written offer” agree more often than two who read “ready”.
The questions are part of the dataset’s config, as config.task.questions, and a decision dataset cannot be
built without them.
The actions
A row may carry a decision the engine logged. The actions are what that decision chose from: a JSON object of{key: description}, with no_action among them. They are optional, stored as config.task.actions, and they
are what a reviewer picks from when saying what they would have done.
The record format
The import reads JSON lines (one record per line), records printed over several lines one after another, or a file holding a JSON array of records. A webhook takes the same record as its body. One complete record, spread over several lines here for reading:
A record needs a call, a history, or both. Some of these words need a definition:
- An event is one thing that happened to the lead: a stage change in the CRM, a call, a booking, a payment,
a message. Its
sourcesays where it came from and itstypewhat it is. Both are free text; the Causality builder uses the same fields. - Provenance says how far to trust an event’s text:
model(a model wrote it),confirmed(a person checked it) orcorrected(a person changed it). - The propensity is a number between 0 and 1. An engine that sometimes tries an action other than its best
one logs it with every decision (Recommendations).
policynames the engine or the version of it that decided. - The logged outcome is
yesorno. Leave it empty when it was not known when the record was written.
ev-5 is dated after the decision time, so it is not part
of the history: it cannot be marked as a cause, and it is left out of the exported journey. And the labels and
causes the record brings are imported as drafts, never as human labels. A row counts only once a reviewer
has saved it.
Tables
A tabular source (TSV, CSV, parquet, a query) maps its columns to these roles:Sources
A decision dataset reads from every source that feeds the language-model kind: a folder on thedatasets volume,
files dropped in the browser, S3, GCS, BigQuery, Hugging Face, a URL list, a webhook, or a query against your own
Postgres or MySQL. It can also be built from a dataset you already have: a language-model dataset, or the
validated rows of a speech-to-text dataset. Those give one row per call with an empty journey, which is the
right start when you have calls and no record of events yet.
What a row holds
After the build, every decision point is one line ofrows.tsv in the dataset’s folder on the datasets volume:
Create one
From the dashboard
Open Datasets, then New dataset. Pick the kind Decision model, then a source, then whether a model drafts the answers. On the last step, name the dataset and give its Questions: paste the JSON, or upload the file. The dialog lists back each question it read, with its type and its number of options, so a wrong paste shows before the build. Actions are optional: one per line, askey: description. The build runs on
the cluster and the page follows it, as for every kind.
From the CLI
--questions is sent as task.questions; leave it out only when the records in the folder carry their own questions.
--actions is optional and is sent as
task.actions. The CLI builds from a folder on the datasets volume; pick any other source in the dialog.
For a table, name the columns with --map. inspect prints the columns and a sample first:
Pre-label with the Decision API
Pre-labelling is optional. The provider for this kind isdecision_api, with an endpoint: the URL of a
Decision API deployed in this workspace (the endpoint line of levers9 decide status). For every row it
drafts:
- every answer, with the model’s confidence in it;
- the events the model read most as evidence for that answer, when the served model returns them;
- a chance per action, when the response carries one.
POST /api/datasets/{id}/prelabel with {"provider": {"type": "decision_api", "endpoint": "..."}}. Only rows
without a draft are drafted unless you ask to overwrite, and a draft never overwrites a human label. The
provider none imports only: reviewers start from an empty row.
The annotation screen
Open the dataset. The row list is on the left, as for every kind (Annotation). The editor is laid out the way a row is reviewed: reading first, answering second. Reading. The journey lists the events before the decision time, oldest first; a lead with no history says so. The new call follows it. Events at or after the decision time are listed apart and dimmed: they are not part of the history and cannot be marked as causes. Answering. The questions come one block each, and one question is active at a time. Then the logged decision, then a rationale.
Drafts. A model’s draft is shown preselected, with its confidence and the probability it gave each option.
It is not the reviewer’s answer until they confirm it: by clicking it, by pressing its number, or by validating
the row, which confirms every draft left as it was. The events the draft read most are offered as suggested
causes, and each action shows the draft’s chance when there is one.
Saving. Edits are saved automatically a moment after the last one, and at once when you move to another
row. Validate & next moves the row from pending to validated; every question needs an answer or a
not sure mark first. Flag & next marks a row that is unclear or unusable. When the API refuses a label, its
sentence is shown at the top of the editor.
Statuses, filters, named links for outside reviewers and the NDA gate work as for every kind
(Sharing).
The labels
Every save appends tolabels.tsv. Nothing is ever edited in place, and each line carries the reviewer’s name.
A label’s value is a string; the first four hold JSON. In them, a field is a question’s id (stage,
price_objection) and an option is one of that question’s option keys.
Through the API, one save is one request:
422 with a sentence when something does not fit: a field that is not
one of the dataset’s questions, an option that is not one of that question’s, an event id that is not in the
row’s journey. An event at or after the decision time cannot be a cause.
What each label is for
- Answers are what the model should say. In training they are the target for each question. In evaluation they are the yardstick: the share of rows where the model’s answer is the reviewer’s, for each question and for each option. An answer marked not sure counts for half.
- Causes tell you whether the model read the right events. A served model can return the events it weighed most for an answer (explain); compare them with the events the reviewer marked. A model that gets the answer right from the wrong events is right by luck.
- Counterfactuals become twin records: a copy of the row with one event removed, and the answer the reviewer said would follow. They test whether the answer follows its cause. Ask the model about the row and about its twin: if the answer does not change when the cause is gone, the model did not use it. What a model reads most is a clue, not proof; the twin is the test.
- Outcome and propensity are for learning what an action changes. The history up to the decision, the
action and the reviewed outcome make one example of what happens after that action. Weighting each example by
one over its propensity corrects for the engine having preferred some actions, and the same logs can then
score a new policy before it runs. An
unknownoutcome is left out; it is not a failure. - The reviewer’s action measures agreement with a person in the review lane: the decisions the engine sends to a person because they are close or doubtful (Recommendations). Where the engine’s action and the reviewer’s differ is where to look first.
- The rationale is for the people who read the disagreements. It is not a label a model learns from.
Export
Three files leave a decision dataset.-o the export goes to stdout, so it can be piped.
These are the owner’s exports, and they hold the text as it is. If the dataset’s
policy is Mask, a share link’s gold set holds the masked
call, journey and rationale; add ?text=masked to your own export.jsonl or export.tsv for the same file,
or --text masked to levers9 dataset export.
A decision dataset is not published to a bucket: that copies recordings and a transcript
manifest, and this kind has neither. Download the gold set instead.
The gold set
One line ofexport.jsonl for the record above, wrapped here for reading:
status=validated keeps the rows a person validated, which is what a gold set is. A row with no human answer
is never exported, whatever its status.
Twins
Withtwins=1 every counterfactual adds one more record: the same row with that event removed from the
journey. Its twin_of is the original’s id, its flip names the event and the question, and its labels hold
that one question only, with the answer the reviewer gave for the world without the event. The twin of the
record above has the journey without ev-4 and:
The schema
schema.json is the dataset’s questions in the form a decision model is configured with: every question
becomes an enum field, whatever its type.
labels, so the gold set and the schema always fit each other.
How many rows, and which
- Stratify by answer. A random draw gives you hundreds of the common answer and five of the rare one, and the rare one is where a model fails. Use the drafts, or any label you already have, to pick about the same number of rows for each option of the question you care about most. The reviewer still decides the answer.
- Include rows with no history. A first contact has an empty journey. These rows show whether the model can answer from the call alone, and no event should be marked as a cause on them.
- Include rows where the history contradicts the call. The CRM says the lead is cold and the customer asks for a viewing. An old price objection was settled by a later message. These are the rows where reading the right events changes the answer, and where a counterfactual is worth the most.
- Put two reviewers on a share of the rows, one in ten to one in five, without either seeing the other’s
answers, and measure their agreement: for each question, the share of those rows where both picked the same
option.
labels.tsvkeeps every save with the reviewer’s name, so it can be computed from that file. The simplest way to keep the second reviewer blind is a second dataset built from the same records. Low agreement on a question usually means its option descriptions are unclear: fix them before labelling the rest. No model can be expected to agree with the gold set more than two people agree with each other. - Size sets what you can see. On 200 rows an agreement number moves by five to seven points either way by chance; on 1,000 rows by two to three. A difference smaller than that between two models is a tie.
- Keep the gold set out of training. A lead in the gold set should not be in the data a model trains on.
The
splitcolumn is drawn from the lead for that reason.

