Skip to content
Docs / Decisions and rules

Decisions and rules

Most domains have one question whose options are the actions: route, refund, escalate, block. That's the domain's decision_question. When it's asked, the answer carries a decision block with your rules applied.

json
"decision": {
  "question": "action",
  "action": "escalate-senior",
  "confidence": 0.91,
  "blocked_by_rules": ["refund-over-250-needs-a-human"],
  "blocked_actions": ["refund"],
  "route": "act"
}
FieldMeaning
questionthe question whose options are the actions
actionthe final action with your rules applied; null if your rules block every action
confidencehow sure it is of that action, after the rules
blocked_by_rulesthe rules that blocked at least one action for this case
blocked_actionsthe actions they blocked
rule_chancesonly when a rule on what the message is about applied: each such rule and the chance it applies here (see below)
routeact, review or ask, from the model's safety bar

The decision question's answer comes back with your rules applied too, so blocked options are at 0 and answers[decision.question].choice equals decision.action.

Rules

A rule says which actions are never allowed, or the only ones allowed, when its conditions hold. Rules read your state fields, or another question's answer (what a message is about; see below).

json
{ "id": "over-500-needs-a-person", "description": "Never approve more than $500 without a person",
  "when": [{ "field": "amount", "op": "gt", "value": 500 }], "block": ["approve"] }

{ "id": "fraud-goes-to-a-person", "description": "Fraud-flagged accounts always go to a person",
  "when": [{ "field": "fraud_flag", "op": "is_true" }], "allow_only": ["escalate"] }
  • Operators: eq, ne, gt, gte, lt, lte, in, not_in, is_true, is_false, is_missing, contains.
  • Combine with {"any": [...]} and {"all": [...]}. Every condition in when must hold.
  • Enforced on every decision, in code, whatever the model thinks. Training reports count how often each rule applied and changed a decision; violations are always 0.

You rarely write these by hand: describe them to the set-up agent in plain words ("never approve over 500") and it adds them. You can also PATCH /v1/domains/{domain} with a rules list.

Rules on what a message is about

A rule can read another question's answer instead of a field: "Always block-card when topic is lost_stolen and card_status is active or frozen", "Never answer-faq when topic is unrecognised". Say them to the set-up agent like any other rule, or write the condition as { "answer": "topic", "op": "eq", "value": "lost_stolen" }.

What a message is about is never certain, so these rules are enforced whenever there's a real chance they apply, not only when it's the likeliest reading:

  • The rule applies when the chance it holds is at least its min_chance (0.15 unless you set another, from 0 to 1).
  • When it applies but isn't sure to (a chance under 0.5) and it changes the action, the decision goes to your review queue instead of being acted on.
  • The decision lists each such rule's chance in rule_chances, and the decision log shows it next to the rule.
  • It works the same hosted and in a downloaded model, and it reads the answer it needs even when you only ask for the decision.

A rule on a field is exact; if your system records the fact ("card_reported_lost is true"), a rule on that field is the strongest form.

Act, review, ask

Each model sets its own safety bar after every training run: the lowest confidence at which its automatic answers are at least 97% right on your held-back cases. There's nothing to set. The bar turns confidence into a route:

WhenrouteWhat to do
at or above the bar, in a language the model is strong inactact on it
below the bar (or another language), still below after it was double-checkedreviewhand it to a person; it joins your unsure queue
your rules allow no actionaskhand it to a person; it's in the unsure queue too

A double-checked message is translated to English and decided again; if that's still unsure, your day-one backend answers (Jev or an OpenAI-compatible model) or, without one, our hosted backup; if neither can answer, it's held for a person. See Languages and Confidence.

The decision log

Every decision is kept, so you can look back at what happened: to debug an integration, or for your own records. In the console, open a decision model and choose Decisions; or use the API, the CLI, the SDK or your coding agent.

  • The table: newest first, with the time, a short summary of the case, the final decision, its confidence, where it came from (source), the route and the version.
  • Filters: a time range, source (trained, translated, fallback, rule, backup, local_fallback), route, an answer (action:refund), the version, and whether an outcome was reported. Search looks for words in the case's text, or a decision id.
  • One decision in full: the case as you sent it, every answer with its probabilities and confidence, the final decision and the rules of yours that blocked options ("Blocked by your rule: never approve over 500"), the version, the detected language, how long it took, and the outcome once reported. Cases your downloaded models decided offline and synced later are marked.
  • Mark wrong: give the right answer for a decision (Mark wrong in the console, or POST /v1/decisions/{decision_id}/wrong). It's recorded as its outcome, marked in the log, and the next retrain learns it. See Mark a decision wrong.
  • Export: CSV or JSONL with the same filters, up to 100,000 decisions per file.
  • Deleting: one decision, or every decision before a date (confirmed with the model's name). Deleting is permanent.

The log stays readable after your free trial ends: it's your data, like your downloads.

bash
curl "https://api.canonopylabs.com/v1/domains/refunds/decisions?route=review&since=2026-09-01&answer=action:approve" \
  -H "Authorization: Bearer $CANONOPY_API_KEY"
curl "https://api.canonopylabs.com/v1/domains/refunds/decisions/export?format=csv&since=2026-09-01" \
  -H "Authorization: Bearer $CANONOPY_API_KEY" -o refunds-decisions.csv

How long decisions are kept

Decisions are kept for 90 days unless you change it in the console's Settings (or PATCH /v1/settings): 30, 90 or 365 days, or until you delete them. Once a day, older decisions are deleted permanently in every decision model of the workspace.

That includes decisions with an outcome or an answer from the unsure queue, and open cases in the queue. Once they're deleted, later versions no longer learn from them, and a question still in day-one mode loses the outcomes it had counted. Versions already trained keep what they learned. If you rely on your outcomes to keep improving, keep decisions for 365 days or until you delete them.