> ## Documentation Index
> Fetch the complete documentation index at: https://levers9.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Tutorial: decisions from a call and a lead's journey

> One lead through the Decision API, step by step, with the real response to every request: a call alone, the call with the lead's history, the events each answer came from, what the model read, and a recommended action

This tutorial takes one lead through the [Decision API](https://levers9.mintlify.site/lab/decision-api.md) with the model that reads journeys.
Every response on this page is the real answer of the served model to the request above it.

The model is **Lev 2**, the second generation of Lev. It is deployed under the name `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:

1. It reads the lead's **history**, cut at the moment of the decision, beside the call.
2. Every answer comes with the **events it came from**, and that is tested by changing one event and checking
   that the answer changes.
3. It answers **what would happen under each action**, doing nothing included, and the decision step records the
   probability of the action it took.

[Why this is not a plain classifier](#why-this-is-not-a-plain-classifier) explains the difference for an engineer.

> [!NOTE]
>
> 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](https://levers9.mintlify.site/lab/recommendation.md#writing-it-back-the-ledger)). `GET $API/health` lists the fields and moves
> the served release knows, and the [Decide playground](https://levers9.mintlify.site/lab/decide-playground.md) checks every request it sends
> against that release's schema.

## Before you start

```bash theme={null}
levers9 decide status                                   # the endpoint, the model, its health
export API=$(levers9 decide status | awk '/^endpoint/ {print $2}')    # https://levers9.uselevers.com/asgi/fin/v11
export TOKEN=...                                        # a workspace token
```

The version at the end of the URL goes up with every deploy, so read it from `levers9 decide status`, do not
pin it. The routes used here:

| route | what it does |
| - | - |
| `POST $API/decide` | answers `questions` about one `state`; with a `decision` block, also a recommendation |
| `POST $API/decide/batch` | the same for up to 200 `states` in one pass |
| `POST $API/v1/systemone` | the same answers in TypeSafe's System One shape, for a client written against it |

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.

| id | time | source and type | what happened |
| - | - | - | - |
| ev-1 | 22 Aug, 21:41 | AI call, call | Short call, customer asked to talk another time, 3:12 |
| ev-2 | 22 Aug, 21:42 | Lev, label | Lev: interest Warm, confidence 0.8 |
| ev-3 | 22 Aug, 21:46 | Lev, label | objection=price (0.53, review band) |
| ev-4 | 23 Aug, 23:27 | reviewer, correction | Reviewer changed objection from price to none |
| ev-5 | 23 Aug, 23:29 | Lev, label | Lev: qualified yes, confidence 0.84 |
| ev-6 | 24 Aug, 13:35 | reviewer, correction | Correction: qualified was yes, should be no |
| ev-7 | 25 Aug, 23:06 | CRM, note | Sales note: the customer says they have decided and are only sorting the paperwork |
| ev-8 | 27 Aug, 04:11 | CRM, action | Source tagged: north Riyadh campaign |

The call that has just ended, on 27 August at 18:15:

```
agent:    Good evening, Hala calling from Dana Hills about the interest you registered.
customer: Sure, I have a few minutes.
agent:    A quick update: the second phase of Dana Hills is open, from 1.6M SAR.
customer: Yes, this is exactly what we're looking for.
customer: Please send me the brochure and the payment plan.
agent:    Sure, I'll send them on WhatsApp now.
agent:    Any other questions?
```

## 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`:

```json theme={null}
{
  "state": [
    "agent: Good evening, Hala calling from Dana Hills about the interest you registered.",
    "customer: Sure, I have a few minutes.",
    "agent: A quick update: the second phase of Dana Hills is open, from 1.6M SAR.",
    "customer: Yes, this is exactly what we're looking for.",
    "customer: Please send me the brochure and the payment plan.",
    "agent: Sure, I'll send them on WhatsApp now.",
    "agent: Any other questions?"
  ],
  "questions": {
    "outcome_now": {
      "type": "choice",
      "instructions": "A real estate sales agent called a customer about a housing project. What was the result of the call that just ended?",
      "criteria": {
        "booked_unit": "the customer reserved or booked a unit",
        "booked_visit": "the customer booked or confirmed a visit or an appointment",
        "requested_info": "the customer asked for more information, such as prices or a brochure",
        "call_back": "the customer asked to be called back later",
        "other": "none of the above, for example a wrong number or no real conversation"
      }
    }
  }
}
```

```bash theme={null}
curl -s -X POST $API/decide -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d @call.json
```

```json theme={null}
{
  "answers": {
    "outcome_now": {
      "type": "choice",
      "choice": "requested_info",
      "confidence": 0.9826,
      "probabilities": {"booked_unit": 0.0034, "booked_visit": 0.0034, "requested_info": 0.9861, "call_back": 0.0036, "other": 0.0035},
      "answer_confidence": 0.9825
    }
  },
  "usage": {},
  "latency_ms": 33.8,
  "model": "fin2-v5",
  "fine_tuned": true,
  "device": "NVIDIA RTX PRO 6000 Blackwell Server Edition"
}
```

* `choice` is the answer, `probabilities` has one number per option of the question and they add up to one.
* `answer_confidence` is 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_ms` is 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:

| part | what it is |
| - | - |
| `segments` | the new call, as `{"role", "text"}` turns. Leave it out to ask about the history alone |
| `journey` | the lead's events, oldest first |
| `decision_time` | the moment of the decision, ISO 8601 with an offset. Events at or after it are never read |

An event:

| field | required | what it is |
| - | - | - |
| `id` | yes, to get evidence | your own id for the event. Answers point back at it |
| `time` | yes | when it happened, ISO 8601 with an offset |
| `source` | yes | who produced it: `CRM`, `AI call`, `reviewer`, the model that labelled a call (`Lev`), `engine` |
| `type` | yes | what kind: `call`, `label`, `correction`, `stage`, `note`, `message`, `action` |
| `text` | yes | what happened, in a short sentence, in English or Arabic |
| `provenance` | no | `model` for something a model said, `confirmed` or `corrected` for something a person checked |
| `propensity` | no | for an engine action: the probability it was taken with (the `ledger_event` of step 5 carries it) |

`journey.json` (three of the nine questions shown; every option's description is a short sentence, as in step 1):

```json theme={null}
{
  "state": {
    "segments": [
      {"role": "agent", "text": "Good evening, Hala calling from Dana Hills about the interest you registered."},
      {"role": "customer", "text": "Sure, I have a few minutes."},
      {"role": "agent", "text": "A quick update: the second phase of Dana Hills is open, from 1.6M SAR."},
      {"role": "customer", "text": "Yes, this is exactly what we're looking for."},
      {"role": "customer", "text": "Please send me the brochure and the payment plan."},
      {"role": "agent", "text": "Sure, I'll send them on WhatsApp now."},
      {"role": "agent", "text": "Any other questions?"}
    ],
    "journey": [
      {"id": "ev-1", "time": "2026-08-22T21:41:14+03:00", "source": "AI call", "type": "call", "text": "Short call, customer asked to talk another time, 3:12"},
      {"id": "ev-2", "time": "2026-08-22T21:42:52+03:00", "source": "Lev", "type": "label", "text": "Lev: interest Warm, confidence 0.8", "provenance": "model"},
      {"id": "ev-3", "time": "2026-08-22T21:46:46+03:00", "source": "Lev", "type": "label", "text": "objection=price (0.53, review band)", "provenance": "model"},
      {"id": "ev-4", "time": "2026-08-23T23:27:04+03:00", "source": "reviewer", "type": "correction", "text": "Reviewer changed objection from price to none", "provenance": "corrected"},
      {"id": "ev-5", "time": "2026-08-23T23:29:13+03:00", "source": "Lev", "type": "label", "text": "Lev: qualified yes, confidence 0.84", "provenance": "model"},
      {"id": "ev-6", "time": "2026-08-24T13:35:05+03:00", "source": "reviewer", "type": "correction", "text": "Correction: qualified was yes, should be no", "provenance": "corrected"},
      {"id": "ev-7", "time": "2026-08-25T23:06:37+03:00", "source": "CRM", "type": "note", "text": "Sales note: the customer says they have decided and are only sorting the paperwork"},
      {"id": "ev-8", "time": "2026-08-27T04:11:44+03:00", "source": "CRM", "type": "action", "text": "Source tagged: north Riyadh campaign"}
    ],
    "decision_time": "2026-08-27T18:15:23+03:00"
  },
  "questions": {
    "next_stage": {"type": "choice",
      "instructions": "Given everything that happened to this lead and the new input, which stage of the sales pipeline is the lead in now?",
      "criteria": {"lead_received": "...", "contacted": "...", "qualified": "...", "viewing_booked": "...", "viewing_attended": "...", "reserved": "...", "spa_signed": "...", "lost": "..."}},
    "live_objection": {"type": "choice",
      "instructions": "Which objection of the customer is still open at the decision?",
      "criteria": {"price": "the customer's open objection is the price", "location": "the customer's open objection is the location",
                   "financing": "the customer's open objection is financing", "none": "no objection is open"}},
    "sales_cue": {"type": "choice",
      "instructions": "What does the latest sales note say about where the customer stands?",
      "criteria": {"ready": "...", "nudge": "...", "not_now": "...", "space": "...", "none": "..."}}
  }
}
```

```bash theme={null}
curl -s -X POST $API/decide -H "Authorization: Bearer $TOKEN" -H 'content-type: application/json' -d @journey.json
```

`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.

```json theme={null}
"next_stage": {
  "type": "choice", "choice": "contacted", "confidence": 0.9813, "answer_confidence": 0.981,
  "probabilities": {"lead_received": 0.0024, "contacted": 0.9836, "qualified": 0.0024, "viewing_booked": 0.0023,
                    "viewing_attended": 0.0023, "reserved": 0.0023, "spa_signed": 0.0023, "lost": 0.0024},
  "evidence": [
    {"id": "ev-1", "weight": 1.0, "source": "AI call", "type": "call", "time": "2026-08-22T21:41:14+03:00"},
    {"id": "ev-8", "weight": 0.0, "source": "CRM", "type": "action", "time": "2026-08-27T04:11:44+03:00"},
    {"id": "ev-2", "weight": 0.0, "source": "Lev", "type": "label", "time": "2026-08-22T21:42:52+03:00"}
  ]
},
"sales_cue": {
  "type": "choice", "choice": "ready", "confidence": 0.9859, "answer_confidence": 0.9853,
  "probabilities": {"ready": 0.9887, "nudge": 0.003, "not_now": 0.0028, "space": 0.0027, "none": 0.0028},
  "evidence": [
    {"id": "ev-7", "weight": 1.0, "source": "CRM", "type": "note", "time": "2026-08-25T23:06:37+03:00"},
    {"id": "ev-8", "weight": 0.0, "source": "CRM", "type": "action", "time": "2026-08-27T04:11:44+03:00"},
    {"id": "ev-5", "weight": 0.0, "source": "Lev", "type": "label", "time": "2026-08-23T23:29:13+03:00"}
  ]
}
```

Asked nine questions about this lead in one request (44 ms of model time), it answered:

| question | answer | probability | the event it read most |
| - | - | - | - |
| `next_stage` | contacted | 0.98 | ev-1, the call that connected |
| `intent_trend` | rising | 0.99 | ev-2, the interest label |
| `label_consistent` | contradicted\_by\_history | 0.99 | ev-6, the reviewer's correction |
| `offer_success` | likely | 0.99 | ev-4, the objection corrected to none |
| `worth_calling_again` | yes | 0.99 | ev-6 |
| `interest_now` | hot | 0.99 | ev-2 |
| `live_objection` | none | 0.99 | ev-6 |
| `engagement` | high | 0.99 | ev-6 |
| `sales_cue` | ready | 0.99 | ev-7, the sales note |

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 without `ev-4` ("Reviewer changed
objection from price to none"). Two answers change, and only those two:

| question | with ev-4 | without ev-4 | the event it now reads most |
| - | - | - | - |
| `live_objection` | none (0.99) | **price** (0.99) | ev-3, the label "objection=price", weight 1.0 |
| `offer_success` | likely (0.99) | **unlikely** (0.99) | ev-3, weight 1.0 |

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:

| question | with ev-7 | without ev-7 |
| - | - | - |
| `sales_cue` | ready (0.99) | **none** (0.99) |

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](https://levers9.mintlify.site/lab/decide-playground.md#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:

```json theme={null}
{"id": "ev-9", "time": "2026-08-28T10:00:00+03:00", "source": "CRM", "type": "stage", "text": "Reservation paid"}
```

```json theme={null}
"explain": {
  "read": [
    {"i": 0, "speaker": "event", "text": "#1 day -5 [AI call | call] Short call, customer asked to talk another time, 3:12 (5 days before)", "event_id": "ev-1"},
    {"i": 1, "speaker": "event", "text": "#2 day -5 [Lev | label] Lev: interest Warm, confidence 0.8 (model, 5 days before)", "event_id": "ev-2"},
    "...",
    {"i": 7, "speaker": "event", "text": "#8 day -1 [CRM | action] Source tagged: north Riyadh campaign (14 hours before)", "event_id": "ev-8"},
    {"i": 8, "speaker": "agent", "text": "agent: Good evening, Hala calling from Dana Hills about the interest you registered.", "event_id": null},
    "..."
  ],
  "windows": [{"index": 0, "turns": [0, 11]}, {"index": 1, "turns": [11, 14]}],
  "evidence_all": {
    "sales_cue": [{"id": "ev-7", "weight": 0.999998}, {"id": "ev-8", "weight": 0.000002}, {"id": "ev-5", "weight": 0.0}, "..."]
  },
  "attention_on_history": {"next_stage": 0.997209, "sales_cue": 0.8621},
  "dropped": [{"id": "ev-9", "why": "at or after the decision time (2026-08-27T18:15:23+03:00): not read"}]
}
```

* `read` is 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.
* `dropped` says what was not read and why. `ev-9` is 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_all` gives every event's share for each answer, and `attention_on_history` how much of the answer's
  attention was on the journey and how much on the call.

Attention is a clue to what was read, not proof of cause. The proof is the twin of step 3.

## 5. Ask for a decision

Add a `decision` 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](https://levers9.mintlify.site/lab/recommendation.md); here it is on our lead.

```json theme={null}
{
  "state": {"segments": ["..."], "journey": ["..."], "decision_time": "2026-08-27T18:15:23+03:00"},
  "decision": {
    "question": "What should we do next for this lead?",
    "candidates": {
      "send_offer":  {"description": "send a written price offer",          "value_sar": 105400, "cost_sar": 1},
      "call_again":  {"description": "make an automated follow-up call",    "value_sar": 122400, "cost_sar": 6},
      "hand_to_rep": {"description": "hand the lead to a human sales rep",  "value_sar": 122400, "cost_sar": 180},
      "no_action":   {"description": "do nothing for now",                  "value_sar": 122400, "cost_sar": 0}
    },
    "baseline": "no_action",
    "objective": "uplift",
    "exploration": 0,
    "rules": {"quiet_hours": {"start": "22:00", "end": "09:00", "actions": ["call_again"]},
              "max_count": {"call_again": 3, "window_days": 7}}
  }
}
```

```json theme={null}
"recommendation": {
  "decision_id": "dec-b4391f6023ce",
  "action": "no_action",
  "objective": "uplift",
  "ranked": [
    {"action": "no_action",   "p_success": 0.9062, "value_sar": 122400, "cost_sar": 0,   "score": 0,         "allowed": true, "uplift": 0.0,     "chance_source": "model", "trained": true},
    {"action": "send_offer",  "p_success": 0.902,  "value_sar": 105400, "cost_sar": 1,   "score": -443.68,   "allowed": true, "uplift": -0.0042, "chance_source": "model", "trained": true},
    {"action": "hand_to_rep", "p_success": 0.8748, "value_sar": 122400, "cost_sar": 180, "score": -4023.36,  "allowed": true, "uplift": -0.0314, "chance_source": "model", "trained": true},
    {"action": "call_again",  "p_success": 0.7803, "value_sar": 122400, "cost_sar": 6,   "score": -15416.16, "allowed": true, "uplift": -0.1259, "chance_source": "model", "trained": true}
  ],
  "baseline": {"action": "no_action", "p_success": 0.9062, "source": "model"},
  "explored": false,
  "exploration": 0.0,
  "propensity": 1.0,
  "review": false,
  "review_reasons": [],
  "why": "no_action: the baseline, score SAR 0; no other allowed action's uplift pays for its cost, best of 4 allowed",
  "ledger_event": {"source": "engine", "type": "action", "action": "no_action", "decision_id": "dec-b4391f6023ce", "propensity": 1.0,
                   "time": "2026-08-27T18:15:23+03:00", "text": "Engine chose no_action (considered: no_action, send_offer, hand_to_rep, call_again)"}
}
```

Read it from the top:

* **`p_success`** is 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"`).
* **`uplift`** is 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 objective `uplift` the 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.
* **`why`** is written from the numbers, never invented.
* **`ledger_event`** is 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 its `record` holds the same as data either way.
  See [Writing it back](https://levers9.mintlify.site/lab/recommendation.md#writing-it-back-the-ledger).

**Exploration.** With `"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:

```json theme={null}
"chances": {"send_offer": 0.90, "call_again": 0.88, "hand_to_rep": 0.88, "no_action": 0.88}
```

```json theme={null}
"action": "send_offer",
"why": "send_offer: uplift +2 points over doing nothing (90% vs 88%) x SAR 105,400 - SAR 1 = SAR 2,107, best of 4 allowed"
```

> [!WARNING]
>
> The model's own chances per action are right on average and nearly the same for every kind of lead: on simulated
> leads it gives about the same lift to a lead an offer would tip as to one it would push away. Use them to see the
> step work. For decisions that matter, pass `chances` from your own estimate until the outcome table is served
> (see [Limits](#limits)).

## 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.

| | a plain classifier | Lev 2 |
| - | - | - |
| **What it may read** | whatever is in the row it is given. If a field was filled in after the outcome, it learns from it, and looks very accurate until it is used live | the events before `decision_time`, and nothing else. The cut is made before the model reads anything, and `dropped` shows it |
| **What an answer rests on** | unknown. It may read the cause, or something that merely goes with it | the events it read are returned with every answer, and the model is tested on twins: change one cause and the answer must change, change something else and it must stay |
| **What it is tested on** | agreement with labels | agreement, and the twin test of step 3 |
| **"Will this lead buy?" against "what should we do?"** | predicts who buys. Ranked by that score, the best leads are the ones who would buy anyway, so every offer looks like a success | asks a different question per action, doing nothing included: `success_if_<action>`. What an action is worth is the difference from doing nothing |
| **Learning from past decisions** | past offers went to the leads an old rule liked, so "offers work" is learned from the rule's taste, not from offers | the probability each past action was taken with is modelled and used, which is what corrects for the old rule's choices |
| **Keeping the logs usable** | nothing | the decision step explores a little and writes the `propensity` of every action it takes into the ledger |

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:

| id | options |
| - | - |
| `next_stage` | lead\_received, contacted, qualified, viewing\_booked, viewing\_attended, reserved, spa\_signed, lost |
| `intent_trend` | rising, stable, falling, unknown |
| `label_consistent` | consistent, contradicted\_by\_history, no\_history |
| `worth_calling_again` | yes, no |
| `offer_success` | likely, unlikely |
| `outcome_now` | booked\_unit, booked\_visit, requested\_info, call\_back, other |
| `interest_now` | hot, warm, cold, unknown |
| `live_objection` | price, location, financing, none |
| `engagement` | high, medium, low |
| `sales_cue` | ready, nudge, not\_now, space, none |
| `success_if_<action>` | yes, no, for `send_offer`, `call_again`, `hand_to_rep`, `no_action`; asked for you by a `decision` block |

For a tenant's own call questions (its outcome and sentiment), use the option keys its schema lists.

> [!WARNING]
>
> A question it was not taught gets an answer that looks just as confident. Asked of this lead "Did the customer
> ask about parking?", it said yes with 0.9955. The customer asked for the brochure and the payment plan. Measure
> any new question on labelled examples before you use it.

## 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 `id` there is no evidence to point at; without a `time` an 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: `422` with the field named, for example
  `objective uplift needs a baseline: name the do-nothing candidate in decision.baseline, or include no_action`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.