Skip to content
Workflow Schema

Workflow Schema

Reference for the JSON format used by dagnats workflow register and dagnats workflow validate.

IDE support: Point your editor at workflow-schema.json for autocomplete and validation. Add "$schema": "./path/to/workflow-schema.json" to your workflow files.

Durations are Go time.Duration strings: "30s", "5m", "1h30m".


WorkflowDef

Top-level workflow definition object.

FieldJSON KeyTypeRequiredDefaultDescription
NamenamestringYesUnique workflow identifier
VersionversionstringYesSchema version for evolution
Stepssteps[]StepDefYesAt least one step required
DefaultRetrydefault_retryRetryPolicyNonilFallback retry for all steps
ConcurrencyconcurrencyConcurrencyLimitNonilWorkflow-level parallelism limits
TimeouttimeoutdurationNo0Overall workflow deadline
InputSchemainput_schemaJSONNonilJSON Schema for workflow input validation
OutputSchemaoutput_schemaJSONNonilJSON Schema for workflow output validation

Minimal example:

{
  "name": "hello",
  "version": "1.0",
  "steps": [
    {"id": "greet", "task": "greet", "timeout": "30s", "type": "normal"}
  ]
}

StepDef

Individual step within a workflow.

FieldJSON KeyTypeRequiredDefaultDescription
IDidstringYesUnique within the workflow
TasktaskstringYesTask name workers subscribe to
DependsOndepends_on[]stringNo[]Step IDs that must complete first
RetriesretriesintNo0Legacy retry count (prefer retry)
TimeouttimeoutdurationYesPer-step execution deadline
TypetypeStepTypeYesExecution semantics (see below)
LooploopAgentLoopConfigNonilRequired when type is agent_loop
SkipIfskip_ifParentCondNonilConditional skip based on parent output
MetadatametadatamapNonilArbitrary key-value pairs
RetryretryRetryPolicyNonilStructured retry policy (overrides retries)
WorkerGroupworker_groupstringNo""Routes to a specific worker pool
OnFailureon_failurestringNo""Step ID to run on failure
CompensatecompensatestringNo""Step ID for saga compensation

Example with dependencies and retry:

{
  "id": "lint",
  "task": "lint.run",
  "timeout": "5m",
  "type": "normal",
  "depends_on": ["fetch-diff"],
  "worker_group": "lint-workers",
  "retry": {
    "max_attempts": 3,
    "strategy": "exponential",
    "initial_delay": "2s",
    "max_delay": "30s",
    "multiplier": 2.0
  }
}

StepType Values

ValueDescription
normalRuns once, completes or fails
agent_loopIterates until termination signal; requires loop config
sub_workflowDelegates to a nested workflow DAG
agentSingle autonomous agent execution

RetryPolicy

Resolution order: step retry > workflow default_retry > legacy retries > none.

FieldJSON KeyTypeRequiredDefaultDescription
MaxAttemptsmax_attemptsintYesTotal retry attempts; 0 means no retries
StrategystrategyRetryStrategyYesBackoff algorithm
InitialDelayinitial_delaydurationYesBase delay between attempts
MaxDelaymax_delaydurationYesDelay cap (0 = uncapped)
Multipliermultiplierfloat64No0Used by exponential strategy only

RetryStrategy Values

ValueDelay FormulaExample (initial=2s)
fixedinitial_delay every attempt2s, 2s, 2s
linearinitial_delay * attempt2s, 4s, 6s
exponentialinitial_delay * multiplier^(attempt-1)2s, 4s, 8s (multiplier=2)

Example:

{
  "max_attempts": 5,
  "strategy": "exponential",
  "initial_delay": "1s",
  "max_delay": "30s",
  "multiplier": 2.0
}

AgentLoopConfig

Required when step type is agent_loop.

FieldJSON KeyTypeRequiredDefaultDescription
MaxIterationsmax_iterationsintYesUpper bound on loop cycles; must be > 0
MaxDurationmax_durationdurationNo0Wall-clock limit; whichever fires first wins
LoopDelayloop_delaydurationNo0Pause between iterations

Example:

{
  "id": "review-loop",
  "task": "agent.code-review",
  "timeout": "15m",
  "type": "agent_loop",
  "loop": {
    "max_iterations": 10,
    "max_duration": "10m",
    "loop_delay": "2s"
  }
}

ConcurrencyLimit

Controls parallelism at the workflow level.

FieldJSON KeyTypeRequiredDefaultDescription
MaxRunsmax_runsintNo0Max parallel runs of this workflow
MaxStepsmax_stepsintNo0Max parallel steps within a run

Example:

{
  "concurrency": {
    "max_runs": 3,
    "max_steps": 2
  }
}

ParentCond (skip_if)

Evaluates a comparison against a parent step’s JSON output. The parent step must be listed in the step’s depends_on.

FieldJSON KeyTypeRequiredDefaultDescription
StepIDstep_idstringYesParent step to check (must be in depends_on)
FieldfieldstringYesTop-level key in parent’s output JSON
OpopstringYesComparison operator
ValuevalueanyYesValue to compare against

Valid operators: ==, !=, <, >, <=, >=

Types are compared as float64 (numbers), string, or bool (equality only).

Example:

{
  "id": "post-results",
  "task": "github.post-comment",
  "timeout": "1m",
  "type": "normal",
  "depends_on": ["lint"],
  "skip_if": {
    "step_id": "lint",
    "field": "issues_found",
    "op": "==",
    "value": 0
  }
}

This step is skipped when the lint step’s output has issues_found equal to 0.


Validation Rules

The following rules are enforced by dag.Validate():

#Rule
1Workflow must have a non-empty name
2Workflow must contain at least one step
3Every step must have a unique id
4Every step must have a non-empty task
5depends_on entries must reference existing step IDs
6on_failure must reference an existing step ID (if set)
7compensate must reference an existing step ID (if set)
8retries must not be negative
9agent_loop steps must have a loop config with max_iterations > 0
10Non-agent_loop steps must not have a loop config
11skip_if.step_id must be in the step’s depends_on list
12skip_if.op must be a valid operator
13The step dependency graph must be acyclic (Kahn’s algorithm)

Complete Example

{
  "name": "code-review-pipeline",
  "version": "1.0.0",
  "timeout": "30m",
  "default_retry": {
    "max_attempts": 2,
    "strategy": "fixed",
    "initial_delay": "5s",
    "max_delay": "5s"
  },
  "concurrency": {
    "max_runs": 3,
    "max_steps": 2
  },
  "steps": [
    {
      "id": "fetch-diff",
      "task": "git.fetch-diff",
      "timeout": "2m",
      "type": "normal"
    },
    {
      "id": "lint",
      "task": "lint.run",
      "timeout": "5m",
      "type": "normal",
      "depends_on": ["fetch-diff"],
      "worker_group": "lint-workers"
    },
    {
      "id": "review-loop",
      "task": "agent.code-review",
      "timeout": "15m",
      "type": "agent_loop",
      "depends_on": ["fetch-diff"],
      "loop": {
        "max_iterations": 10,
        "max_duration": "10m",
        "loop_delay": "2s"
      },
      "retry": {
        "max_attempts": 3,
        "strategy": "exponential",
        "initial_delay": "2s",
        "max_delay": "30s",
        "multiplier": 2.0
      }
    },
    {
      "id": "post-results",
      "task": "github.post-comment",
      "timeout": "1m",
      "type": "normal",
      "depends_on": ["lint", "review-loop"],
      "skip_if": {
        "step_id": "lint",
        "field": "issues_found",
        "op": "==",
        "value": 0
      },
      "on_failure": "notify-failure",
      "metadata": {
        "channel": "pull-requests"
      }
    },
    {
      "id": "notify-failure",
      "task": "notify.slack",
      "timeout": "30s",
      "type": "normal",
      "depends_on": ["fetch-diff"]
    }
  ]
}