fin, and the registry and
some other pages call it Fin 2: it is the same model. Like any decision model it scores, it does not write.
Three things set it apart from a plain classifier, and each has a step below:
- It reads the lead’s history, cut at the moment of the decision, beside the call.
- Every answer comes with the events it came from, and that is tested by changing one event and checking that the answer changes.
- It answers what would happen under each action, doing nothing included, and the decision step records the probability of the action it took.
The responses on this page were recorded on
fin2-v5. The release served since 6 October 2026, fin2-v7,
answers the same requests with other numbers, and it has seven questions of its own about a journey (the
customer’s interest now, an open objection, how engaged they have been, what the latest sales note says, our last
step, the offers we sent, how much we contacted them). It also reads our own earlier decisions, so write them the
way its training journeys do: the decision’s line with what a rule shut, then a row for what was done
(Writing it back). GET $API/health lists the fields and moves
the served release knows, and the Decide playground checks every request it sends
against that release’s schema.Before you start
levers9 decide status, do not
pin it. The routes used here:
The dashboard’s
POST /api/rl/decide, /api/rl/decide/batch and /api/rl/recommend take the same state,
questions, decision and explain, and hold the token for you.
The lead
One synthetic lead, written for this page: no client data. Five days of history, then a call. The history starts at the lead’s first call: before a first call’s transcript the system knows nothing about a customer.Lev is our
own model, which labelled that call.
The call that has just ended, on 27 August at 18:15:
1. One call
The simplest use: a call and a question. Send the call as a list of turns, never one joined string.call.json:
choiceis the answer,probabilitieshas one number per option of the question and they add up to one.answer_confidenceis the model’s own probability of the chosen option before the “none of these” slot is taken out. When “none of these” wins, the answer carries"low_confidence": true: none of your options fits.latency_msis the model’s time on the card. The round trip adds network time.- Ask as many questions as you like in one request: all of them are answered in the same pass.
POST $API/v1/systemone with "model": "lev-latest" returns the same answers in the System One shape.
2. The call with the lead’s journey
To give the model the history, send the state as an object with three parts:
An event:
journey.json (three of the nine questions shown; every option’s description is a short sentence, as in step 1):
source is free text: write who produced each event. Here Lev is our own model, which labelled the first call.
Each answer now carries evidence: the three events it read most, with their share of its attention.
Without the call (
segments left out), interest_now comes back warm (0.99), the last reading in the
history: the “hot” above is read from what the customer said in the call. So the same request serves a trigger
with no new call, such as “no reply in 48 hours”: send the journey and the decision time alone.
3. Check that it reads causes
An answer that names an event is a claim, and it can be tested: take one event away and ask again. This is a twin: the same lead in every respect but one. Twin A: the reviewer’s correction never happened. Send the same request withoutev-4 (“Reviewer changed
objection from price to none”). Two answers change, and only those two:
The other seven answers are the same as before. Without the correction, the model’s label of a price objection
stands, and an offer is no longer likely to work.
Twin B: the sales note never happened. Send the request without
ev-7. One answer changes:
This is the test to run on your own data before you trust the model on it: for a few leads you know well,
remove or change the event you believe decides an answer, and check that the answer follows. An answer that does
not move was not read from that event. On our test set of synthetic journeys, 96% of the answers that should
change do, and 99% of the others stay; through this endpoint, on 60 pairs, 96% and 100%. The Decide page’s
causality builder does the same by hand.
4. Ask what it read
Add"explain": true and the response carries, beside the answers, what the model actually read. Here one more
event is added to the journey, dated after the decision:
readis every line in the order the model read it. Each event became one line with its order, its day before the decision, its source and type, its text and its provenance; the call’s turns follow.droppedsays what was not read and why.ev-9is after the decision time, so it was cut before the model saw anything: the model cannot be told the future by accident. A journey longer than 64 lines keeps its newest events and folds the older ones into one summary line; those are listed here too.evidence_allgives every event’s share for each answer, andattention_on_historyhow much of the answer’s attention was on the journey and how much on the call.
5. Ask for a decision
Add adecision block: the actions you could take, what a success through each is worth, what each costs, your
rules and your objective. The response then carries a recommendation. The block is described in full on
Recommendations; here it is on our lead.
p_successis the chance that this lead reserves if that action is taken now. The request gave none, so the model answered one question per candidate (chance_source: "model").upliftis what the action adds over doing nothing. This lead is very likely to reserve whatever happens (0.91 if left alone), so no action adds anything, and with the objectiveupliftthe step recommends doing nothing. A model that only predicted “will this lead reserve” would rank this lead first and spend an offer on it. That is the point of asking for the baseline.whyis written from the numbers, never invented.ledger_eventis the event to append to the lead’s journey, so the next request sees what was done. Its text is one line; with"ledger": "complete"in the decision block it says the whole decision (what was read, every option’s chance, what the rules shut, how the pick was made), and itsrecordholds the same as data either way. See Writing it back.
"exploration": 0.1 the same request came back with "action": "no_action",
"explored": false and "propensity": 0.925. One time in ten the step draws an allowed action at random, so with
four allowed the best one is taken with probability 0.925 and each of the others with 0.025; when another one is
taken the response says "explored": true and carries its 0.025. Keep that propensity with the outcome. It is
what lets you measure later, from your own logs, what each action really changes.
Your own chances. Pass chances and the model is not asked for them:
Three ways to use it
After every call. Step 1, with your tenant’s own questions. Use/decide/batch to score every prefix of a
call, or a day of calls, in one pass.
At a trigger with no call. “No reply in 48 hours”, “viewing attended yesterday”: send the journey and the
decision time with no segments, and ask where the lead stands (next_stage, interest_now, sales_cue,
worth_calling_again). The four came back for our lead as contacted, warm, ready and yes.
A review queue. Send to a person the answers with low_confidence, the recommendations with
"review": true, and any answer whose evidence points at an event a person has since corrected. The reviewer
sees the events behind the answer, not only the answer.
Why this is not a plain classifier
A discriminative model learns to map an input to a label. That is enough to say what a call was about. It is not enough to decide what to do with a lead, for three reasons, and Lev 2 is built around them.
In one sentence: a classifier answers “what is true of this lead”, and a decision needs “what changes if we
act”. The first is a reading task. The second is about a future that is only seen for the action that was taken,
so it needs the cut in time, the test by twins, a do-nothing baseline and the recorded probability of each action.
Those four are what “causal” means on this page.
Questions it answers best
The model reads any question from its text, but it answers best the ones it was taught, asked with the same id and the same option keys:
For a tenant’s own call questions (its outcome and sentiment), use the option keys its schema lists.
Limits
- Every journey it has been measured on is synthetic. Nothing has been tested on a tenant’s real journeys yet. Run the twin test of step 3 on your own leads first.
- Its chances per action are right on average, not per lead. The plan is an outcome table built from your
logged decisions on the model’s answers; on simulated leads it orders leads by the effect of an action far
better than the model’s own chances (0.84 against 0.48 on our measure, where 1 is a perfect order). It is not a
part of the Decision API yet. Until then, pass your own
chances. - Two rare objections, location and financing, are not told apart. It knows an objection that is not the price is open, and not which one.
- A label it has never seen needs examples. Rewording a taught option works; a new option does not.
- Events need ids and times. Without an
idthere is no evidence to point at; without atimean event cannot be cut and is always read. - A wrong field is answered with a sentence. A rule that names an unknown action, an objective that needs a
baseline when none is given, a chance outside 0 to 1:
422with the field named, for exampleobjective uplift needs a baseline: name the do-nothing candidate in decision.baseline, or include no_action.

