Destination triggers

When a destination fires — conditions on answers, confidence, weighted levels and composites, final actions, a minimum confidence per route, score levels, probability thresholds — and in which order.

Every destination has a when object. It fires when the decision's final action is one of when.actions (if you set any) and every condition in when.conditions holds:

{
  "conditions": [
    { "field": "route", "on": "output", "operator": "eq", "value": "sales" },
    { "field": "route", "on": "confidence", "operator": "gte", "value": 0.8 }
  ],
  "actions": ["continue"]
}
PropertyTypeDefaultDescription
conditionsarray[]Up to 5 conditions, all of which must hold. Same shape as a policy condition: field, on (output, confidence or score), operator and value.
actionsarray—Fire only when the final action is one of these: continue, block, escalate, fallback.

An empty when fires after every decision.

Per option

The most common trigger, and what the Destination selector of an option creates: the question's answer equals the option.

{ "conditions": [{ "field": "route", "on": "output", "operator": "eq", "value": "sales" }] }

Use neq for "every route except spam". The reserved other option is an answer like any other: a destination on route = other fires when no option fits — while the decision's fallback action (escalate by default) tells your code that a person should look.

Minimum confidence per route

The Minimum confidence field of a destination adds a confidence ≥ condition on the same question, so the route only fires when the engine is sure enough:

{
  "conditions": [
    { "field": "route", "on": "output", "operator": "eq", "value": "sales" },
    { "field": "route", "on": "confidence", "operator": "gte", "value": 0.8 }
  ]
}

It is independent of the question's own minConfidence, which changes the action: a low-confidence answer gets the fallback action and action_reason.type = "low_confidence", whatever the destinations. Combine both, or give each route its own threshold — a refund route can demand 0.9 while a "send an article" route accepts 0.6. See confidence-gated routing.

Per score level

A score question's selectors create a condition on the most likely level:

{ "conditions": [{ "field": "priority", "on": "output", "operator": "eq", "value": "critical" }] }

Levels can also be compared by number (1 = the first level) with any operator — "operator": "gte", "value": 3 is high or critical on a four-level scale — or by weighted level with "on": "score", which can land between levels: "on": "score", "operator": "gte", "value": 3.5.

Probability thresholds

Destination when … ≥ … on a probability question creates a threshold on the probability of yes, 0.8 by default:

{ "conditions": [{ "field": "purchase_intent", "on": "output", "operator": "gte", "value": 0.8 }] }

Composites and other answers

A condition can use any question or composite of the decision, so a destination doesn't have to follow the question its panel belongs to: lead_score ≥ 0.75 sends hot leads to a workflow whatever the route.

{
  "conditions": [
    { "field": "lead_score", "on": "output", "operator": "gte", "value": 0.75 },
    { "field": "route", "on": "output", "operator": "neq", "value": "spam" }
  ]
}

Final actions

actions ties a destination to the verdict instead of an answer — every escalate posts to Slack, every block opens a moderation ticket:

{ "actions": ["escalate"] }

With both conditions and actions, both must hold.

Validation

Conditions follow the rules of policy conditions, and they are checked when you save or deploy — 422 INVALID_SCHEMA otherwise:

  • the field must be a question or a composite of the decision;
  • choice answers only support eq and neq, and the value must be one of the options;
  • probabilities and confidence are numbers from 0 to 1 — 0.7, not 70;
  • score levels go from 1 to the number of levels, or use a level label;
  • composites go from −1 to 1 (0 to 1 with positive weights).

Evaluation

After the action is chosen, Dcision walks the destinations in schema order:

  1. A destination that is switched off ("enabled": false) is ignored.
  2. Its actions and conditions are checked. Destinations that don't match leave no entry.
  3. Its params are resolved. When a param marked required has no value, the destination doesn't fire: its entry says "status": "skipped", "reason": "missing_param" and names the param.
  4. At most 10 destinations fire per execution, and at most 3 of them can make the caller wait — LLM answers and agents in sync mode. Beyond that, the entry says "status": "skipped", "reason": "limit".
  5. The rest run: replies and functions are returned, LLM and sync agent calls are awaited in parallel, and background deliveries are queued.

When the engine fails

With onEngineError: "fallback", an engine failure answers 200 with the fallback action and no answers. Then:

  • destinations with conditions don't fire — there is no answer to compare;
  • destinations without conditions do, filtered by actions when set — the right place for an alert such as escalate → notify the on-call person;
  • result.*, confidence.*, scores.* and composites.* variables are null.

Fallback answers are retried for real

Engine-error fallbacks aren't stored for idempotency: retrying the call with the same Idempotency-Key runs the decision again, and its destinations fire again with new delivery IDs. Deduplicate such alerts on your side — for example by your own record ID.

Replays

An idempotent replay returns the stored response — including its destinations entries and their delivery IDs — and fires nothing again. The SDKs, though, dispatch the functions of a replayed response again, so make function handlers idempotent.

On this page