Skip to main content

Deciders

Versioned question sets answered against a state, producing append-only decisions confined to each question's answer space.

Overview​

A decider is a named set of questions. Asked about a state — a ticket, a message, a record — it produces a decision: one answer per question, each one of the options, levels or truth values the question declares. The questions live on the decider, never in the request, so no call site can widen what is judged; and every decision names the question-set version it was answered under.

A decider is answered by exactly one backend: a tool-less agent, which is shown the questions and the state and answers within a schema compiled from them, or an http or pipeline tool of yours, which receives the questions and the state and answers in the contract below.

This module is a verbatim mirror of the runtime: every field, method, status code and error shape is the runtime's own, re-rooted under the project in the path. It serves two collections — /deciders and /decisions — under that project prefix.

On every plan

Deciders are not gated by plan. A decision is a run: it counts against the plan's monthly runs, and one answered by an agent on a managed provider spends the balance like any other generation.

See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Deciders.

Data Model​

Decider​

FieldTypeDescription
idstringPublic decider ID (dcd_ prefix).
project_idstringThe owning project.
namestringUnique within the project.
descriptionstring, nullable
agent_idstring, nullableThe tool-less agent that answers; null when a tool does.
tool_idstring, nullableThe http or pipeline tool that answers; null when an agent does.
versionintegerThe question set's version; only a change to questions moves it.
questionsobjectQuestion id → { type, instructions, criteria }, 1 to 20 of them.
created_atstring (date-time)
updated_atstring (date-time)

Decision​

FieldTypeDescription
idstringPublic decision ID (dec_ prefix) — the polling handle.
project_idstringThe owning project.
decider_idstringThe decider asked; kept after the decider is deleted.
decider_versionintegerThe question-set version it was answered under.
statusstringqueued, running, completed, failed.
answersobject, nullableQuestion id → answer. Null until the decision completes, then never rewritten.
errorobject, nullable{ code, message } on a failed decision.
generation_idstring, nullableThe generation an agent answered in; null for a tool.
metadataobject, nullableYour own identifiers, written once with the decision.
created_at / updated_atstring (date-time)

The evaluated state is not stored. Put the id of what was judged in metadata to find the decision again.

Key Concepts​

Questions and answers​

typecriteriaAnswer
choiceoption → description, 2 to 20 options{ "type": "choice", "choice": "<option>" }
scoreordered level descriptions, 2 to 20 levels{ "type": "score", "score": <index>, "legend": "<level>" }
booleanoptional { "false": "…", "true": "…" }{ "type": "boolean", "value": <bool> }

A score is the level's zero-based index; legend is that level's text, taken from the decider. An answer from a tool may also carry probabilities, a distribution over the answer space. It is carried as the tool supplied it.

Versions​

Changing questions archives the previous set and moves version; a rename, a new description or a new backend does not. Read the criteria a decision was answered under with GET /v1/projects/{project_id}/deciders/{decider_id}/versions/{version}, and pass expected_version on PATCH /v1/projects/{project_id}/deciders/{decider_id} to refuse a stale write with 409.

A tool backend​

The tool is called with { "state": …, "questions": … } and answers { "answers": { "<question id>": { "choice" | "score" | "value": …, "probabilities"?: {…} } } } for every question. Any other answer field, such as confidence, fails the decision with DECISION_ANSWER_INVALID; so does a value outside the answer space. An engine that speaks a close dialect is bridged with a pipeline tool: a step's input maps the request, the pipeline's output maps the answer.

A client, mcp tool, or a tool whose preset_parameters set state or questions, cannot back a decider: 400 DECIDER_TOOL_NOT_CALLABLE. A tool a decider names cannot be deleted (409 TOOL_HAS_DEPENDENTS), nor can its agent.

Requesting a decision​

POST /v1/projects/{project_id}/deciders/{decider_id}/decisions takes state (any JSON value), optional metadata and wait. With wait omitted the answer is 201 with the decision queued; poll GET /v1/projects/{project_id}/decisions/{decision_id} or subscribe to decision.completed and decision.failed on a webhook. With wait: true it is 201 with the decision settled. A failed decision is terminal: request a new one.

Before anything reaches the runtime, a decision clears the same gates a generation does. The decider is read for its backend: a decision an agent answers is refused while that agent's managed model has no price (503 model_not_priced) or the balance is negative (402 insufficient_credit); one a tool answers is a run that spends no credit. Either is refused when a Free account is out of runs (403 plan_limit_reached).

Examples​

Create a decider over an agent​

naturali create-decider \
--project-id proj_V1StGXR8Z5jdHi6B \
--name support-triage \
--agent-id agent_V1StGXR8Z5jdHi6B \
--questions '{
"route": {
"type": "choice",
"instructions": "Which team should own this ticket?",
"criteria": { "billing": "Charges, refunds, invoices", "technical": "Errors, outages" }
},
"needs_human": {
"type": "boolean",
"instructions": "Must a person read this before any automated reply?"
}
}'

Request a decision and wait for it​

naturali create-decision \
--project-id proj_V1StGXR8Z5jdHi6B \
--decider-id dcd_V1StGXR8Z5jdHi6B \
--state 'I was charged twice for the same order.' \
--metadata '{ "ticket_id": "ZD-48213" }' \
--wait true