Your first workflow
A workflow orchestrates a process by chaining steps: decisions, gateways, human tasks, and service tasks. This continues the loan example: score risk, price the loan, branch on approval, get underwriter sign-off, disburse funds.
The model
{
"id": "loan_application",
"name": "Loan application",
"start": "score",
"steps": [
{ "type": "decision_task", "id": "score", "decision": "loan_risk", "next": "price" },
{ "type": "decision_task", "id": "price", "decision": "loan_pricing", "next": "gate" },
{
"type": "gateway",
"id": "gate",
"branches": [{ "variable": "approved", "op": "eq", "value": true, "next": "review" }],
"default": "reject"
},
{ "type": "human_task", "id": "review", "assignee": "underwriter", "next": "disburse" },
{
"type": "service_task",
"id": "disburse",
"action": "disburse-funds",
"next": "done",
"retry": [{ "errors": ["States.TaskFailed"], "max_attempts": 3, "interval_seconds": 5, "backoff_rate": 2.0 }],
"catch": [{ "errors": ["States.ALL"], "next": "reject" }]
},
{ "type": "end", "id": "done" },
{ "type": "end", "id": "reject" }
]
}
Step types used here:
decision_task— calls a named decision (must exist in the same project/release) with the whole workflow state as input; its outputs land under$.decisions.<stepId>.gateway— branches on a prior step’s output.gatereads$.decisions.price.approved(theloan_pricingdecision’sapprovedoutput) via thevariablefield.human_task— pauses the execution until a person completes it via the API (see Executions);assigneeis stored but authorization also accepts anapprover/adminrole.service_task— calls out to an external system through a connector;retry/catchfollow Step Functions’Retry/Catchshape.end— a terminal state; a workflow can have several (here:done,reject).
How state flows
Each decision_task/service_task/human_task result is written under a
per-step namespace instead of replacing the state:
$.decisions.<stepId> # decision_task output
$.tasks.<stepId> # service_task / human_task result
So after score runs, state looks like:
{ "credit_score": 780, "debt_to_income": 0.2,
"decisions": { "score": { "risk_tier": "LOW" } } }
price still receives the entire state, so it can read risk_tier from
$.decisions.score even though its own declared inputs are named
differently at the top level — the compiled ASL passes the whole object
through. Because results accumulate rather than replace, a decision downstream
never clobbers an upstream one, and a gateway can always branch on
$.decisions.<step>.<field>.
decisionsandtasksare reserved top-level keys in workflow/decision state. Don’t name an input schema fielddecisionsortasks— see Workflows § reserved namespaces.
Compile it
Compiling turns the model into an AWS Step Functions definition. This is a read-only, non-persisting operation useful to sanity-check a workflow before saving it:
curl -sS -X POST "$RULEFLOW_API/api/engine/compile-workflow" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d @loan_application.json
The response is Amazon States Language JSON — an internal detail; authors
never hand-write or hand-edit it. It still contains unresolved placeholders
(like ${RuleFlowEngineArn}) that only get filled in at deploy time (see
Release, deploy, execute) — compiling alone does not
produce a runnable machine.
Next
Save the workflow, cut a release, deploy it, and run it: Release, deploy, execute.