Workflow Engine
YAML-defined workflow execution with retry, DAG validation, and persistent state.
Source: pkg/workflow/
Overview
Workflow definitions are stored in the database. YAML is an exchange format for
apply / export only — the server does not execute local file paths. The runner
supports capability, Docker, shell, machine, and mapper actions, Go template params,
and DAG validation.
flowbot workflow apply --file wf.yaml
│
▼
ParseYAML → ApplyDefinition (DB) → ReloadTriggers (cron/webhook)
│
POST /service/workflow/run { "name", "input" }
│
▼
StartRunAsync → ValidateInputs → CreateRun → Execute (goroutine)
│
▼
GET /service/workflow/runs/:name (inspect status)
YAML Schema
Workflow files are standalone YAML documents:
name: save_and_track
describe: "Save a URL as a bookmark, archive it, and create a kanban task"
enabled: true # false disables cron/webhook triggers
resumable: true # enable state persistence
inputs: # declared run inputs (validated on run)
- name: url
type: string
required: true
- name: title
type: string
required: true
pipeline: # ordered list of task IDs
- save_bookmark
- archive_url
- create_task
tasks:
- id: save_bookmark
action: capability:karakeep.create # action format: <type>:<details>
describe: "Save the URL as a bookmark"
params: # template-rendered input
url: "{{input.url}}"
title: "{{input.title}}"
vars: # declared variables (reserved)
- url
conn: # DAG dependency edges (for validation)
- archive_url
retry: # task-level retry (optional)
max_attempts: 3
delay: 2s
backoff: exponential
max_delay: 30s
jitter: true
- id: archive_url
action: capability:archive.add
describe: "Archive the URL in ArchiveBox"
params:
url: "{{input.url}}"
retry:
max_attempts: 5
delay: 1s
backoff: linear
max_delay: 60s
- id: create_task
action: capability:kanboard.create_task
describe: "Create a follow-up task"
params:
title: "Read: {{input.title}}"
description: "Bookmark reference: {{step "save_bookmark" "result"}}"
tags:
- reading
- bookmark
Top-Level Fields
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique workflow identifier |
describe |
string | No | Human-readable description |
enabled |
bool | No | Default true; false disables cron/webhook |
resumable |
bool | No | Enable checkpoint persistence (default: false) |
max_concurrency |
int | No | >1 enables parallel DAG execution |
inputs |
[]Input | No | Declared run inputs for validation/forms |
triggers |
[]Trigger | No | Trigger configurations (cron, manual, webhook) |
pipeline |
[]string | Yes | Ordered list of task IDs to execute |
tasks |
[]Task | Yes | Task definitions |
Task Fields
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique task identifier |
action |
string | Yes | capability:<type>.<op>, docker:<image>, shell:<cmd>, machine:<name>, mapper: |
describe |
string | No | Human-readable description |
params |
KV | No | Input parameters (template-rendered) |
vars |
[]string | No | Declared variable names (reserved) |
conn |
[]string | No | DAG dependency edges (validated for cycles; used for parallel scheduling) |
retry |
RetryConfig | No | Retry strategy (see below) |
Action Types
| Prefix | Runtime | Example |
|---|---|---|
capability: |
Capability | capability:karakeep.create |
docker: |
Docker container | docker:nginx:latest |
shell: |
Shell command | shell:echo hello |
machine: |
Remote SSH | machine:vm1 |
mapper: |
Inline data transform | mapper: |
| Free-form | Shell fallback | custom-action |
Mapper Step (mapper:)
The mapper step provides a lightweight data transformation node within the workflow. It takes template-rendered parameters and serializes them to a JSON string, making it suitable for converting output formats between steps. Unlike other action types, mapper is handled inline in the workflow runner -- no external runtime or process is involved.
Mapper steps are resolved before the normal task execution path. When a task's action starts with mapper:, the runner:
- Resolves template expressions in the step's
paramsagainst previous step results. - Marshals the resolved params to a JSON string.
- Stores the JSON as the step result for downstream consumption.
- Skips the engine/runtime dispatch entirely.
In YAML, quote the action because a trailing colon is otherwise invalid: action: "mapper:".
Retry Strategy
See Pipeline Retry for the full retry field schema. The workflow engine uses the same types.RetryConfig converted via ToBackoffConfig() and executed with backoff.Do().
Key difference: the workflow engine retries ALL errors (no retry_on filtering), since workflow tasks don't typically return types.Error.
Parameter Resolution
Task params are rendered through the template engine before execution. The data context for each step includes:
- Results from all previously completed steps (mapped as
{{step "id" "result"}}) - Input variables passed to the workflow entry point (
{{input.*}})
See Pipeline Template Engine for the full template syntax.
DAG Validation
The conn field declares dependency edges between tasks. Before execution, ValidateDAG() performs a DFS cycle check. With max_concurrency > 1, conn also drives parallel scheduling; otherwise execution order follows pipeline.
Invocation
CLI
flowbot workflow apply --file docs/examples/workflows/save_and_track.yaml
flowbot workflow run save_and_track --input '{"url":"https://example.com","title":"Example"}'
flowbot workflow runs save_and_track
Scopes: workflow:read (list/get/export/runs), workflow:run (apply/delete/run).
From HTTP
POST /service/workflow/apply
{"yaml": "..."}
POST /service/workflow/run
{"name": "save_and_track", "input": {"url": "...", "title": "..."}}
→ 202 {"status":"ok","data":{"run_id":123}}
GET /service/workflow/list
GET /service/workflow/get/:name
GET /service/workflow/export/:name
DELETE /service/workflow/delete/:name
GET /service/workflow/runs/:name
Webhook triggers are served at /webhook/workflow/{path} with the same token/HMAC auth fields as pipeline webhooks.
Web UI
Open Automate → Workflows (/service/web/workflows):
| Page | Features |
|---|---|
| List | Name, status, triggers, task count, Last Run; Enable/Disable; open Runs |
| Detail | Overview tab: Inputs, Triggers (Enable/Disable), Execution DAG, Run now, Recent runs; YAML tab: exported definition text |
| Runs | Expandable run rows with step Input/Output/Error; polling pauses while a run is expanded |
DAG badge Parallel DAG appears only when max_concurrency > 1. Conn graphs with max_concurrency ≤ 1 still show topology but are labeled Sequential.
From Code
runID, err := svc.StartRunAsync(ctx, "save_and_track", "manual", types.KV{
"url": "https://example.com",
"title": "Example",
})
Testing
go test ./pkg/workflow/... # Unit tests for parsing, DAG, params, runner, service