Your first process

Start a run, fill a step, complete it, and see where it stops.

Assumes installation is done and npm run dev is running. Output blocks show what a local file-storage install returns.


The template

integrator-onboarding ships with the seed data. Six steps, starting with a form.

curl -s localhost:3000/api/process-templates | jq '.[].key'

1. Start a run

curl -s -X POST localhost:3000/api/process \
  -H "Content-Type: application/json" \
  -d '{"templateKey":"integrator-onboarding"}'
processId: ed9a17c8-0fab-4272-bd51-f9a58849e097
status:    running
steps:     ["input"]

The process is created, the template is snapshotted onto it, and the first step is pushed. Note steps — that is the list of step instances reached so far, not the template's steps.

2. Find the step instance id

Writes address the instance, not the template step key. They are different ids.

curl -s localhost:3000/api/process/$PID | jq '.steps[-1]'
id:      c8f0caa3-b54c-4fe0-ada8-f840a401a462   ← use this
stepKey: input                                   ← not this

This matters because a loop visits the same stepKey more than once, each visit getting its own id.

3. Write some fields

curl -s -X PUT localhost:3000/api/process/$PID/steps/$SID \
  -H "Content-Type: application/json" \
  -d '{"name":"Test Partner","contractAddress":"0x1234","contact":"@test","agent":"Spark"}'

200. Now look at what happened:

context.input:  {"name":"Test Partner","contractAddress":"0x1234","contact":"@test","agent":"Spark"}
audit entries:  1
  2026-07-27T00:53:09  user=dev-user  step=input  keys=['name','contractAddress','contact','agent']

Two things to notice:

  • Values landed in context.input — keyed by step key, which is how later steps reference them as ${input.name}.
  • One stepContextAudit entry recorded who wrote what, when. Every write appends one. Nothing is overwritten in the audit trail.

4. Try to complete it — and get rejected

curl -s -X POST localhost:3000/api/process/$PID/steps/$SID/complete \
  -H "Content-Type: application/json" -d '{}'
HTTP 403
{"error":"Step completion is not allowed for your account, or the submitted data
does not satisfy the completion rule for this step."}

Rejected even as a dev user with all 8 permissions. The step has:

permissions:        []          ← anyone may act
mandatory fields:   []          ← nothing marked mandatory
completeExpression: trim(input.name).length > 0 && trim(input.contractAddress).length > 0
                    && trim(input.contactPlatform).length > 0 && trim(input.contact).length > 0
                    && trim(input.agent).length > 0 && trim(input.wallet).length > 0

completeExpression is the gate here, not mandatory. The request above sent four of the six fields it requires.

mandatory is a per-field UI affordance. completeExpression is a whole-step rule evaluated server-side, so it can express conditions a per-field flag cannot. This template marks nothing mandatory and enforces everything through the expression.

The error message does not distinguish "you lack permission" from "your data is incomplete" — both return the same 403.

5. Fill the rest and complete

curl -s -X PUT localhost:3000/api/process/$PID/steps/$SID \
  -d '{"contactPlatform":"Telegram","wallet":"0xabcd"}'      # 200

curl -s -X POST localhost:3000/api/process/$PID/steps/$SID/complete -d '{}'   # 200
status:        running
steps reached: ["input", "notify"]
parked on:     notify   type=slack_notify
audit entries: 2

The run advanced. steps now has two instances.

6. Watch it stop

It is sitting on notify, a slack_notify step — and it will sit there forever.

npm run dev does not start the worker. Only input steps advance from an API call; every other type needs the worker to come along and execute it.

Restart with:

npm run dev:all

Now the worker picks it up on the next tick. Without SLACK_BOT_TOKEN configured the step will fail — but note how it fails: slack_notify records the failure into context and advances anyway. The run continues — but the step is marked as not sent, and the completion screen says so.

That is the soft-failure contract. See step types.


What you just saw

ConceptWhere it showed up
Template snapshotprocess.template copied at start
Step instances vs step keyssteps[-1].id ≠ stepKey
Context keyed by stepcontext.input.name
Append-only auditone entry per write, with actor and timestamp
Completion gatingcompleteExpression, not mandatory
Human vs automated stepsparked on notify until the worker ran

Where the data lives

With file storage, all of it is on disk in readable JSON:

cat .process-platform/process-states.json | jq     # runs
cat .process-platform/templates.json | jq          # templates

Delete process-states.json to throw away every run and keep the templates. Delete the whole .process-platform/ directory to reset both — the next template list/get gap-fills shipped templates that are missing from the store. See manage templates.


Next