This is the repository-level overview of Dagu's workflow YAML schema. It is meant to help a visitor understand how a workflow is shaped, what the current canonical syntax is, and where to look when they need the full field-level reference.
Dagu workflows are DAGs described in YAML. The current schema has a simple execution split:
run: for local shell commands and scriptsaction: for named builtin actions, custom actions, or versioned remote action packagesactions: for reusable custom action definitionsThe older v1 execution fields (command:, script:, step-level type:,
call:, and step_types:) are still loadable for compatibility, but new
workflows should use the syntax below.
name: release-check
type: graph
params:
ENVIRONMENT: staging
steps:
- id: test
run: go test ./...
- id: health
action: http.request
with:
method: GET
url: https://example.com/health
depends: [test]
- id: deploy
run: ./deploy.sh ${ENVIRONMENT}
depends: [health]
retry_policy:
limit: 3
interval_sec: 10
The root type: controls how the workflow executes:
graph runs steps according to depends: and can run independent steps in
parallel.graph is the default when type: is omitted.chain runs steps in order.Do not confuse root type: with legacy step-level type:. Step-level
type: is deprecated; use action: for named executors.
Do not use scalar step shorthand such as - echo hello. It is deprecated;
write explicit step objects with run: instead.
A Dagu file has three layers:
| Layer | Fields | Purpose |
|---|---|---|
| Workflow metadata | name, description, group, labels |
Identify and organize the DAG. |
| Workflow runtime | schedule, params, env, working_dir, shell, shell_args, queue, worker_selector, timeout_sec, max_active_runs, artifacts, log_output |
Configure when and where the DAG runs. |
| Step graph | steps, handler_on, defaults, actions |
Define executable work, shared step defaults, lifecycle handlers, and custom actions. |
Most workflows only need name, type, params, and steps.
Every step has common workflow-control fields plus one execution field.
steps:
- id: step_id
run: echo hello
depends: [previous_step]
env:
- LOG_LEVEL: info
timeout_sec: 300
retry_policy:
limit: 2
interval_sec: 5
output: RESULT
Common step fields:
| Field | Meaning |
|---|---|
id |
Stable identifier for dependencies and output references. Prefer this on every step. |
name |
Optional display name. |
description |
Step description. |
depends |
Step dependency or dependency list. |
env |
Step environment variables. |
working_dir |
Step working directory. |
timeout_sec |
Step timeout in seconds. |
retry_policy |
Retry behavior for this step. |
repeat_policy |
Repeat or polling behavior. |
continue_on |
Continue after selected failure, skip, exit-code, or output conditions. |
preconditions |
Conditions that must pass before the step starts. |
worker_selector |
Required worker labels. |
stdout, stderr, log_output |
Step log output configuration. stdout can also publish DAG/action outputs. |
output |
Captured stdout variable or structured step-scoped output. |
output_schema |
JSON Schema for stdout JSON validation. |
approval |
Human approval gate after step execution. |
container |
Container context for a local run: command. |
run: for Local CommandsUse run: for local shell commands and scripts.
steps:
- id: hello
run: echo hello
Multi-line run: becomes a script:
steps:
- id: build
run: |
set -e
npm ci
npm test
run: can also be an array of commands:
steps:
- id: build_and_test
run:
- go build ./...
- go test ./...
Only shell settings are accepted in with: for a run: step:
steps:
- id: bash_step
run: echo "$SHELL"
with:
shell: bash -e
shell_args: [-c]
shell_packages: [curl]
Important: object-form run: is not part of the current schema. This is
invalid:
steps:
- run:
type: shell
input: echo hello
action: for Named ExecutionUse action: when the step is not just a local shell command.
steps:
- id: create_job
action: http.request
with:
method: POST
url: https://api.example.com/jobs
body: '{"queue":"default"}'
action: names select builtin actions, custom actions, or versioned remote
action packages. Inputs and executor-specific options go under with:.
Current builtin actions:
| Action | Use for | Key inputs |
|---|---|---|
http.request |
HTTP calls | method, url, headers/body config |
ssh.run |
Remote shell over SSH | command, SSH connection config |
exec |
Direct process execution without shell parsing | command, optional args |
docker.run |
Docker executor | optional command, Docker config |
container.run |
Container executor | optional command, container config |
k8s.run, kubernetes.run |
Kubernetes job execution | optional command, Kubernetes config |
postgres.query, sqlite.query |
SQL queries | query, database config |
postgres.import, sqlite.import |
SQL imports | import, database config |
redis.<operation> |
Redis operations | Redis config; operation comes from the action suffix |
jq.filter |
jq transforms | filter, plus data or input |
dag.run |
Child DAG execution | dag, optional params |
dag.enqueue |
Asynchronous child DAG enqueue | dag, optional params, optional queue |
router.route |
Conditional routing | value, routes |
chat.completion |
LLM chat completion | prompt or messages, model config |
harness.run |
CLI coding-agent harnesses | prompt, provider config, optional stdin |
template.render |
Text/template rendering | template, optional data/config |
log.write |
Log messages | message |
mail.send |
Email sending | mail executor config |
archive.create, archive.extract, archive.list |
Archive operations | archive config |
file.stat, file.read, file.write, file.copy, file.move, file.delete, file.mkdir, file.list |
File operations | path/source/destination/content config |
git.checkout |
Git repository checkout | repository, path, optional ref, depth, auth config |
wait.duration, wait.until, wait.file, wait.http |
Wait or poll for time, files, or HTTP readiness | duration, until, path, url, optional polling config |
s3.upload, s3.download, s3.list, s3.delete |
S3 operations | S3 config |
sftp.upload, sftp.download |
SFTP transfers | SFTP config |
noop |
Output-only or approval-only placeholder step | no with, or empty with |
owner/repo@version, name@version, source:target@version |
Remote action package | caller input object under with: |
run: and action: are mutually exclusive on a step. Do not combine either
with legacy execution fields such as command:, script:, step-level type:,
call:, messages:, llm:, value:, or routes:.
Remote action packages contain a dagu-action.yaml manifest and a DAG
entrypoint. GitHub refs such as acme/dagu-action-notify@v1.2.0 and official
refs such as slack@v1.2.0 are portable across worker pools when the process
executing the action can resolve the Git ref. Explicit source: refs support
local paths, file:// paths, and Git URLs; local paths must exist on the worker
executing the action step.
The action manifest inputs schema validates the caller's with: object before
the action DAG starts. The outputs schema validates the final action output
object published by stdout.outputs or action: outputs.write before it is
exposed to the parent step as ${step.outputs.*}.
steps:
- id: active_users
action: postgres.query
with:
dsn: ${DATABASE_URL}
query: SELECT id, email FROM users WHERE active = true
The built-in SQL action family supports PostgreSQL and SQLite. Use the official
duckdb@v1 remote action when a workflow needs DuckDB while keeping the Dagu
core binary cgo-free.
steps:
- id: analyze
action: duckdb@v1
with:
query: SELECT 42 AS answer
Use dag.run when the parent workflow must wait for the child DAG result:
steps:
- id: process_account
action: dag.run
with:
dag: workflows/process-account
params:
ACCOUNT_ID: acct_123
REGION: us-east-1
Use dag.enqueue when the parent only needs to create a queued child DAG run
and continue:
steps:
- id: queue_account_report
action: dag.enqueue
with:
dag: workflows/account-report
params:
ACCOUNT_ID: acct_123
queue: background
dag.enqueue accepts the same with.dag and with.params inputs as
dag.run, plus with.queue to override the queued child run's queue.
parallel: currently requires action: dag.run or action: dag.enqueue:
steps:
- id: fanout
action: dag.run
with:
dag: workflows/process-account
params:
ACCOUNT_ID: ${ITEM.account_id}
REGION: ${ITEM.region}
parallel:
max_concurrent: 3
items:
- account_id: acct_1
region: us-east-1
- account_id: acct_2
region: eu-west-1
steps:
- id: wait_for_api
action: wait.http
with:
url: https://api.example.com/health
status: 200
poll_interval: 5s
request_timeout: 10s
timeout_sec: 300
Use timeout_sec to cap total wait time for polling actions such as
wait.file and wait.http.
harnesses:
codex-cli:
binary: codex
prefix_args: [exec]
prompt_mode: arg
steps:
- id: review
action: harness.run
with:
provider: codex-cli
prompt: Review the current branch and list actionable issues.
Prefer action: harness.run for new workflows. Compatibility note: a
top-level harness: config still causes steps without an explicit executor type
to infer the harness executor. Do not mix top-level harness: with ordinary
shell run: steps unless prompt inference is intended.
Use top-level actions: to define a validated reusable action.
actions:
release.announce:
description: Print a release announcement
input_schema:
type: object
additionalProperties: false
required: [channel, version]
properties:
channel:
type: string
enum: [changelog, slack]
version:
type: string
template:
run: echo {{ json .input.channel }} {{ json .input.version }}
steps:
- id: announce
action: release.announce
with:
channel: changelog
version: v1.2.3
Custom action rules:
^[A-Za-z][A-Za-z0-9_-]*(\.[A-Za-z][A-Za-z0-9_-]*)*$.input_schema is required and must resolve to an object schema.template is required and must contain exactly one of run or action.type: is not supported inside an actions: definition.with: at the call site is validated against input_schema.Legacy step_types: remains loadable, but it is deprecated. Use actions: for
new reusable definitions.
String-form output: captures trimmed stdout into a flat variable:
steps:
- id: version
run: git rev-parse --short HEAD
output: VERSION
- id: publish
run: echo "Publishing ${VERSION}"
depends: [version]
Object-form output: publishes structured step output:
steps:
- id: inspect
run: echo '{"version":"v1.2.3","artifact":{"url":"https://example.test/app.tgz"}}'
output:
version:
from: stdout
decode: json
select: .version
artifact:
from: stdout
decode: json
select: .artifact
- id: publish
depends: [inspect]
run: echo "${inspect.output.version} ${inspect.output.artifact.url}"
Structured output sources are stdout, stderr, and file. Decoders are
text, json, and yaml. select requires json or yaml.
Use stdout.outputs or action: outputs.write when a DAG or remote action
needs to return values to its caller:
steps:
- id: notify
run: ./notify.sh
stdout:
outputs:
fields:
messageId:
decode: json
select: .id
- id: publish
action: outputs.write
with:
values:
status: sent
Caller-visible outputs are available as ${step.outputs} or
${step.outputs.messageId}. This is separate from object-form output:, which
is step-scoped and remains available through ${step.output.*}.
Use handler_on for lifecycle steps:
handler_on:
failure:
action: log.write
with:
message: Workflow failed
exit:
run: ./cleanup.sh
steps:
- id: main
run: ./run.sh
Handlers use the same current step execution syntax as normal steps.
Use these commands when editing workflow YAML:
dagu validate workflow.yaml
dagu schema dag
dagu schema dag steps
Implementation-level references:
internal/cmn/schema/dag.schema.json - generated JSON Schema used by editor
tooling and schema navigationinternal/core/spec/step_v2.go - run: and action: normalization