Skip to main content
Decisions.ask sends some state and one or more typed questions to a decision model and returns typed answers with probabilities. It never returns free text, so there is nothing to parse. Two providers sit behind one shape, TypeSafe Jev by default and OpenAI Decisions when the call carries images, sets region: 'eu' or sets zeroRetention, or when Jev is down. Available in tools, jobs, webhooks, triggers, processors and workflow code steps. Verified against lua-cli 3.48.0.

Quick example

Every answer can also be { type: 'refused' }, so check type before you read its fields. A single question returns its answer directly instead of a map.

Question types

Each question type is named after the field its answer carries. options is an object of label to description, and the answer’s choice is typed to those labels. levels is an ordered list from low to high, either strings or { label, description }. score is a position along the levels and can fall between two of them; level is the nearest one. probabilities holds one entry per option or level and they sum to 1. Any answer can also be { type: 'refused' } when the provider declines to judge the content. Only OpenAI returns refused. Jev never does, but a call reaches OpenAI through images, region, zeroRetention or a fallback, so handle it on every call.

Methods

ask(request)

string | string[] | object
required
What the questions are about. Use an object with descriptive keys for anything beyond one piece of text, and refer to the keys in the instructions.
Record<string, DecisionQuestion>
required
One to 64 questions built with Decisions.odds, Decisions.choice and Decisions.score. The answer keys match the question keys.
{ data: string, mimeType: string }[]
Up to 4 base64 images (image/png, image/jpeg, image/webp, image/gif), 5 MB each. Only OpenAI takes images, so they route the call to OpenAI.
'auto' | 'jev' | 'openai'
default:"auto"
auto picks Jev unless the call carries images, sets region: 'eu' or sets zeroRetention. A pinned provider is never switched silently. It also overrides the image, region and zero-retention rules: provider: 'jev' with images fails with decision_rejected, and provider: 'jev' with region: 'eu' runs on Jev.
string
A model of the chosen provider, jev-latest, jev-preview or gpt-6-luna. Defaults to the provider’s current model. A model of the other provider fails with decision_invalid. After a fallback, the other provider uses its own default model.
'jev' | 'openai' | 'none'
default:"the other provider"
The provider to try once when the first one times out or is unavailable. It defaults to the other provider, and to none when provider is pinned. none keeps the call on one provider. Calls with images, region: 'eu' or zeroRetention never fall back to Jev.
'eu'
Routes the call to OpenAI’s EU endpoint.
boolean
default:"false"
Routes the call to OpenAI. No retention setting is sent with the call.
number
default:"0.7"
Choice and score answers below this confidence come back with uncertain: true so your code can escalate instead of acting.
number
default:"3000"
Per-call timeout in milliseconds. A value outside 100 to 15000 is clamped to that range, not rejected.
{ ttlSeconds: number }
Caches identical calls per agent for up to a day. A longer ttlSeconds is capped at 86400. A cache hit costs nothing and carries cached: true.
'summary' | 'full' | 'none'
default:"summary"
How much of state the log row keeps. summary records its size and top-level keys. full records the first 8,000 characters of the state as sent, with no redaction, and sets truncated when the state was longer. none records nothing.
Returns a DecisionResult.

ask(state, question, options?)

The single-question form. options takes every field above except state, questions and images, and the promise resolves to that one answer.

odds(instructions, criteria?)

criteria spells out what counts as yes and what counts as no when the instruction alone is ambiguous.

choice(instructions, options)

Two to 64 options. Each key is a label the answer can return and each value describes when it applies.

score(instructions, levels)

Two to 10 levels, ordered from low to high.

Examples

Route a message before the agent sees it

Gate a webhook on a yes-or-no

Judge an image

OpenAI can refuse to judge an image, so handle damaged.type === 'refused' too.

Errors

Decisions.ask and Decisions.run throw a DecisionError with these fields.

Limits and cost

A decision costs 0.005 actions when Jev answers and 0.01 actions when OpenAI answers. A call that falls back is charged once, at the price of the provider that answered. Cached hits and failed calls cost nothing. Every call writes one row to the agent’s logs under the decisions source, visible with lua logs --type decisions. The row carries the provider, the routing rule, every answer with its probabilities, the tokens, costUsd and any fallback. costUsd is the provider’s token-based cost estimate, not what you are charged.
  • LuaDecision declares a named, versioned decision on the agent and runs it with Decisions.run.
  • AI for free-text generation.