RECIPE / RETRY TRIAGE

Classify a failure before you retry it.

A heartbeat timeout and a broken request call for different moves. Retrying everything wastes spend and can repeat side effects. Failing on everything throws away work that would have recovered. Retry triage asks two bounded questions about the failure. Your code then picks the move.

Try it in the playground with the Retry triage tab.

The request

Put the failure in state: where it happened, the status or exception type, the error text, and anything that changes the answer, such as the time left on a lease or the attempt number. Leave out tokens, keys, and other secrets.

curl https://api.verdictdecision.com/v1/decisions \
  -H "Authorization: Bearer $VERDICT_DECISION_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  --data '{
  "model": "laya-typed-decisions",
  "state": {
    "surface": "agent executor",
    "status": "TimeoutError",
    "error": "Heartbeat to the coordinator timed out after 10 s.",
    "lease_seconds_remaining": 71,
    "attempt": 1
  },
  "questions": {
    "failure_class": {
      "type": "choice",
      "instructions": "Classify this failure to decide how the caller should respond.",
      "criteria": {
        "transient": "Temporary; the same request is likely to succeed shortly",
        "deterministic": "Will fail again until code, state, or input changes",
        "outcome_unknown": "The request may have completed upstream; the result was not observed",
        "auth_or_config": "Credentials, permissions, or configuration are wrong"
      }
    },
    "safe_to_resend": {
      "type": "proposition",
      "instructions": "Is resending the identical request with the same Idempotency-Key likely to succeed without duplicate side effects?"
    }
  }
}'

Failure classes

The safe_to_resend proposition asks whether sending the same request again with the same Idempotency-Key is likely to succeed without repeating side effects.

Map the answers to a move

Keep the mapping in your code, where you can read and test it. The thresholds below are starting points. Initial abstention thresholds are not yet fitted, so check abstained and send those cases to a person.

// Your policy, not the model's. Permissions and tests stay authoritative.
function nextMove(answers) {
  const kind = answers.failure_class;
  const resend = answers.safe_to_resend;
  if (kind.abstained || resend.abstained) return "show_to_user";
  if (kind.decision === "auth_or_config") return "stop_and_reauthenticate";
  if (kind.decision === "outcome_unknown") return "reconcile_then_decide";
  if (kind.decision === "transient" && resend.probability >= 0.7) return "retry_same_key";
  return "stop_and_show_reason";
}
def next_move(answers):
    kind = answers["failure_class"]
    resend = answers["safe_to_resend"]
    if kind["abstained"] or resend["abstained"]:
        return "show_to_user"
    if kind["decision"] == "auth_or_config":
        return "stop_and_reauthenticate"
    if kind["decision"] == "outcome_unknown":
        return "reconcile_then_decide"
    if kind["decision"] == "transient" and resend["probability"] >= 0.7:
        return "retry_same_key"
    return "stop_and_show_reason"

What the live model returned

We sent the request above and five failures from our own harness test to laya-typed-decisions (then called verdict-laya-typed-1) on the live API on 2026-09-29. Every failure_class answer abstained, so the policy above sends all six to a person. safe_to_resend was false or abstained every time, so nothing would have been resent blind.

The leading class matched the expected one for the 409, the 504 and the 401, and missed the heartbeat timeout and the sequence error. It never led by enough to act on. Warm calls took about 0.23 s on the worker.

Choosing a model

laya-typed-decisions (aliases laya-typed, verdict-laya-typed-1) is the 421M triage model. It is fast, but on the failures above it abstained every time, so treat its answers as a first pass and keep the abstention branch. Use decider-4b-v2 when the state includes a long trace or several earlier failures.

Limits

A probability is an assessment. It does not grant permission to retry and does not show that a retry is safe. Apply your own permission and idempotency rules. If the Decision call itself fails or times out, fall back to your existing retry rule instead of blocking the caller.

Verified retry recipe · API docs