Skip to main content

Tasks

Units of work moving through a workflow — each transition guarded, dispatched and recorded.

Overview​

A task is one item of work: it names the workflow it runs on, the state it is currently in, and a caller-owned payload. Moving it is not an update — you fire a transition by name and the runtime decides whether that move is legal from the current state, evaluating the workflow's guard and parking the move if it requires approval.

Entering a state can dispatch automation (the workflow's on_enter), so a task is also the thing that drives agents, tools and orchestrations without a caller in the loop. Every move is appended to an immutable history, including the ones a machine made.

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.

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

Data Model​

Task​

FieldTypeDescription
idstringPublic task ID.
project_idstringThe owning project.
workflow_idstringThe workflow it runs on.
workflow_versionintegerFixed at creation — the machine this task keeps running.
titlestring
statestringThe current state's name.
statusstringopen or closed (reaching a terminal state closes it).
payloadobject, nullableCaller-owned data; readable by guards as task.payload and by dispatch mappings.
metadataobject, nullableCaller-owned key/value attribution — invisible to guards and payload_writes, unlike payload.
last_resultany, nullableServer-owned: the current state's last completed dispatch result.
assigneestring, nullable
active_dispatchobject, nullable{ kind, id, status } of the dispatch in flight.
automation_statusstring, nullablerunning, completed, failed, unrouted, paused; null until a state with automation is entered.
automation_chain_depthintegerHow many machine-driven moves have run back-to-back — the loop bound.
pending_transitionstring, nullableA requires_approval move parked awaiting a human.
pause_requested_atstring (date-time), nullableWhen this task's automation was paused, or null when no pause is in force.
pause_reasonstring, nullableThe reason given with the pause, when one was.
entered_state_atstring (date-time)
created_at / updated_atstring (date-time)

TaskTransition (history entry)​

FieldTypeDescription
idstringPublic entry ID.
task_idstring
from_state / to_statestringThe move.
transitionstringThe transition's name.
principal_kindstringuser, api_key, automation or approval — who moved it.
principal_idstring, nullableThe acting principal; for key auth, the key's own id.
generation_idstring, nullableSet when an agent dispatch's generation caused the move.
orchestration_run_idstring, nullableSet when an orchestration run caused it.
tool_idstring, nullableSet when a tool dispatch caused it.
notestring, nullable
created_atstring (date-time)

Key Concepts​

You fire a transition, not a state​

POST /v1/projects/{project_id}/tasks/{task_id}/transitions takes a transition name. An undeclared name is a 400; a name that is not legal from the current state is a 409. Both come from the runtime, which is what makes the workflow the single source of truth about legal moves — PATCH /v1/projects/{project_id}/tasks/{task_id} edits the title, assignee and payload, and never the state. Model a process as a workflow fires revise to send a task back to an earlier state.

A task is pinned to a workflow version​

workflow_version is stamped at creation and never moves. Rewiring the workflow leaves in-flight tasks on the machine they started on.

Automation, and the loop bound​

If the entered state declares on_enter, the task dispatches it and reports progress through active_dispatch and automation_status. A dispatch may itself transition the task, which is recorded with principal_kind: automation; automation_chain_depth counts how many such moves have run back-to-back, which is what stops a workflow from looping forever without a human or a client.

A pause stops the automation, not the board​

POST /v1/projects/{project_id}/tasks/{task_id}/pause suppresses every dispatch a state would start — an agent generation, a tool call, an orchestration — and abandons a retry chain's remaining attempts, so the task stops spending without losing its place. Transitions keep working under it: a move costs nothing while the dispatch it would start is suppressed, so a board stays usable. A state entered under a pause records automation_status: paused, and POST /v1/projects/{project_id}/tasks/{task_id}/resume is the only thing that lifts one — it starts the dispatch the pause suppressed and leaves a state whose work had already completed alone.

A negative balance stops what a task would dispatch​

Entering a state can generate, so POST /v1/projects/{project_id}/tasks/{task_id}/transitions and POST /v1/projects/{project_id}/tasks/{task_id}/resume answer 402 insufficient_credit and dispatch nothing while the project owes for usage already served. It is the project owner's balance. See A negative balance stops managed generation.

A Free account that has used its plan's monthly runs answers 403 plan_limit_reached with resource: "runs" here as well, on its own credential too. See A plan's run allowance can stop generation.

A move into a state that declares no on_enter is refused with the rest: which states carry one is in the workflow, not in the request. Reading, closing and pausing are never refused — a pause is how the debt stops growing.

An automation chain already dispatching is paused instead. A chain runs on nobody's request, so nothing can refuse it; the reconciliation that discovers the debt pauses every open task whose automation_status is running, with a pause_reason saying to top up. A task whose automation has come to rest is left alone, since what would move it on is a transition, which is refused meanwhile.

The history is the audit trail​

GET /v1/projects/{project_id}/tasks/{task_id}/history returns every move in order, each naming who made it and what caused it — the generation, the run or the tool. It is append-only. Model a process as a workflow reads one with a move back to an earlier state in it.

Who may do what​

Every route needs any project member.

Examples​

Open a task on a workflow​

naturali create-task \
--project-id proj_V1StGXR8Z5jdHi6B \
--workflow-id wf_V1StGXR8Z5jdHi6B \
--title "Invoice 4471" \
--payload '{ "amount": 4200, "customer": "acme" }'

Move it through a declared transition​

naturali transition-task \
--project-id proj_V1StGXR8Z5jdHi6B \
--task-id task_V1StGXR8Z5jdHi6B \
--transition approve \
--note "under the 10k limit"

Read who moved it, and why​

naturali get-task-history \
--project-id proj_V1StGXR8Z5jdHi6B \
--task-id task_V1StGXR8Z5jdHi6B