Skip to main content

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": [ /* ... */ ]
}
FieldDescription
id / name / descriptionIdentity and documentation.
versionIncremented automatically; previous versions are retained (Version History).
triggerHow the workflow starts — see Triggers.
variablesStatic key/value pairs available to every step via {{vars.name}}.
settings.timeoutOverall workflow timeout (Go duration string, e.g. "10m").
settings.max_retriesDefault retry count applied to steps that don't set their own.
settings.error_workflow_idOptional workflow to trigger if this one fails (for alerting/cleanup).
input_schemaTyped 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.
stepsThe 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"
}
FieldDescription
idUnique within the workflow — referenced by other steps and in expressions.
typeA registered node type, e.g. http, llm, agent, slack-send, condition, connector.
depends_onStep IDs that must complete before this step runs. Steps with no shared dependencies run in parallel automatically.
branch_depsGates 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.
configNode-specific configuration. Values support {{...}} expressions (see below).
timeout / max_retries / retry_delayPer-step overrides for Temporal activity retry policy.
conditionA simple expression — if it evaluates falsy, the step is skipped.
continue_onSet 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 step
  • trigger.<field> — data from the trigger (webhook body, poll event, schedule time, etc.)
  • vars.<name> — workflow variables
  • secret.<NAME> — a resolved secret value (never logged in plaintext)
  • input.<name> — values supplied via input_schema for 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 — PreloadedOutputs carries 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}/export downloads the DSL as a .goagents.json file.
  • POST /api/v1/workflows/import creates a new workflow from an uploaded .goagents.json file — 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