Writing your first workflow.
A workflow turns "the assistant could do this" into "the assistant does this, repeatably, under scope." This guide walks you from blank file to shipped workflow for the most common starting point: triage of inbound support tickets.
- How to define a workflow in TOML
- How to attach an existing assistant or write your own
- How to test a workflow safely with dry-run and replay
- How to ship it to production with version control
Anatomy of a workflow
A workflow is a TOML file with four sections: [workflow], [trigger], [scope], [steps]. Optional sections add approvals, retries, and SLAs. The full schema is in configuration reference.
[workflow] name = "support-triage" description = "Triage new support tickets within 5 minutes." owner = "team:support" [trigger] event = "task.created" filter = "task.labels.includes('support')" [scope] read = ["crm:accounts", "tasks", "documents"] write = ["tasks.update", "tasks.comment"] [[steps]] assistant = "triage" input = { task_id = "$trigger.task_id" }
Pick a trigger
Triggers are events on the work graph. The most common starters:
task.createdwith a label filterforms.submittedwith a form-id filteremail.receivedwith a sender domain filterschedule.dailywith a cron-style spec for recurring runs
Triggers fire under the identity of the user who caused the event. If a customer fills out your form, the workflow runs under the customer's permissions, which usually means "nothing" until you grant the workflow elevated scope.
Define scope before logic
Counterintuitive, but: write the scope first. Start narrow. Add scopes only when a step refuses to run because of missing permission.
Why? Once a workflow is approved, your team will copy it. A workflow with too-wide scope is the most common audit finding in the wrxstack support data.
Write the steps
Steps run in order. Each step calls an assistant, a tool, or a sub-workflow. The output of one step is available as $steps.<name> in the next.
[[steps]] name = "classify" assistant = "triage" input = { task_id = "$trigger.task_id" } [[steps]] name = "notify" tool = "slack.post_message" when = "$steps.classify.severity == 'high'" input = { channel = "#sales-urgent", text = "⚠ High-severity ticket: $steps.classify.summary", }
Test with dry-run
The CLI runs your workflow against a real trigger but writes nothing back to the graph or external services.
wrxstack workflow run ./support-triage.toml --trigger task_id=tsk_01HQ3K... --dry-run # Step 1: classify → assistant.triage # would read: tasks/tsk_01HQ3K, accounts/acc_01HQ3K # would write: tasks/tsk_01HQ3K.labels=['high'], tasks/tsk_01HQ3K.assignee='priya@' # Step 2: notify → slack.post_message # would call: slack.post(channel='#sales-urgent', text='⚠ High-severity ticket: …')
Replay a real run
Once shipped, every workflow run is replayable. Replay re-executes the same steps against the same inputs but writes to a sandbox graph. Use replay to diff behavior across workflow versions before bumping production.
wrxstack workflow replay run_01HQ3K... --target=./support-triage.toml@v4
Ship to production
Commit the TOML to your repo. Apply via CI:
wrxstack workflow apply ./support-triage.toml --workspace acme --env prod
Applies are versioned. Each apply gets a numeric version; rollback is one command.
Next steps
- Tool calling for adding new tools.
- Safety & approvals for human-in-the-loop steps.