Skip to main content

Workflows

The state machines work moves through — states, the transitions between them, their guards, and the automation each one fires.

Overview​

A workflow is a declaration, not an execution: it names the states a piece of work can be in, the legal moves between them, and what should happen on entering a state. The work itself is a task, and the runtime — not your client — decides whether a move is legal.

Every write that changes the machine bumps version and archives the previous one, so a task already in flight keeps running the machine it started on. That is the point of versioning here: editing a workflow must never rewrite the history of work already done.

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 → Workflows.

Data Model​

Workflow​

FieldTypeDescription
idstringPublic workflow ID.
project_idstringThe owning project.
namestringHuman-readable name.
descriptionstring, nullable
versionintegerBumped on every change to the machine; the previous version is archived.
statesarray of WorkflowStateThe states a task can occupy.
transitionsarray of WorkflowTransitionThe legal moves.
payload_schemaobject, nullableJSON Schema a task's payload is validated against.
created_at / updated_atstring (date-time)

WorkflowState​

FieldTypeDescription
namestringRequired. Unique within the workflow.
initialbooleanWhere a task starts.
terminalbooleanReaching it closes the task.
kindstring, nullablehuman parks the task: the state dispatches nothing and waits for a person.
stalled_afterinteger, nullableSeconds before a task sitting here is reported as stalled.
on_enterobject, nullableThe automation fired on entry — an agent, a tool or an orchestration dispatch.

WorkflowTransition​

FieldTypeDescription
namestringRequired. What you pass when moving a task.
fromarray of stringRequired. States the move is legal from.
tostringRequired. The destination state.
guardobject, nullableJSON Logic over the task; a falsy result refuses the move.
requires_approvalbooleanParks the move as a pending decision instead of applying it.

WorkflowVersion​

FieldTypeDescription
idstringPublic version ID.
workflow_idstringThe workflow it belongs to.
versionintegerThe archived version number.
configobjectstates, transitions and payload_schema as they stood.
labelstring, nullableHuman tag, set with version_label on the write that archived it.
created_bystring, nullableThe user whose write produced it; null for key-authenticated writes.
created_atstring (date-time)

Key Concepts​

Versioning​

PATCH /v1/projects/{project_id}/workflows/{workflow_id} archives the machine as it was and bumps version. A task records its workflow_version at creation and keeps it, so rewiring a workflow never retroactively changes what an in-flight task was allowed to do. Read the archive with GET /v1/projects/{project_id}/workflows/{workflow_id}/versions and put an old machine back with POST /v1/projects/{project_id}/workflows/{workflow_id}/versions/{version}/restore, which is itself a new version rather than an edit of history.

Guards and approvals are the runtime's decision​

A guard is JSON Logic evaluated over the task; requires_approval parks the move for a person. Both are enforced upstream, which is why an illegal move comes back as a 409 from the runtime rather than something this API decides.

Automation hangs off states, not transitions​

on_enter is what makes a workflow do work: entering a state dispatches an agent, a tool or an orchestration, and its result drives the next move. A human state deliberately dispatches nothing. Model a process as a workflow dispatches a writer agent on entering draft and waits for a person in a human review state.

Who may do what​

Every route needs any project member.

Examples​

Declare a two-state workflow​

naturali create-workflow \
--project-id proj_V1StGXR8Z5jdHi6B \
--name invoice-review \
--states '[{ "name": "open", "initial": true }, { "name": "approved", "terminal": true }]' \
--transitions '[{ "name": "approve", "from": ["open"], "to": "approved" }]'

Add a guarded, approval-gated move​

naturali update-workflow \
--project-id proj_V1StGXR8Z5jdHi6B \
--workflow-id wf_V1StGXR8Z5jdHi6B \
--version-label pre-rewire \
--transitions '[{ "name": "approve", "from": ["open"], "to": "approved", "guard": { "<": [{ "var": "task.payload.amount" }, 10000] }, "requires_approval": true }]'

Read the version history​

naturali list-workflow-versions \
--project-id proj_V1StGXR8Z5jdHi6B \
--workflow-id wf_V1StGXR8Z5jdHi6B