Workflows & the DSL
A workflow is a directed acyclic graph (DAG) of steps, each backed by a node type, started by a trigger. Internally every workflow is represented as a JSON document called the DSL (pkg/dsl.WorkflowDSL). The visual canvas reads and writes this document; you can also create or edit it directly via the API, import/export it as a .goagents.json file, or generate it from a natural-language prompt.
Anatomy of a workflow
{
"id": "wf_abc123",
"name": "New Lead Notification",
"description": "Notify sales when a new lead is added to HubSpot",
"version": 1,
"trigger": { "type": "http", "config": {} },
"variables": { "default_channel": "#sales" },
"settings": {
"timeout": "10m",
"max_retries": 2,
"error_workflow_id": ""
},
"input_schema": [
{ "name": "lead_email", "label": "Lead Email", "type": "string", "required": true }
],
"steps": [ /* ... */ ]
}
| Field | Description |
|---|---|
id / name / description | Identity and documentation. |
version | Incremented automatically; previous versions are retained (Version History). |
trigger | How the workflow starts — see Triggers. |
variables | Static key/value pairs available to every step via {{vars.name}}. |
settings.timeout | Overall workflow timeout (Go duration string, e.g. "10m"). |
settings.max_retries | Default retry count applied to steps that don't set their own. |
settings.error_workflow_id | Optional workflow to trigger if this one fails (for alerting/cleanup). |
input_schema | Typed inputs (string, number, boolean, dropdown, text) the caller must/can supply — rendered as a form for manual runs and chat deployments, and validated for API calls. |
steps | The DAG of nodes — see below. |
Steps
Each entry in steps is one node instance:
{
"id": "notify_slack",
"name": "Notify Slack",
"type": "slack-send",
"depends_on": ["fetch_lead"],
"branch_deps": [{ "step_id": "is_high_value", "branch": "true" }],
"config": {
"channel": "{{vars.default_channel}}",
"message": "New lead: {{steps.fetch_lead.output.email}}"
},
"timeout": "30s",
"max_retries": 3,
"retry_delay": "5s",
"continue_on": "error"
}
| Field | Description |
|---|---|
id | Unique within the workflow — referenced by other steps and in expressions. |
type | A registered node type, e.g. http, llm, agent, slack-send, condition, connector. |
depends_on | Step IDs that must complete before this step runs. Steps with no shared dependencies run in parallel automatically. |
branch_deps | Gates execution on a Condition or Switch node taking a specific branch ("true"/"false"). A step with branch_deps only runs if all listed conditions match. |
config | Node-specific configuration. Values support {{...}} expressions (see below). |
timeout / max_retries / retry_delay | Per-step overrides for Temporal activity retry policy. |
condition | A simple expression — if it evaluates falsy, the step is skipped. |
continue_on | Set to "error" to let the workflow continue past this step's failure (its output will contain an error field for downstream steps to check). |
Expressions
Step config values are templated with {{ ... }} and resolved at execution time against:
steps.<step_id>.output.<field>— output of any upstream steptrigger.<field>— data from the trigger (webhook body, poll event, schedule time, etc.)vars.<name>— workflowvariablessecret.<NAME>— a resolved secret value (never logged in plaintext)input.<name>— values supplied viainput_schemafor this run
Expressions can reach into nested JSON ({{steps.fetch.output.body.user.email}}) and are resolved before each step's activity runs.
Execution model
When a workflow runs, the API enqueues a Temporal workflow (internal/execution/workflow.go) with one activity per step (internal/execution/activities.go). Temporal:
- Persists the full event history — every step's input/output is durable and inspectable, even after a worker crash.
- Retries failed activities according to
max_retries/retry_delay. - Supports signals for human-in-the-loop approval steps (see Human-in-the-Loop).
- Lets you resume from the failure point on retry —
PreloadedOutputscarries already-completed step outputs so they aren't re-run.
See Executions & Monitoring and Architecture for the full picture.
Status & lifecycle
A workflow definition has a status:
draft— being edited, not yet enabled.pending_approval— awaiting an admin/owner to approve before it can run (if your tenant requires workflow approval).active— approved and can be enabled.archived— retired.
And an independent enabled flag controlling whether triggers (webhooks, schedules, pollers) are live. PUT /api/v1/workflows/{id}/enable / .../disable toggle this without changing the definition.
Versions & history
Every save creates a new version of the Definition, keyed by a content hash (dsl_hash). The workflow_versions table retains prior versions so you can review history and roll back (Version History UI).
Templates
The workflow_templates table stores reusable starting points (e.g. "Slack notification on new GitHub issue") that can be cloned into a new workflow from the gallery.
Import / export
GET /api/v1/workflows/{id}/exportdownloads the DSL as a.goagents.jsonfile.POST /api/v1/workflows/importcreates a new workflow from an uploaded.goagents.jsonfile — useful for sharing workflows between environments or with other tenants.
Sub-workflows
The Subworkflow node (type: "subworkflow") calls another workflow synchronously and returns its output, letting you compose smaller workflows into larger ones.
Next
- Nodes — every available step type.
- Triggers — how workflows start.
- AI Workflow Generator — generate a DSL from a prompt.
- Promotion Gates — require an evaluation to pass before promoting
envto staging/prod.