Skip to main content
Lab → Decide opens on a grid of use cases. Each card asks the Decision API something different, with the model that is deployed right now, and shows the answers in its own small animation. Pick a card and the view below it changes; the address keeps the pick as a hash, so a use case can be shared as a link: Back and forward move between the cards you picked. Older links to /lab/decide and the guided tour still land on the sales call coach.

The live cards

Every live card works the same way:
  • Try one: a few synthetic inputs, in English and Saudi Arabic. Picking one asks the model at once.
  • The input is editable. Ask the model (or Ctrl+Enter) sends it again.
  • What is asked shows the request in two parts. Your words is the only part that changes: what you typed or picked, sent as the state (a call as a list of agent: and customer: turns, a message or a request as its text). The questions the card always asks the model are fixed for the card: each question in its own words and its options as a list. Raw request shows both together, the exact JSON body sent to POST /api/rl/decide ({state, questions}).
  • Under the hood shows the request and the endpoint’s raw answer, as on the sales call coach.
When no Decision API is running, the cards still play their input and say what would be asked; Deploy on the card’s bar starts one. The search card shows the filters only; it does not search the calls itself. The model only picks categories: exact dates and numbers (“on the 3rd”, “two days ago”, “longer than 5 minutes”) are a job for a value parser beside it, not for these questions. A chip none of your words point to is marked “not in your words”. The QA gauge is the expected level (each level weighted by its probability) on a scale of 0 to 100.

Trained or read from text

Every live card carries a pill, and so does every question under What is asked:
  • trained: the served model was trained on this question, with these option keys, on a call. With a Fin release that is outcome and sentiment with the keys above; with a Lev it is the sentiment question when the model reports it. A base model is trained on none of them.
  • read from text (weak): anything else. The model reads the question and the options from their words, which it does far less well than a question it was trained on. Treat these answers as a demo of the shape, not as numbers to act on, until you have measured them on your own data under Decision models.
A weak card says so loudly: a banner under its title (“never learned these labels; expect confident mistakes”), and its top answer is shown as a guess with a not trained marker instead of a verdict. A high percentage on a weak card is not a sign the answer is right: outside what it was trained on, a model can be confidently wrong (a viewing request read as a payment question, at 78%, is a real example). The animations still play; only the verdict is toned down. A question asked of a message or a search request is always read from text, even when its id matches a trained one: a trained question was trained to read calls. When the model’s own “none of these” option beats every answer, the answer is marked as unsure on the card.

Causality

Causality builder

The first Causality card, /lab/decide#causality-builder, replays one lead’s journey in time order through the live model, as the system really meets a customer. There is no simulator and nothing is mocked: every label and every decision on the screen is the served model’s own answer. A journey starts at the first call. Before the first call’s transcript nothing is known about a customer, so no story has an event from before it. From then on things arrive one at a time: a call’s transcript, a note or a stage from the CRM, a booking, a payment, a message. Pick a story (from the first call to the brochure, a visit and a deposit, retries then a booking and after the visit, a price objection in the call, or empty) and the page draws it as a graph on a canvas: drag a node to move it, drag the background to pan, and use the buttons in the corner to zoom or fit. The graph is always drawn at reading size and no text in it is cut: a long journey scrolls sideways, it is never shrunk to fit. The three rows are the order of cause, named at the left: 1 Read, 2 Understand, 3 Decide, and then round again, because what was decided is written into the history and read at the next arrival.
  • Arrival nodes (Read). One per thing that came from outside, left to right in time and joined by the timeline, with its day counted from the first call. A call shows the customer’s last two lines and how many turns it has.
  • Labels nodes (Understand). Under an arrival, what our model labelled or read at that moment, from everything known by then. The first one lists every answer (the stage now, the interest and its trend, the open objection, what the sales note says, whether an offer would succeed); a later one lists only the answers that changed, with what they were.
  • Decision nodes (Decide). The move, with two bars: the chance with it and the chance if we do nothing. The gap is what the move adds. When the best move is to do nothing there is one bar.
A node appears only where something changed. A labels node is drawn where the model’s reading changed, a decision node where the decision or its chances changed; the last step always shows the decision that stands. An arrival that changed nothing is a small node marked “changed nothing”. So a decision that never moves is drawn once, not once per arrival, and every arrow means a change: down from an arrival to the labels it changed and to the decision, across from one reading to the next, and a coloured arrow from a decision into the next reading, marked “written into the history, read next”. The arrows of the process move in their direction. Each arrival is one real request, cut at that arrival’s time, so nothing that arrived later is in it. They are asked in order, because each step reads what the one before left in the journey: the model’s label of what the customer said, written as a label event by our own model (source Lev, “Lev: interest Warm, confidence 0.91”); the decision as the engine’s action, the ledger event the decision step returns; and one minute after it a row for what the system then did (the offer as our own WhatsApp message, “Follow-up call placed”, “Lead handed to a sales rep for a callback”; nothing when the decision was to do nothing). All three are written the way the served model’s training journeys write them. The sentence above the graph says how the decision went. One line, or the complete decision. Above the graph, “A decision is written into the journey as” has two choices. One line writes “Engine chose X (considered: …)”, the name of what was done, and after it the moves a rule shut, ”; not allowed: call_again (quiet_hours)”. The complete decision writes that line and then everything behind it: what the model read, every option’s chance, what the rules shut, how the pick was made and whether it was flagged. The next step’s request carries whichever was written, as the text of the engine’s event; open a node and “written down for the next step” shows it word for word. With the complete decision chosen the journey is also replayed with the one line, and a sentence beside the switch says how many steps answered differently and what changed at the end, or that nothing did. That is the test of whether a fuller history helps the model you serve: a model reads the complete decision only as well as it was trained on journeys written that way, and one trained on the one line can change its answers just because the longer text repeats its own earlier readings. The page writes the complete decision itself when the endpoint was deployed before the decision step returned it, so the switch works with any deployment. A call nobody answered is not a decision. The system retries such a call by itself, on its own schedule: no sales rep redials it and nothing is chosen. So an unanswered call (a call with no transcript whose line says no answer) is drawn on the timeline as a small dashed node, “Call attempt”, and the model is not asked about it: there is nothing to read and nothing to decide. It stays in the history, so the next real arrival is read knowing the calls were tried. A move that calls the customer again is another thing: a new follow-up call, which is decided like any other move. An outcome does not end the journey. A visit that happened, a deposit that was paid: each arrives like anything else, is read, and a decision follows it. The story with the retries ends with the customer’s visit, and the last node is what is decided after it. Click any node to see its cause. A modal opens on the step that node belongs to, in the order of cause:
  • Known before it: everything the model was given at that moment, oldest first. Rows of ours are marked, our label and our decision, and the newest decision among them is marked as the previous decision read at this step.
  • 1 Read: what arrived, whole. A call shows every turn.
  • 2 Understand: every answer with every option’s probability, and which answers changed with this arrival.
  • 3 Decide: every move with its chance, what a rule shut out, the chance if we do nothing, and why the pick.
  • Written into the history: what this step wrote down, word for word, and a link to the arrival that reads it next.
The part you clicked is marked and the modal opens on it. The step before and The step after walk the chain without closing it. Nothing is asked again: the modal shows what the replay already got from the model. The round button at a node’s corner opens the same modal from the keyboard. Replay the journey plays the graph again one node at a time, in the order it happened: the arrival, what the model read, what was decided, then the next arrival, with each arrow appearing when both of its ends are there. The line beside the button says which step is playing; Show it all stops it. It costs no request, and with reduced motion on it still steps, without the movement. Take an arrival away for the counterfactual: the button inside it, “what if this had not arrived?”, or the same button in its modal. The journey is replayed without it: its node is struck out, the chain joins around it, and the nodes show what the model would have read and decided instead, with what it is in the real journey. The sentence above says what that changed at the end. Put it back returns. Steps before the arrival are not asked again. On a narrow screen the same steps are a stacked list without the canvas; each card opens the same modal. Requests are sent with no exploration, so the same journey always gives the same answers. At most the 12 newest arrivals are steps; older events are history every step reads. Why there is no percentage on most answers. On these questions the served model answers at about 99% nearly always: it was trained on simulated journeys whose answers follow rules, with label smoothing 0.02, so 98 to 99% is the most it can say, and it says it. That figure means “this is my answer”, not how often it is right on a real lead, so the graph prints a percentage only on an answer the model is unsure of (under 90%). Every probability is still under Inspect, Output. A later labels node folds the answers that did not change; click the fold to see them all. When the served model was trained on the action outcomes, each move’s chance is the model’s own (success_if_<action>), tagged “right on average, not per lead yet”: on held-out simulated journeys those chances are right on average and rank who an action helps only weakly. Two closed sections hold the rest:
  • Change the story. The timeline, one editable event per row (time or days before the decision, source, type, text, provenance) with a checkbox to switch an event off without deleting it, and a decision time (events at or after it are greyed and never sent); the new call, in English or Arabic; the questions (the fields the served model was trained on, each switchable, plus custom ones marked not trained); and the decision block: candidates with value and cost in SAR, where each chance comes from, the rules (quiet hours, max count, exclude), the objective, exploration and the review margin. Every edit is replayed. A story’s earlier call keeps the transcript it came with; its line in the timeline can be edited, its turns cannot yet.
  • Every ask, with the raw requests. While it is open, each edit asks the whole journey at once and adds a card: what changed since the previous ask, every answer with all its probabilities and how each moved, the ranked candidates with chance, uplift, score and the rule that excluded one, the propensity, the review and the why. Pin a card to compare every later ask with it.

Wired journey

The second Causality card, /lab/decide#causality-wired, is the one lab where what happens next follows the decision. The other labs replay a customer whose side was written beforehand: whatever is decided, the same messages come. Here a simulated lead is run forward from its first AI voice call, and every time something arrives that needs a decision (a call that connected, a WhatsApp message from the customer, a sales rep’s note, a visit kept or missed) the world stops and the served model is asked. The world then carries out the move it picked: the offer goes out and gets an answer or silence, the follow-up call is placed and picked up or not, a sales rep calls back and writes a note, the customer reserves or goes quiet. The next decision reads all of it, our own earlier decisions included.
  • The journey is a timeline: each row says when, whose it is and what it says, with the row’s source and type as the model reads them. A call opens to its transcript. Our own rows are marked: our label (the model’s reading of what the customer just said), our decision, and what we did (the row the world writes when it carries a decision out).
  • A decision is a card in the timeline: what set it off, what the model read in the journey (its seven answers, one to a line), the chance of a reservation it gave each move, the move that was made, and the line written into the journey for the next decision to read. A move the client’s house rules shut is greyed and says why in plain words; the line names it with the client’s own reason, ”; not allowed: send_offer (no_discount_before_visit)”. Inspect on the card shows the request word for word and its check against the served model’s schema.
  • This client’s house rules are listed above the journey: who hands a lead to a rep, how WhatsApp reaches us, whether an offer can go out before a visit, how many offers and contacts a week. They differ from lead to lead.
  • Show what only the simulator knows puts the truth beside each decision: the lead’s hidden kind, what really held them back at that point, the true chance of every move for this lead as a second bar, which open move was best, what this client’s old rule would have done, and under each of the model’s seven answers whether it is what the journey really shows. At the end it counts how many picks were the best open move and how many readings were right. None of it is ever part of a request to the model.
  • What if we had made another move here? Under a decision, once the journey has ended, each other open move is a button. It plays the same lead again with that move at that decision: everything before it is the same, and what follows is the world’s answer to the move you made, decided on by the model from there. The card says the move was yours and what the model had picked.
  • Next lead, Previous, Play it again and the language (English, Arabic or both in one journey) choose the lead. A lead is a number: the same number gives the same lead.
What is real here is every label and every decision: they are the live model’s own, asked in its own words. The world is simulated, with known causes; it is the world the served model’s training journeys came from, written in wordings that no training journey has, so the model is reading text it has not seen. A reservation is a roll of the dice against the lead’s true chance, so one journey proves nothing by itself: play several. The world runs on the dashboard API, which keeps nothing between calls:
returns the journey so far (rows), and pending: the decision the world waits for, with the state to ask the model on, the moves that are open and the ones a house rule shut. Ask POST /api/rl/recommend with that state, then call again with the answer added to steps ({"move", "interest", "objection", "ledger"}: the move, the model’s label of what the customer said, and the decision’s ledger event). decided holds the truth of each decision made, and ended is reserved, quiet or limit when the journey is over.

Inspect

Every card under “Every ask” in the builder, the decisions of the wired journey, and the labelling, WhatsApp labelling, journey and decision rows in the labs, have an Inspect button. It opens a drawer with three tabs:
  • Graph: for a read or a decision in a lab’s journey, the whole process it belongs to on the same canvas: the journey replayed from the first call, read, understand, decide and round again. For a card of the builder’s log, that ask’s own journey. For the labelling of a single call, the labelling itself: on the left the call that was labelled, every turn of it, and on the right one node per label with the answer and every option’s probability as a bar.
  • Input: what the model actually read, line by line in order, with each event’s id, the windows drawn as brackets, and what it dropped and why, from the model’s own explain block. Below it, each exact request body.
  • Output: every answer with all its probabilities, then the recommendation: the ranked candidates with chance, uplift, score and any rule that excluded them, the propensity, the review and the ledger event.

Causality labs

The other Causality cards open one lab per pack: a sales lead, a visit no-show, rent collection and a support escalation. They ask what would happen if you acted (send an offer, call, hand to a person, or wait) rather than what is true about a call. Each lab works the same way:
  • Pick a journey. A list of 24 prepared customers per pack, with filters for language, outcome and stage, or Randomize. Moving to the next customer is always your click; nothing rolls on by itself.
  • Read the call. The first call is shown in full, right to left when it is in Arabic. Switch the same customer’s call between English and Arabic, or write your own conversation and run the journey with it.
  • Your own conversation. Two parts can be yours: the first call (a voice call) and the WhatsApp messages after it, each one line per turn, agent: ... or customer: ..., in English or Arabic. Write them in the picker’s box, or from the timeline: the call’s row has a Conversation button and every WhatsApp row an Edit the conversation button, which open the conversation to read, with Edit. Run with this conversation rebuilds the journey, and the timeline, the model’s reading, the decision and the decision graph are asked again by themselves; Back to the simulated conversation undoes it. Only what you changed is marked as yours. A typed call is told to the model as what it is: its line says what our own model labelled it, never a summary the page guessed, and the simulated labeller’s and reviewer’s rows are left out, since they never read it. A typed WhatsApp line moves no stage and does not count toward the contact cap.
  • The WhatsApp conversation, labelled again. After the call, a journey’s WhatsApp conversation is a real exchange: the customer’s message, our reply, the customer again (in every prepared journey of the sales lead lab, and wherever the customer wrote in the other three). The served model labels it as it labelled the call: the customer’s sentiment in it, their interest now and an open objection, read as one conversation with the call’s label already in its history. The timeline has a row for it, Model labelled the WhatsApp conversation, with the answers and the label that was written into the journey after the last message. Its Inspect opens the conversation that was labelled, every message of it, beside one node per label with every option’s probability; the exact request (the conversation, and the history it was read with) is under Input. In the decision graph a customer’s message is labelled again the same way and its label is read by the next step.
  • Whose chances decide, and where. In the sales lead lab the served model has chances of its own for the lab’s moves, so the decision is made on them, for a simulated journey as for a conversation you typed: the request gives no chance. The journey is decided step by step, as production decides it: after the first call, after the customer’s last message in a WhatsApp conversation, at a sales note or a booking, and at the lab’s own decision time. A stage move, a label, a call nobody answered and a line of our own are read and not decided on, and neither is a message the customer follows with another one a few minutes later: nobody decides in the middle of a conversation. Each earlier decision has its row in the timeline (Next-best action, at this point: the move, its chance, the chance with nothing done, what quiet hours shut, the label written from what the customer just said, and what the system then did), and it is written into the journey whole with the row for what was done after it, so the last request holds every earlier decision in full and the last decision reads them: an offer already sent makes another one worth less. The earlier steps do not explore. The simulated customer never saw them: what they do next was scripted without them, and the lab still scores the last decision only. The journey is sent as production sends it. The first call is the new input, the model labels it, and the label is written into the journey; the WhatsApp conversation is labelled again the same way; every WhatsApp message is a row of the source WhatsApp whose text opens with the speaker (customer: ..., agent: ...), the way the model’s training journeys write one; and the moves go under the names the model knows (the lab’s “Wait 3 days” is sent as no_action, the baseline). The decision is asked with the model’s own seven questions about the journey in the same request (the customer’s interest now, an open objection, how engaged they have been, what the latest sales note says, our last step, how many offers we sent in 14 days, how much we contacted them in 7), when the served model was trained on all seven; the decision row lists what it read above what it decided, and under each answer the row of the journey it read most (“read most from our own earlier decision, 4 May 03:55 (98%)”); the call’s row shows the label that was written into the journey for it. In the decision graph and the Causality builder, where a journey is asked step by step, the same seven are asked at every step, and a decision of ours goes into every later request as the row it is: its one line of text, which the model reads, with the move, the decision’s id, its odds and the complete decision as data beside it (what was read, every option with its chance, what a rule shut, the pick, the review). The decision row and the action card say whose chances decided, and Inspect on the decision shows each request word for word. The chances the lab learned on its own simulated logs are then not used for the decision. The model’s chances follow its own labels, the interest above all. On 5 October 2026, on the model served then, for one typed call followed by three different WhatsApp conversations: a customer who asked for the offer was labelled hot, and the offer got 55% against 38% with nothing done; one who wrote not to be contacted again was labelled cold, 22% against 7%; an angry one was labelled cold, 21% against 7%. Without the label of the WhatsApp conversation, only the call’s label was in the journey and the offer stayed at 23 to 25% whatever was written. The pick was the offer in all three: the step weighs a sale’s value against a small cost and has no do-not-contact rule. A served model that was not trained on the lab’s moves, and the other three labs, where no model has chances of its own for the moves, still decide on the chances learned on the simulated customer; a typed conversation then changes only the labels, and the lab says so.
  • The model’s own schema, and a check of every request. A journey model answers well when it is asked in the words it was trained on, about a journey written the way its training journeys were. The page keeps one copy of the served model’s schema, written from the release’s own files: every question with its option keys in the model’s order and each option’s description, the moves the model has a chance of its own for, and the kinds of row its training journeys hold. Everything the labs and the builder ask is built from that copy: the seven questions of a decision, the five journey questions, the two that label what a customer said, and for the first call the model’s own question for a call that just ended (outcome_now) with its sentiment question. Inspect checks each request against it and says so under the request, “Checked against fin2-v7’s own schema. 7 questions, 4 moves, 15 journey rows: all as fin2-v7 was trained”, or lists what is not: a question asked in other words, an option the model has that was not offered, a move it has no chance for, a row its journeys never hold, a WhatsApp line that does not say whose words it is, a decision with no row after it for what was done. A row the model knows from its older journey set only (a reviewer’s confirmation, a deposit) is listed as outside the decision journeys, not as wrong. The builder’s palette adds rows in that same form, and its source suggestions are the sources of those journeys (CRM, AI call, WhatsApp, Lev, reviewer, engine). When the served model is another release than the copy’s, Inspect says the request was not checked.
  • Simulate this decision. Under the action card: 100, 500 or 2,000 imagined leads with this same journey, each rolled once and drawn as a dot: succeeds anyway, succeeds because of the move, lost by the move, or does not succeed either way. One roll serves both worlds (the engine deciding, and nothing done), so nobody is counted twice; the engine’s own dice are rolled too, so a few leads get another move, as exploration does. Again rolls new dice and adds the runs up. It follows the decision above, whatever journey or conversation made it, and it plays out that decision’s chances: it shows what they mean and adds no evidence.
  • The timeline. Every event of the journey in order. What the model answered is a short list, the question on the left and the answer on the right, one to a line: the call’s labels, the journey’s reading, and the decision. On an answer a percentage is printed only where the model is unsure (under 90%); every probability is under Inspect. The decision is all percentages: the chance of success with the move, the chance if we do nothing, what the move adds in points, how it was chosen, and how often the move is taken. That last line is the propensity, the logged chance of the move taken, said as a share of the time (“95% of the time, the other 5% another move is tried, to keep learning”) and never as a bare 0.950. The timeline ends at the outcome because a lab scores one decision against its outcome. A real journey does not end there: the outcome arrives like anything else and the next decision reads it, which the builder’s story with the visit shows.
  • Decision graph. The second step of every lab is the same graph as the builder’s: this customer’s journey replayed from the first call through the live model, a node per thing that arrived, a labels node where the model’s reading changed and a decision node where the decision changed, every node opening its cause, an arrival to take away, and the replay. A call the lab’s customer did not pick up is an automatic retry there too. Only what arrived from outside is given (the calls, the customer’s messages, what the CRM returned); the lab’s simulated labels and actions are left out, and the model’s own label and the decision are asked for at every step and fed forward. The moves and their chances are the lab’s own, given with the request and the same at every step, so what moves a decision here is the rules: quiet hours, and caps that count what was already done. The served model was trained on sales-lead journeys, so its reading of the other three worlds is weak, and the step says so.
  • What the model did. The served model, the exact request, the latency (in the browser, through the dashboard and on the model) and every probability, marked live or simulated, and how each label changed the engine’s inputs. The model only labels what happened in the call; the causal part is the engine choosing an action from chances learned on logged decisions that carry their propensities.
  • Why the old rule fooled us. Replays 500 past customers the old rule handled: what the logs say, what really happens, and what can be recovered when the logs include exploration. Those panels are simulated from end to end.
  • Our model, asked live (in that step, sales lead lab). The one panel there that is not simulated: 100, 250 or 500 leads of the same kind are sent to the served model, one whole journey each. Per lead it is asked as production asks: it labels the call, that label is written into the journey, it labels the WhatsApp conversation after it the same way, and then it decides, with its own chance for every move (the request gives none). A dot waits at the top until the model has answered and lands in the bucket of the move it picked. Under the dots, on the same leads: what doing nothing, the old rule, our model, the lab’s own engine and the ceiling earn per lead; which move the model made for each hidden kind of lead; and the chance it gave its picks against the chance they really have. The leads stay simulated, because only a simulated world knows what the same lead would have done under another move; every decision is the model’s. It is three requests a lead (100 leads take about 55 seconds); the leads are the same every time and an answer is kept for the session. On one run of 100 leads on 5 October 2026 the served model earned SAR 29,744 a lead, against SAR 24,376 for the old rule, SAR 31,666 for the lab’s engine (which learned its chances on that world’s own logs) and SAR 34,272 at the ceiling. It sent the offer to 88 of the 100, and it was too hopeful: it gave its picks 45% where that world gives 28%. Before the WhatsApp conversation was labelled, the same 100 leads gave SAR 30,435 a lead with a more even mix of moves. The other three labs do not offer the run, since the served model has chances of its own for the sales-lead moves only.
Everything except the model’s labels is a simulated world with known causes; the page marks every simulated number. What a lab sends to the model starts at the customer’s first call: the lead’s creation in the CRM is never sent, and a call’s label is written as our own model’s (source Lev).