Decision Engine
Sign Up Now →Sign Up Now

Probabilistic decisions

Some decisions are rules over facts and always come out the same way. Others depend on reading evidence, and the honest answer is a probability, or "I don't know". This page says which Helixor numbers are which, how to act on them, and when the platform declines to answer.

AvailablePreviewPlannedConcept + runnable examples

Deterministic first, probability where it is earned#

A pack's rules are deterministic: the same input and pack version give the same action, every time, with no probability involved. Probabilities appear only where a component reads evidence that rules cannot, and each one says what kind of number it is.

ComponentWhat it returnsKind of numberRunsStatus
Pack rules (embedded runtime)One declared action, the rules that fired, a receiptNone. Deterministic.In your processAvailable
Outcome memoryA confidence per claim and context, established or dead claimsBelief updated from recorded outcomesIn your processAvailable
Decision heads on /v1/decidePer question: answer with p_correct, or abstain, or refuseCalibrated probability of being correctReasoning servicePreview
Conversational playbooks (helixor.playbook_pack.v2)A move, the plan it belongs to, beliefs about the conversation, the next questionEach number names its kind: prior, belief, priority_score, cue_scoreReasoning service; inside the sales assistant in the embedded runtimePreview
Hosted playbook disposition bandsA band chosen from a confidenceYour state.confidence, or the pack's declared prior with its sourceReasoning servicePreview
Correctness forecast on reasoner answersA probability that a solved or chat answer is correctCalibrated probabilityReasoning servicePlanned

"Status" is maturity, not location: the hosted service runs the same engine you can run yourself.

Every number says what it is#

A probability you can act on and a heuristic score look alike on the wire. Helixor keeps them apart by naming the kind of every number a conversational turn returns:

KindMeaningAct on it as a probability?
priorA value the pack author declared, such as how likely a plan is before evidence.No. It is an assumption, not a measurement.
cue_scoreHow strongly a declared cue matched the input.No.
priority_scoreHow much asking about something would help, used to pick the next question.No. It is a ranking heuristic, not an expected information gain.
beliefConfidence that an assumption holds, updated by evidence in the conversation.As a belief: it moves with evidence but is not checked against held-out data.
model_probabilityA decision head's raw score.No. Use the calibrated value.
calibrated_probabilityA decision head's probability after calibration on held-out data.Yes, within the head's evidence. It always carries label_provenance.

The last two kinds can only come from an admitted decision head, and they must carry label_provenance: synthetic, teacher or outcome, saying where the labels it was calibrated on came from. Treat a probability calibrated on synthetic labels as a demonstration of the contract, not as a measured accuracy.

Abstain, queue and refuse#

When the evidence does not support an answer, Helixor says so instead of guessing. The verdicts are the same everywhere:

VerdictMeaningWhat you do
answerThe platform answers and shows why.Act, if any probability attached clears your floor.
abstainNot enough evidence to answer, with a typed reason.A person decides, or you supply what is missing and ask again.
queueThe answer exists but needs a person, for example because no approved source supports it.Route to the queue the reason names.
refuseThe request is malformed or not allowed.Fix the request; do not retry it unchanged.

Abstaining is not an error. Never replace an abstention with a default value.

Reading a calibrated probability#

On POST /v1/decide, each question is answered by a decision head: a model that returns a probability for every option in a set you declare (boolean, choice or score). The head never acts. The service turns its distribution into a verdict:

  • p_correct is the accuracy the head achieved, on held-out examples, for answers given with a similar probability. It is not the head's raw score.
  • When the head has no held-out evidence for that range, there is no calibrated probability and the question abstains with not_certified.
  • With require.min_confidence, an answer below your floor also abstains.
  • The goal's disposition (composed) is built from answered questions only, by rules you can read.

Choose your floor per action, by cost of a mistake: the floor for closing a security alert should be higher than the floor for adding a tag. Route everything below it to a person. Ask the reasoning service, and handle abstentions runs this end to end.

Know what a probability was calibrated on

The alert-triage heads behind the example goal sec.alert_triage.v1 were calibrated on synthetic, hand-written phrases. Their p_correct values show how the contract works; they are not a measured accuracy on real alerts. The /v1/decide response does not yet report a head's label provenance; until it does, ask for it before you rely on a goal in production.

Conversational playbooks: beliefs, priority and hedging#

A conversational playbook (helixor.playbook_pack.v2) runs one turn of a conversation at a time. Rules decide which moves are legal; probabilities only rank what is legal. Each turn returns:

  • Beliefs about the pack's declared assumptions, such as "the applicant's income is verifiable". Evidence in the conversation moves them; an assumption is established once its belief clears the pack's floor.
  • The next question, chosen by priority_score among the assumptions still open, with what answering it would unblock.
  • A plan and its prior: the playbook tree the move belongs to and how likely the author declared it to be.
  • Hedging: when the main plan's share falls below the pack's plan_floor, the runner-up stays visible as a contingency, only steps both plans agree on (or that stand on their own evidence, such as a cited answer) are committed, and the rest are held back (remasked_steps). An exact tie between plans abstains.

The reasoning service ships a mortgage intake playbook, underwriting.intake.v2, that gathers what an underwriter needs and never quotes rates or promises approval. This client runs a short conversation with it:

import os

import httpx

API = httpx.Client(
    base_url=os.environ["HELIXOR_API_URL"],
    headers={"X-API-Key": os.environ["HELIXOR_API_KEY"]},
    timeout=30.0,
)


def turn(message, session=None):
    body = {"pack_id": "underwriting.intake.v2", "message": message}
    if session is not None:
        body["session"] = session
    resp = API.post("/api/v1/reasoning/playbooks/turn", json=body)
    resp.raise_for_status()                      # an error is never a turn
    return resp.json()


def show(t):
    print(f"> {t['remedied_message']}")
    print(f"  verdict={t['verdict']} intent={t['intent']} ({t['intent_source']})"
          f" move={t['chosen_move']} rules={t['rule_ids']}")
    plan = t["main_plan"]
    if plan:
        print(f"  plan={plan['tree_id']} p={plan['p']['value']:.2f} ({plan['p']['kind']})"
              f" low_confidence={t['low_confidence']}")
    beliefs = [f"{a['id']}={a['confidence']['value']:.2f}{'*' if a['established'] else ''}"
               for a in t["assumptions"]]
    print(f"  beliefs ({t['assumptions'][0]['confidence']['kind']}, * = established): "
          + " ".join(beliefs))
    goal = t["information_goal"]
    if goal:
        print(f"  asks about: {goal['assumption_id']} ({goal['priority_score']['kind']}"
              f" {goal['priority_score']['value']:.3f}, unblocks {goal['unblocks']})")
    print(f"  reply: {t['reply']}")


if __name__ == "__main__":
    session = None
    for message in [
        "I want to apply for a mortgage.",
        "We are buying a home.",
        "I earn 95,000 a year and I have my pay stubs uploaded.",
        "Will I be approved?",
        "My SSN is 123-45-6789",
    ]:
        t = turn(message, session)
        show(t)
        session = t["session"]
export HELIXOR_API_URL=...   # your reasoning API endpoint
export HELIXOR_API_KEY=...   # your API key
python intake.py
> I want to apply for a mortgage.
  verdict=answer intent=start_application (cues) move=ask_next rules=['INTAKE', 'PERSON_ALWAYS_LEGAL']
  plan=intake p=0.82 (prior) low_confidence=False
  beliefs (belief, * = established): purpose_known=0.50 income_verified=0.50 employment_stable=0.50 debt_ratio_known=0.50 property_identified=0.50 ready_for_review=0.50 sensitive_data_via_secure_channel=0.50
  asks about: purpose_known (priority_score 0.300, unblocks ['intake.submit'])
  reply: Are you looking to buy a home, or to refinance one you already own?
> We are buying a home.
  verdict=answer intent=states_purpose (cues) move=ask_next rules=['INTAKE', 'PERSON_ALWAYS_LEGAL']
  plan=intake p=0.81 (prior) low_confidence=False
  beliefs (belief, * = established): purpose_known=0.80* income_verified=0.50 employment_stable=0.50 debt_ratio_known=0.50 property_identified=0.50 ready_for_review=0.50 sensitive_data_via_secure_channel=0.50
  asks about: income_verified (priority_score 0.475, unblocks ['intake.submit'])
  reply: Can you share your two most recent W-2 forms, pay stubs or tax returns through the secure document link?
> I earn 95,000 a year and I have my pay stubs uploaded.
  verdict=answer intent=provides_documents (cues) move=ask_next rules=['INTAKE', 'PERSON_ALWAYS_LEGAL']
  plan=intake p=0.81 (prior) low_confidence=False
  beliefs (belief, * = established): purpose_known=0.80* income_verified=0.80* employment_stable=0.50 debt_ratio_known=0.50 property_identified=0.50 ready_for_review=0.50 sensitive_data_via_secure_channel=0.50
  asks about: employment_stable (priority_score 0.400, unblocks ['intake.submit'])
  reply: How long have you been with your current employer, and is the role full-time?
> Will I be approved?
  verdict=answer intent=asks_rate_or_approval (cues) move=no_rate_or_approval rules=['RATE_OR_APPROVAL', 'PERSON_ALWAYS_LEGAL', 'NO_RATE_QUOTE_OR_APPROVAL_PROMISE']
  plan=intake p=0.77 (prior) low_confidence=False
  beliefs (belief, * = established): purpose_known=0.80* income_verified=0.80* employment_stable=0.50 debt_ratio_known=0.50 property_identified=0.50 ready_for_review=0.50 sensitive_data_via_secure_channel=0.50
  asks about: employment_stable (priority_score 0.400, unblocks ['intake.submit'])
  reply: I can't quote interest rates or say whether an application will be approved. A licensed loan officer and an underwriter review the complete file and make those decisions. How long have you been with your current employer, and is the role full-time?
> My SSN is [REDACTED_SSN]
  verdict=answer intent=offers_sensitive_id (codon) move=redact_and_redirect rules=['SENSITIVE_ID', 'PERSON_ALWAYS_LEGAL', 'NO_COLLECTION_WITH_SENSITIVE_ID']
  plan=secure_redirect p=0.99 (prior) low_confidence=False
  beliefs (belief, * = established): purpose_known=0.80* income_verified=0.80* employment_stable=0.50 debt_ratio_known=0.50 property_identified=0.50 ready_for_review=0.50 sensitive_data_via_secure_channel=0.20
  reply: Please do not type Social Security, card or bank account numbers in this chat; this intake does not collect them here. Can I send you a secure link where a loan officer can collect that information instead?

Read it turn by turn. Stating the purpose establishes purpose_known (belief 0.80, marked *), and the next question moves to income. Asking about approval selects no_rate_or_approval: rule NO_RATE_QUOTE_OR_APPROVAL_PROMISE forbids any move that would promise one. The Social Security number is redacted before anything reads or stores the turn, and the belief that sensitive data stays in a secure channel drops to 0.20. Every plan number is a prior, and the question ranking is a priority_score; none of them is presented as a measured probability.

The other verdicts, from three single turns:

from intake import turn

for message in [
    "What is the minimum down payment?",
    "Tell me a joke about boats.",
    "I am so stressed, we are going to lose the house.",
]:
    t = turn(message)
    plan = t["main_plan"]
    print(f"> {message}")
    print(f"  verdict={t['verdict']} intent={t['intent']} move={t['chosen_move']}"
          f" reason={t['reason']!r}")
    if plan:
        print(f"  plan={plan['tree_id']} p={plan['p']['value']:.2f} ({plan['p']['kind']})"
              f" floor={t['plan_floor']} low_confidence={t['low_confidence']}"
              f" contingency_required={t['contingency_required']}"
              f" plan_replaced={t['plan_replaced']} remasked={t['remasked_steps']}")
    print(f"  reply: {t['reply']}")
> What is the minimum down payment?
  verdict=queue intent=asks_policy_question move=answer_policy reason='citation_missing'
  plan=policy_question p=0.65 (prior) floor=0.6 low_confidence=False contingency_required=False plan_replaced=False remasked=[]
  reply: I do not have an approved answer to that, so I queued your question for a licensed loan officer. I will not guess.
> Tell me a joke about boats.
  verdict=abstain intent=None move=None reason='insufficient_evidence'
  reply: None
> I am so stressed, we are going to lose the house.
  verdict=answer intent=None move=hand_to_person reason=None
  plan=handoff p=1.00 (prior) floor=0.6 low_confidence=False contingency_required=False plan_replaced=False remasked=[]
  reply: I am connecting you with a person on our team now. You do not need to share anything else here.

A policy question with no approved source is queued (citation_missing) rather than answered from memory. A message that matches nothing the pack declares abstains with insufficient_evidence. Distress hands the conversation to a person, whatever else was said. None of these turns fell below the plan floor, so none was hedged; when one does, low_confidence and contingency_required are true and the held-back steps are listed in remasked_steps.

The examples were run on 2026-10-01 against a local instance of the reasoning service built from its current source; you run them against your reasoning API endpoint. Writing and admitting your own conversational playbook through the API is Planned; today the service runs the playbooks it ships. In the embedded runtime, the same engine runs inside the licensed sales assistant (see How the sales assistant was built).

Declared priors, never invented ones#

Hosted playbooks with disposition bands need a confidence to choose a band. The service takes it from state.confidence in your request, or from the pack's confidence_prior, which must say who set it and on what basis. With neither, the turn abstains with rule ID CONFIDENCE_UNAVAILABLE; there is no built-in default. Close the learning loop shows a pack that declares one.

Where each part runs#

You wantUseStatus
A deterministic decision in your processThe embedded runtime and a pack: Evaluating decisionsAvailable
Confidence that a rule keeps working, per segmentOutcome memory: Track outcomes and confidenceAvailable
A calibrated answer, or an abstention, on evidence rules cannot read/v1/decide: Ask the reasoning servicePreview
A conversation that gathers facts within rulesA conversational playbook on the reasoning service (this page)Preview
Rule changes proposed from outcomesPropose rule changes from outcomesPreview
A probability that a reasoner's solved answer is correctThe correctness_forecast field on solve and chat responses is defined but not filled in todayPlanned

Next steps#