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 duration strings ("30s", "5m", "1h30m") or integer nanoseconds (30000000000). Strings work in every duration field, including those inside a step’s config. Definitions dagnats writes back, such as dagnats workflow show --json, use nanoseconds. Go duration strings have no day unit; write "24h", not "1d".

A workflow file may also carry a top-level $schema key (ignored) and, for dagnats workflow register, a triggers array that registers triggers with the workflow. Other unknown top-level keys are rejected by both register and validate.


WorkflowDef

Top-level workflow definition object.

FieldJSON KeyTypeRequiredDefaultDescription
NamenamestringYes–Unique workflow identifier
VersionversionstringNo""Free-form version label for your own bookkeeping; not validated
Stepssteps[]StepDefYes–At least one step required
DefaultRetrydefault_retryRetryPolicyNonilFallback retry for all steps
ConcurrencyconcurrencyConcurrencyLimitNonilWorkflow-level parallelism limits (max_steps 0 to 1000)
TimeouttimeoutdurationNo0Overall workflow deadline
InputSchemainput_schemaJSONNonilJSON Schema for workflow input validation
OutputSchemaoutput_schemaJSONNonilJSON Schema the final output (terminal step outputs) must match; a mismatch fails the run
AuxStepsaux_stepsmap[string]boolNonilSteps whose outcome does not decide the run’s status (e.g. on_failure handlers)
IdempotencyKeyidempotency_keystringNo""Input path whose value dedups run starts
StickystickystringNo""Worker affinity: soft or hard (hard requires a workflow timeout)
PrioritypriorityPriorityConfigNonilInput-driven dispatch priority (key, rules, default_offset)
CancelOncancel_on[]CancelOnNonilEvents that cancel the run (event, match, timeout); max 5
SingletonsingletonSingletonConfigNonilOne run at a time: mode (skip or cancel), optional key

Minimal example:

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

StepDef

Individual step within a workflow.

FieldJSON KeyTypeRequiredDefaultDescription
IDidstringYes–Unique within the workflow
TasktaskstringYes–Task name workers subscribe to: 1 to 128 bytes of A-Za-z0-9_.-, no leading or trailing ., no empty token
DependsOndepends_on[]stringNo[]Step IDs that must complete first
RetriesretriesintNo0Legacy retry count (prefer retry): a fixed 5s delay between attempts. 0 to 100000
TimeouttimeoutdurationNo0Per-attempt deadline, enforced by the engine. 0 arms no timeout. It runs from when the current attempt first started: a redelivered or resumed task re-arms it only for the time left, so time spent paused counts. A retry gets the full timeout
TypetypeStepTypeNonormalExecution semantics (see below). An unknown value is an error
ConfigconfigobjectDepends on typenilType-specific settings, e.g. AgentLoopConfig for agent_loop, sleep settings for sleep
SkipIfskip_ifParentCondNonilConditional skip based on parent output
Metadatametadatamap[string]stringNonilStatic key-value pairs, delivered to the worker in the task payload
RetryretryRetryPolicyNonilStructured retry policy (overrides retries)
WorkerGroupworker_groupstringNo""Routes to a specific worker pool. Same character rule as task, but no dots
OnFailureon_failurestringNo""Step ID to run on failure
CompensatecompensatestringNo""Step ID for saga compensation
RateLimitrate_limitRateLimitNonillimit dispatches per period for this step’s task
KeyedRateLimitkeyed_rate_limitKeyedRateLimitNonilRate limit per input key (key, limit, period, units)
MaxTaskConcurrencymax_task_concurrencyintNo0Max in-flight tasks of this step’s task type; 0 = unlimited, max 1000
SingletonsingletonboolNofalseOnly one instance of this step runs at a time
RequiredCapabilitiesrequired_capabilities[]stringNonilCapabilities the workflow must be granted, e.g. control-plane

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 an AgentLoopConfig in config
sub_workflowDelegates to a nested workflow DAG
agentSingle autonomous agent execution
mapFans out one task per item of an input array
sleepDurable delay: duration, until_input_path, or cron (with optional cron_timezone / cron_timezone_input_path)
wait_for_eventWaits for a matching external event
approvalWaits for a human approval (timeout, subject, optional description)
plannerGenerates and runs steps at runtime
respondSends the HTTP response for an HTTP trigger (status, headers, body_from, content_type)

Step config by type

A step’s config holds the settings for its type. A typed step whose config does not parse is rejected at registration.

sleep

Exactly one of duration, cron or until_input_path.

FieldTypeDescription
durationdurationFixed delay. Must be positive
cronstringWake at the next occurrence of a cron expression, strictly after dispatch
until_input_pathstringInput path holding a deadline: an RFC3339 instant or integer milliseconds
cron_timezonestringIANA zone the cron expression is evaluated in, e.g. America/Denver
cron_timezone_input_pathstringInput path that supplies the zone at run time

cron_timezone is validated when the workflow is registered. cron_timezone_input_path is read at dispatch; a missing or unknown zone there falls back to UTC with a logged warning. Both apply only with cron, and at most one may be set. With neither, the cron expression is evaluated in UTC, not the host’s local zone.

{"id": "morning", "task": "sleep", "type": "sleep", "depends_on": ["prep"],
 "config": {"cron": "0 9 * * 1-5", "cron_timezone": "America/Denver"}}

map

FieldTypeDescription
max_itemsintCap on the input array, 1 to 10000

A map step must have exactly one dependency, and that dependency may not itself be a map step.

approval

FieldTypeDescription
timeoutdurationHow long to wait. Positive, at most 168h
subjectstringRequired. Names the approval request
descriptionstringOptional text shown to the approver
metadatamap[string]stringOptional extra context

wait_for_event

FieldTypeDescription
eventstringEvent name to wait for
matchobjectleft (a path into the event data), op (eq), right (input.<path> or step.<id>.<path>, resolved when the wait starts)
timeoutdurationRequired, positive

sub_workflow

FieldTypeDescription
workflowstringName of the child workflow
detachboolWhen true, the parent step completes as soon as the child is spawned

planner

FieldTypeDescription
max_stepsintCap on generated steps, 1 to 100
max_depthintNesting cap, 0 to 10
allowed_tasks[]stringTask types the planner may use

respond

status (default 200), headers, body_from, content_type (default application/json). See the HTTP trigger guide.

agent_loop

See AgentLoopConfig below.


RetryPolicy

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

FieldJSON KeyTypeRequiredDefaultDescription
MaxAttemptsmax_attemptsintYes–Total retry attempts; 0 means no retries
StrategystrategyRetryStrategyYes–Backoff algorithm
InitialDelayinitial_delaydurationYes–Base delay between attempts
MaxDelaymax_delaydurationYes–Delay cap (0 = uncapped)
Multipliermultiplierfloat64No0Used by exponential strategy only. 0 or omitted means 2

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

Set in the step’s config when its type is agent_loop.

FieldJSON KeyTypeRequiredDefaultDescription
MaxIterationsmax_iterationsintYes–Upper bound on loop cycles; must be > 0
MaxDurationmax_durationdurationNo0Wall-clock limit; whichever fires first wins
LoopDelayloop_delaydurationNo0Pause between iterations
BudgetWarnAtbudget_warn_atintNo0Advisory: iteration at which workers should warn the model
BudgetForceAtbudget_force_atintNo0Advisory: iteration at which workers should force a wrap-up

Example:

{
  "id": "review-loop",
  "task": "agent.code-review",
  "timeout": "15m",
  "type": "agent_loop",
  "config": {
    "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_idstringYes–Parent step to check (must be in depends_on)
FieldfieldstringYes–Top-level key in parent’s output JSON
OpopstringYes–Comparison operator
ValuevalueanyYes–Value 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 be 0 to 100000, and retry / default_retry max_attempts at most 100000; an exponential multiplier must be between 1 and 100
9agent_loop steps must have a config with max_iterations > 0
10Each typed step’s config must parse for its type (sleep, map, approval, respond, …)
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)
14task and worker_group follow the character rules above
15on_failure and compensate targets must not depend on other steps or reference themselves
16idempotency_key must not start or end with . or contain an empty segment
17sticky must be soft or hard; hard requires a workflow timeout, and sticky cannot be combined with a per-step worker_group
18priority needs a key and 1 to 20 rules, with offsets in [-600, 600]; cancel_on has at most 5 entries, each with an event; singleton.mode must be skip or cancel

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"],
      "config": {
        "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"
    }
  ]
}