-
-
Notifications
You must be signed in to change notification settings - Fork 294
Expand file tree
/
Copy pathllms.txt
More file actions
1820 lines (1409 loc) · 66.2 KB
/
Copy pathllms.txt
File metadata and controls
1820 lines (1409 loc) · 66.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Dagu
Dagu is a self-contained workflow orchestration engine for running DAGs defined in YAML. It runs as a single binary without requiring an external database or message broker. It stores state locally by default and supports local, queued, and distributed execution modes.
Use this compact reference when authoring, validating, or troubleshooting Dagu workflows with an AI agent. It summarizes repository-local workflow references.
---
# DAG Authoring
Load only the reference file that matches the task.
## Default Approach
- Prefer `type: graph` for new DAGs. It supports both sequential flow via `depends:` and parallel flow.
- Prefer `id` on every step. Omit `name` unless the display label must differ from the step ID.
- Prefer `dagu schema ...` and `dagu validate ...` over guessing field names or shapes.
- Prefer `action: template.render` when generating text files, prompts, or artifacts instead of assembling them with shell `echo` or heredocs.
- Prefer `file.*` actions for local file operations such as stat, read, write, copy, move, delete, mkdir, and list instead of shelling out to `cp`, `mv`, `rm`, or `mkdir`.
- Prefer `git.worktree.add` and `git.worktree.remove` when steps need isolated branches inside an existing local Git repository. Add an explicit remove step when the workflow should delete the worktree.
- Prefer `stdout.artifact` / `stderr.artifact` when a command stream should become a DAG-run artifact, especially for large reports, JSON, Markdown, logs, or generated files.
- Prefer `artifact.*` actions for explicit artifact reads/writes/lists. Use `DAG_RUN_ARTIFACTS_DIR` only when a tool truly needs a filesystem path inside the step.
- Prefer string-form `output: VAR_NAME` for capturing small stdout values into flat variables.
- Prefer object-form `output:` when downstream steps need structured values via `${step_id.output.*}`.
- Prefer declared step `outputs:` with `$DAGU_OUTPUT_FILE` when a step must publish explicit values for `${steps.<step_id>.outputs.<name>}`.
- Use `action: human.task` when an operator must provide typed input before downstream steps continue. Human task form outputs use `${steps.<step_id>.outputs.<name>}` without an authored `outputs:` field.
- Prefer `stdout.outputs` or `action: outputs.write` when a DAG or remote action needs to return caller-visible values via `${step_id.outputs.*}`.
- Prefer `state.*` actions for small persistent JSON state across DAG runs, such as cursors, checkpoints, and previous-value comparisons.
- Prefer temporary files in the artifacts dir only when downstream steps need file paths; otherwise let commands write large artifact content to stdout and attach it with `stdout.artifact`.
- Prefer scoped Dagu references for named values: `${consts.NAME}`, `${params.NAME}`, and `${env.NAME}`. Avoid unscoped braced names in examples unless the example is intentionally showing shell syntax.
- Declare portable external CLI dependencies in top-level `tools` using aqua shorthand when the binary version affects reproducibility, for example `tools: ["jqlang/jq@jq-1.7.1"]`.
- For remote actions, put `tools` in the referenced action DAG file, not in `dagu-action.yaml`; caller DAG tools are not inherited across the action boundary.
- Use remote action packages (`dagu-action.yaml`) when reusable logic needs helper files, its own DAG, versioning, or an input/output schema contract.
## High-Signal Rules
- `output:` has two modes:
- string form captures trimmed stdout into an env-scope variable such as `${env.VERSION}`
- object form publishes structured step-scoped output for `${step_id.output.*}` access
- Declared step `outputs:` publish explicit values through `${steps.<step_id>.outputs.<name>}`. Write values inside the running step to `$DAGU_OUTPUT_FILE`; Dagu captures them only after the command succeeds.
- `human.task` is a processless root-DAG step with an explicit `id`, a required `with.prompt`, and an optional flat scalar form. A root DAG containing one can run locally or on a distributed worker. Every declared form property is a step output, published when submitted or defaulted, and available as `${steps.<step_id>.outputs.<name>}`.
- `stdout.artifact` / `stderr.artifact` store command stdout/stderr directly as relative artifact paths, for example `stdout: {artifact: reports/report.md}`. Artifact outputs auto-enable artifacts unless `artifacts.enabled: false` is explicitly set, which is invalid.
- `${step_id.stdout}` is a log file path, not stdout content.
- Use `${context.*}` for run metadata in DAG YAML, for example `${context.dag.name}`, `${context.run.id}`, or `${context.paths.artifacts_dir}`. Unavailable context values remain unresolved text instead of becoming empty strings.
- Use `${consts.NAME}`, `${params.NAME}`, and `${env.NAME}` for Dagu-side named values. Use shell `$NAME` or `printenv NAME` only when the target shell or process should read the variable at execution time.
- `consts:` must use list form with one key per item, for example `consts: [{service: api}]`. Const values are resolved while loading the DAG and can reference inherited or earlier consts.
- `env:` should use list-of-maps when values depend on earlier env vars.
- `params:` values arrive as strings. The `params:` field supports JSON schema-like types and validation, check for schema to see how to specify types and validation rules.
- Single-line `run:` values are command-form entries. Array-form `run:` entries run one by one. Multi-line `run:` values are scripts. Dagu does not split pipes, redirects, `&&`, or `;` into separate commands; those stay with the selected shell.
- Do not assume `bash` for `run:` steps. If a script depends on a specific interpreter, add a shebang such as `#!/bin/sh` or `#!/usr/bin/env bash` only after checking that shell exists on the target host or container. Otherwise keep the script portable or set `with.shell:` explicitly.
- `parallel:` currently requires `action: dag.run` to a child DAG.
- Sub-DAGs do not inherit parent env vars; pass what you need via `params:`.
- For arbitrary text inside shell steps, prefer `printenv VAR_NAME` or `action: template.render` over Dagu interpolation such as `${env.VAR_NAME}`.
- `harness.run` supports built-in CLI provider adapters (`claude`, `codex`, `copilot`, `opencode`, `pi`) and custom top-level `harnesses:` entries. It can use top-level `container:` or step-level `container:`.
- Container runtime selection is service-level, not a DAG YAML field. Set `DAGU_CONTAINER_RUNTIME=podman` to use Podman, and set `DAGU_PODMAN_HOST` only when the Podman Docker-compatible socket is not the default.
- DAG/action outputs are collected from string-form `output: VAR_NAME`, `stdout.outputs`, and `action: outputs.write`. Object-form `output:` stays step-scoped for `${step_id.output.*}` unless the workflow explicitly republishes values through `stdout.outputs` or `outputs.write`.
- `state.get`, `state.set`, `state.delete`, `state.list`, and `state.diff` persist small JSON values across DAG runs. State scopes are `dag`, `root_dag`, `global`, and `custom`; use artifacts or external storage for large payloads.
- Git worktree actions discover the repository from the step `working_dir`. Relative worktree paths resolve from the repository root, and the actions never fetch or push.
- Remote action packages define `dagu-action.yaml` with `apiVersion: v1alpha1`, `name`, `dag`, and optional `inputs`/`outputs` JSON Schemas. `inputs` validates caller `with:` before the action DAG starts; `outputs` validates the final action output object after the action DAG returns.
- Remote action manifests do not support `tools`. Declare external CLI tools in the action DAG itself so local and distributed workers prepare the right binaries for that action run.
- In remote action examples, prefer `dag: workflow.yaml` for the action DAG filename. The `dag` field accepts any safe relative file path, but `workflow.yaml` avoids confusing the executable DAG with the `dagu-action.yaml` manifest.
- Object-form `output:` with `decode: json` or `decode: yaml` can act as lightweight runtime validation. Malformed data or an unresolved `select:` path fails the step, so normal `retry_policy` applies.
- Use DAG-level `shell` and `shell_args` only when every inherited `run:` step should use the same shell invocation. Use step-level `with.shell` and `with.shell_args` for a single step.
- Use `dagu schema dag` to check the full list of available fields and their shapes.
- Use `dagu example` to see different DAG patterns and how to express them in YAML.
## Example of Params, template step, and artifacts
```yaml
params:
type: object
properties:
name:
type: string
maxLength: 50
age:
type: integer
minimum: 0
maximum: 120
favorite_color:
type: string
required: [name, age]
steps:
- id: render
action: template.render
with:
data:
name: ${params.name}
age: ${params.age}
favorite_color: ${params.favorite_color}
template: |
Hello, {{ .name }}!
You are {{ .age }} years old.
{{- if .favorite_color }}
Your favorite color is {{ .favorite_color }}.
{{- end }}
stdout:
artifact: greeting.txt
```
## Example of Large Command Output as Artifact
```yaml
steps:
- id: report
run: ./generate-report --format markdown
stdout:
artifact: reports/report.md
```
## Example of Reproducible External CLI
```yaml
tools:
- jqlang/jq@jq-1.7.1
steps:
- id: inspect
run: jq --version
```
## Example of Object-Form Output
```yaml
steps:
- id: inspect_build
run: echo '{"version":"v1.2.3","artifact":{"url":"https://example.test/app.tgz"}}'
output:
# decode + select act as a lightweight contract check:
# malformed JSON or a missing selected field fails the step.
version:
from: stdout
decode: json
select: .version
artifact:
from: stdout
decode: json
select: .artifact
- id: publish
depends: [inspect_build]
output:
versionLabel: "ver - ${inspect_build.output.version}"
artifactUrl: "${inspect_build.output.artifact.url}"
```
## Example of Action Outputs
```yaml
steps:
- id: classify
run: ./classify.sh "${params.INPUT}"
stdout:
outputs:
fields:
label:
decode: json
select: .label
confidence:
decode: json
select: .confidence
- id: publish
depends: [classify]
action: outputs.write
with:
values:
label: ${classify.outputs.label}
reviewed: false
```
## Example of Human Input
```yaml
steps:
- id: review
action: human.task
with:
prompt: Choose the deployment target
form:
type: object
properties:
environment:
type: string
enum: [staging, production]
note:
type: string
default: ""
required: [environment]
- id: deploy
depends: [review]
run: ./deploy --environment '${steps.review.outputs.environment}' --note '${steps.review.outputs.note}'
```
Complete the task from a local CLI context with `dagu human-task complete --run-id=<run-id> --step=review --input environment=production <dag-name>`. A form is optional for acknowledgement-only tasks. Human tasks cannot be used in sub-DAGs; a distributed root run is re-queued through the scheduler after completion.
## Reference Guide
Load only the file you need:
- `references/steptypes.md` when choosing an action or checking action-specific behavior such as `human.task`, `dag.run`, `parallel`, `git.worktree.*`, `jq.filter`, `file.*`, `state.*`, or `template.render`
- `references/dagu-action.md` when creating a reusable `dagu-action.yaml` package or checking action input/output schema behavior
- `references/cli.md` when you need command flags or lookup commands such as `dagu schema`, `dagu config`, or `dagu history`
- `references/context.md` when using `${context.*}` metadata references or declared step `outputs:`
- `references/harnesses.md` only when the DAG invokes external CLI harnesses through `harness.run`
---
# Actions
## run: Shell Commands And Scripts
Use top-level `run:` for local shell commands and scripts.
```yaml
steps:
- id: hello
run: echo "hello"
- id: multi_line
run: |
echo "step 1"
echo "step 2"
- id: ordered
run:
- echo "first"
- echo "second"
- id: custom_shell
run: |
set -euo pipefail
echo "running in bash"
with:
shell: /bin/bash
```
Fields:
- `run` - command string or multi-line shell script
- `with.shell` - shell interpreter, for example `/bin/bash`
- `with.shell_args` - shell interpreter arguments
- `with.shell_packages` - optional packages to install before execution
Notes:
- Single-line `run:` values are command-form entries.
- Array-form `run:` entries run one by one and stop on the first failing entry.
- Multi-line `run:` values are scripts.
- Dagu sends pipes, redirects, `&&`, and `;` to the selected shell. It does not split that shell syntax into separate Dagu commands.
- DAG-level `shell` and `shell_args` provide defaults for inherited `run` steps. Use `with.shell` and `with.shell_args` when one step needs a different shell invocation.
- Dagu resolves `${...}` references before the shell runs. For large or arbitrary text, prefer `printenv VAR_NAME`, reading `${step_id.stdout}` as a file, or `action: template.render`.
- Use scoped Dagu references for named values: `${consts.NAME}`, `${params.NAME}`, and `${env.NAME}`. Use shell `$NAME` only when the target shell should read the variable at execution time.
- When large command output should become an artifact, write it to stdout/stderr and attach the stream directly instead of redirecting inside shell:
```yaml
steps:
- id: report
run: ./generate-report --format markdown
stdout:
artifact: reports/report.md
```
- Use string-form `output: VAR_NAME` only for small stdout values. Large reports, JSON dumps, Markdown summaries, and logs belong in `stdout.artifact` / `stderr.artifact`.
## docker.run / container.run
Run commands in Docker containers.
```yaml
steps:
- id: build
action: docker.run
with:
image: golang:1.23
pull: always
auto_remove: true
working_dir: /app
volumes:
- /local/src:/app
command: go build ./...
```
`with` fields: `image`, `container_name`, `pull`, `auto_remove`, `working_dir`, `volumes`, `network`, `platform`, `command`.
Dagu can drive Docker or Podman through a Docker-compatible API. Runtime selection is service-level, not a DAG YAML field. Set `DAGU_CONTAINER_RUNTIME=podman` for Podman. Set `DAGU_PODMAN_HOST` only when the Podman socket is not the default.
## git.worktree.add / git.worktree.remove
Create isolated working directories for branches in an existing local Git repository. The actions discover the repository from the step `working_dir`; they do not clone, fetch, or push.
This example creates a generated branch, runs tests inside its worktree, and then removes the worktree explicitly:
```yaml
working_dir: ./repo
steps:
- id: worktree
action: git.worktree.add
- id: test
depends: worktree
working_dir: "${steps.worktree.outputs.path}"
run: go test ./...
- id: remove_worktree
depends: test
action: git.worktree.remove
with:
path: "${steps.worktree.outputs.path}"
```
When `branch` is omitted, Dagu generates a stable branch name for that step and DAG run. The default path is `<repository-root>.worktrees/<branch>`.
To create an explicit branch from a local commit, branch, `origin` remote-tracking branch, or tag:
```yaml
working_dir: ./repo
steps:
- id: worktree
action: git.worktree.add
with:
branch: feature/api
create_branch: true
base: main
path: ../worktrees/feature-api
```
`git.worktree.add` fields:
- `branch` - local branch to check out. Omit it to let Dagu generate one.
- `path` - worktree directory. Relative paths resolve from the repository root.
- `create_branch` - allow creation of an explicitly named branch. Defaults to `false`.
- `base` - local commit, branch, remote-tracking branch, or tag used when creating the branch. Defaults to repository `HEAD`.
The add action is idempotent. It reuses a matching registered worktree without resetting its branch or discarding local changes. Worktrees remain registered until an explicit remove action or an external Git command removes them.
Use `git.worktree.remove` for explicit removal:
```yaml
working_dir: ./repo
steps:
- id: worktree
action: git.worktree.add
- id: remove_worktree
depends: worktree
action: git.worktree.remove
with:
path: "${steps.worktree.outputs.path}"
branch: "${steps.worktree.outputs.branch}"
delete_branch: true
```
`git.worktree.remove` fields:
- `branch` and `path` - provide either selector or both. When both resolve to a worktree, they must identify the same registration.
- `force` - remove a dirty worktree. Defaults to `false`.
- `delete_branch` - delete the local branch after removing the worktree. Requires `branch`.
- `force_delete_branch` - allow deletion of an unmerged branch. Requires `delete_branch: true`.
`force` and `force_delete_branch` protect different data: `force` permits removal of local worktree changes, while `force_delete_branch` permits deletion of unmerged commits.
Both actions publish fixed outputs. Do not add `output`, `outputs`, or `stdout.outputs` to these steps. Read results through `${steps.<id>.outputs.<field>}`.
- Add outputs: `path`, `branch`, `commit`, `worktree_created`, `branch_created`.
- Remove outputs: `path`, `branch`, `worktree_removed`, `branch_deleted`.
Dagu refuses to remove the primary working tree. Worktree mutations against the same repository are serialized, but Git changes made outside Dagu are not covered by that lock.
## dag.run
Execute another DAG as a child DAG.
```yaml
steps:
- id: child
action: dag.run
with:
dag: child-workflow
params:
input: /data/file.csv
```
Sub-DAGs do not inherit parent env vars. Pass values explicitly via `with.params`.
## human.task
Pause a root DAG run until an operator completes a processless step. A human task does not execute a command and is distinct from an approval gate: completion always succeeds the step, with no reject or rewind operation.
```yaml
params:
RELEASE: v1.2.3
steps:
- id: review
action: human.task
with:
prompt: Select a deployment window for ${params.RELEASE}
form:
type: object
title: Deployment review
properties:
window:
type: string
enum: [morning, evening]
ticket:
type: string
pattern: '^CHG-[0-9]+$'
notify:
type: boolean
default: true
required: [window, ticket]
- id: deploy
depends: [review]
run: ./deploy --window '${steps.review.outputs.window}' --ticket '${steps.review.outputs.ticket}'
```
`with.prompt` is required and supports normal Dagu value references. `with.form` is optional; omit it for an acknowledgement-only task that accepts no input.
The form is a flat object JSON Schema:
- `type` must be `object`.
- Property names must start with a letter and contain only letters, digits, or `_`.
- Property types are `string`, `integer`, `number`, and `boolean`.
- Supported property constraints include `default`, `enum`, `oneOf` choices, `minimum`, `maximum`, `minLength`, `maxLength`, and `pattern`.
- `additionalProperties` defaults to `false`. Set it explicitly to `true` only when undeclared completion fields are intended.
Dagu derives outputs from form properties; do not add an `outputs:` field to the human task. Every declared property is a step output, published when submitted or defaulted, and available as `${steps.<step_id>.outputs.<name>}`.
Human tasks require an explicit `id` and cannot be used in sub-DAGs, lifecycle handlers, or `foreach.steps`. A root DAG containing human tasks can run locally or on a distributed worker selected by its DAG-level `worker_selector`. Executor, retry, repeat, timeout, container, step-level worker selector, output capture, and approval fields are not supported on the same step.
Complete a waiting task from a local CLI context:
```sh
dagu human-task complete --run-id=<run-id> --step=review --input window=morning --input ticket=CHG-123 <dag-name>
```
Use `--inputs-json` instead of repeated `--input` flags when input types must be preserved exactly.
Completing the last waiting human task resumes a local run directly. A distributed run is re-queued, so its scheduler must be running.
## Declared Step Outputs
Declare `outputs:` when a step should publish named values for later steps as `${steps.<step_id>.outputs.<name>}`.
```yaml
steps:
- id: build
run: |
printf 'image_tag=v1.2.3\n' >> "$DAGU_OUTPUT_FILE"
{
printf 'metadata<<JSON\n'
printf '{"commit":"abc123"}\n'
printf 'JSON\n'
} >> "$DAGU_OUTPUT_FILE"
outputs:
- name: image_tag
- name: metadata
type: json
- id: deploy
depends: [build]
run: ./deploy.sh '${steps.build.outputs.image_tag}'
```
Rules:
- The step must have an `id`.
- `outputs:` must be a non-empty sequence.
- Each output requires `name`.
- `type` can be `string` or `json`. The default is `string`.
- The step writes output records to `$DAGU_OUTPUT_FILE`.
- Output records use `name=value` or heredoc form: `name<<DELIMITER`, value lines, matching `DELIMITER`.
- The output file must be valid UTF-8.
- Every declared output must be written exactly once.
- Undeclared, duplicate, missing, or invalid JSON outputs fail the step.
- Dagu captures declared outputs only after the command succeeds.
## outputs.write
Publish DAG or remote action outputs assembled from literals, parameters, or prior step values.
```yaml
steps:
- id: send
run: ./scripts/notify.sh "${params.text}"
output:
response:
from: stdout
decode: json
- id: publish
depends: [send]
action: outputs.write
with:
values:
messageId: ${send.output.response.id}
status: sent
```
Published values are available as `${publish.outputs.messageId}` in the same DAG. When the step runs inside a remote action DAG, the parent action caller reads the final action outputs as `${action_step.outputs.messageId}`.
Notes:
- `values` must be a non-empty object.
- Keep values small and JSON-compatible; use artifacts for files, reports, logs, screenshots, or large JSON payloads.
- If the remote action manifest declares an `outputs` schema, Dagu validates the final collected action output object after the action DAG returns. `outputs.write` itself does not validate the manifest.
## state.get / state.set / state.delete / state.list / state.diff
Read and write persistent JSON state that survives across DAG runs. Use state actions for cursors, checkpoints, and comparing the current result with the previous run. Use artifacts or external storage for large files.
```yaml
steps:
- id: load_cursor
action: state.get
with:
key: cursors/feed
default: null
- id: save_cursor
action: state.set
with:
key: cursors/feed
value: ${fetch.output.nextCursor}
- id: detect_change
action: state.diff
with:
key: snapshots/feed
value: ${fetch.output.items}
update: true
```
Scope fields:
- `scope` - state scope: `dag` (default), `root_dag`, `global`, or `custom`
- `namespace` - namespace override. For `custom` scope, this is required.
Default namespaces:
- `dag` - current DAG name
- `root_dag` - root DAG name for nested DAG runs
- `global` - `_`
- `custom` - no default; set `namespace`
Operation fields:
- `state.get`: `key`, optional `default`, `required`
- `state.set`: `key`, `value`, optional `expected_version`, `create_only`
- `state.delete`: `key`
- `state.list`: optional `prefix`, `limit`, `include_values`
- `state.diff`: `key`, `value`, optional `expected_version`, `update`
All state actions write JSON to stdout. Common output fields include `operation`, `scope`, `namespace`, and key or prefix information.
- `state.get` returns `found`, and when found, `value`, `version`, and `hash`. If not found and `default` is set, `value` contains the default.
- `state.set` returns `version`, `hash`, and `created`.
- `state.delete` returns `deleted`.
- `state.list` returns `entries`; entry values are omitted unless `include_values` is true.
- `state.diff` returns `changed`, `foundPrevious`, `current`, optional `previous`, and `version` / `hash` when the stored value was written or already exists.
Values must be JSON-serializable. Dagu normalizes state values before storing them and enforces the state payload size limit after normalization.
## parallel
`parallel:` currently works only with `action: dag.run`.
```yaml
steps:
- id: fan_out
action: dag.run
with:
dag: process-item
parallel:
items:
- item1
- item2
- item3
max_concurrent: 5
- id: fan_out_dynamic
action: dag.run
with:
dag: process-item
parallel: ${params.ITEMS}
```
Each child invocation receives the current item as `ITEM`.
## ssh.run / sftp.upload / sftp.download
Remote command execution and file transfer over SSH.
```yaml
steps:
- id: remote
action: ssh.run
with:
user: deploy
host: server.example.com
key: ~/.ssh/id_rsa
timeout: 60s
command: systemctl restart app
- id: upload
action: sftp.upload
with:
user: deploy
host: server.example.com
key: ~/.ssh/id_rsa
source: /local/file.tar.gz
destination: /remote/file.tar.gz
```
Shared SSH fields: `user`, `host`, `port`, `key`, `password`, `timeout`, `strict_host_key`, `known_host_file`, `shell`, `shell_args`, `bastion`.
## http.request
HTTP requests.
```yaml
steps:
- id: api_call
action: http.request
with:
method: POST
url: https://api.example.com/data
headers:
Authorization: "Bearer ${env.TOKEN}"
Content-Type: application/json
body: '{"key": "value"}'
json: true
timeout: 30
```
`with` fields: `method`, `url`, `timeout`, `headers`, `query`, `body`, `silent`, `debug`, `json`, `skip_tls_verify`.
## jq.filter
JSON processing.
```yaml
steps:
- id: transform
action: jq.filter
with:
filter: ".items[] | {name: .name, count: .quantity}"
data:
items:
- name: a
quantity: 1
- id: transform_file
action: jq.filter
with:
filter: .name
input: ${fetch_json.stdout}
```
Use `with.data` for inline JSON or `with.input` for a JSON file path. Do not set both.
## template.render
Render text using Go `text/template`.
```yaml
steps:
- id: render
action: template.render
with:
data:
name: Alice
template: |
Hello, {{ .name }}!
output: RESULT
```
`with.template` is required and is rendered as a template, not executed as shell. `with.output` writes rendered content to a file; top-level `output:` captures or publishes step output.
## file.stat / file.read / file.write / file.copy / file.move / file.delete / file.mkdir / file.list
Local filesystem operations.
```yaml
steps:
- id: ensure_output_dir
action: file.mkdir
with:
path: ${context.paths.artifacts_dir}/reports
- id: write_report
action: file.write
with:
path: ${context.paths.artifacts_dir}/reports/summary.txt
content: "status=ok\n"
overwrite: true
- id: copy_report
action: file.copy
with:
source: ${context.paths.artifacts_dir}/reports/summary.txt
destination: ${context.paths.artifacts_dir}/reports/latest.txt
overwrite: true
- id: list_reports
action: file.list
with:
path: ${context.paths.artifacts_dir}/reports
pattern: "*.txt"
```
Use `path` for `file.stat`, `file.read`, `file.write`, `file.delete`, `file.mkdir`, and `file.list`. Use `source` and `destination` for `file.copy` and `file.move`. `file.write` also requires `content`.
`with` fields: `path`, `source`, `destination`, `content`, `mode`, `format`, `pattern`, `overwrite`, `create_dirs`, `atomic`, `recursive`, `missing_ok`, `dry_run`, `include_dirs`, `follow_symlinks`, `max_bytes`.
Safety defaults:
- `overwrite` defaults to false for write, copy, and move.
- `atomic` defaults to true for file writes.
- `recursive` is required for directory copy and directory delete.
- `file.delete` refuses to delete the filesystem root.
- Copy and move reject the same source and destination, and directory copy rejects destinations inside the source tree.
## postgres.query / sqlite.query / postgres.import / sqlite.import
SQL database queries and imports.
```yaml
steps:
- id: query
action: postgres.query
with:
dsn: "postgres://user:pass@localhost:5432/db"
query: "SELECT * FROM users WHERE active = true"
output_format: json
timeout: 120
transaction: true
```
`with` fields include `dsn`, `query`, `params`, `timeout`, `transaction`, `isolation_level`, `output_format`, `headers`, `null_string`, `max_rows`, `streaming`, `output_file`, and `import`.
## redis.<operation>
Redis operations use the operation in the action name.
```yaml
steps:
- id: cache_set
action: redis.set
with:
url: "redis://localhost:6379"
key: mykey
value: myvalue
ttl: 3600
```
Connection fields: `url`, `host`, `port`, `password`, `username`, `db`, TLS fields, `mode`, `timeout`, `max_retries`.
## s3.upload / s3.download / s3.list / s3.delete
S3 object operations.
```yaml
steps:
- id: upload
action: s3.upload
with:
region: us-east-1
bucket: my-bucket
key: data/output.csv
source: /local/output.csv
```
Connection fields: `region`, `endpoint`, `access_key_id`, `secret_access_key`, `session_token`, `profile`, `force_path_style`.
## mail.send
Send email.
```yaml
steps:
- id: notify
action: mail.send
with:
from: noreply@example.com
to: team@example.com
subject: "Build Complete"
message: "The build finished successfully."
```
SMTP server settings come from global configuration.
## archive.create / archive.extract / archive.list
Archive operations.
```yaml
steps:
- id: compress
action: archive.create
with:
source: /data/output
destination: /data/output.tar.gz
format: tar.gz
exclude:
- "*.tmp"
```
`with` fields: `source`, `destination`, `format`, `compression_level`, `password`, `overwrite`, `strip_components`, `include`, `exclude`.
## harness.run
Invoke external coding-agent CLIs through built-in provider adapters or custom harness definitions.
```yaml
harnesses:
gemini:
binary: gemini
prefix_args: ["run"]
prompt_mode: flag
prompt_flag: --prompt
harness:
provider: gemini
model: gemini-2.5-pro
fallback:
- provider: claude
model: sonnet
steps:
- id: generate_tests
action: harness.run
with:
prompt: "Write unit tests for the auth module"
yolo: true
output: RESULT
```
`with.prompt` is required and is passed to the selected provider according to its built-in adapter or custom harness definition. `with.provider` can be a built-in provider adapter (`claude`, `codex`, `copilot`, `opencode`, `pi`) or a top-level `harnesses:` entry. For host subprocess runs, `with.stdin` is piped to stdin as supplementary context.
Harness behavior:
- Built-in provider adapters and custom providers pass non-reserved `with` keys as CLI flags. Built-in adapters normalize `snake_case` keys to kebab-case flags.
- `fallback` is an ordered list of provider configs. Nested fallback is not supported.
- Provider value references must resolve to a concrete provider string before execution. Unresolved `${...}` provider values fail at runtime.
- Prefer `action: harness.run` for new workflows. A top-level `harness:` config still causes steps without an explicit executor type to infer the harness executor for compatibility.
Container support:
- Use root-level `container:` to run compatible harness steps inside the shared DAG-level container.
- Use step-level `container:` when only that step needs a container, or when it needs a different container from the root-level container.
- Step-level `container:` takes precedence for that step.
- The selected provider binary must exist inside the container that runs the step.
- `with.stdin` and custom `prompt_mode: stdin` are rejected for containerized harness steps.
- Do not set `container.name` for step-level image-mode harness steps. Use `container.exec` when the step must run inside an existing container.
- Docker or Podman is selected by the Dagu service process, not by a DAG YAML field.
## router.route
Conditional routing based on expression value. Routes reference existing step IDs.
```yaml
steps:
- id: check_status
run: "curl -s -o /dev/null -w '%{http_code}' https://example.com"
output: STATUS
- id: route
action: router.route
with:
value: ${env.STATUS}
routes:
"200":
- handle_ok
"re:5\\d{2}":
- handle_error
- send_alert
depends: [check_status]
- id: handle_ok
run: echo "success"
- id: handle_error
run: echo "server error occurred"
- id: send_alert
run: echo "alerting on-call"
```
Routes are evaluated in priority order: exact matches first, then regex, then catch-all.
---
# Remote Action Packages
Use this reference when creating a reusable package-style action with `dagu-action.yaml`.
Remote actions are different from DAG-local `actions:` templates:
- DAG-local `actions:` are inline wrappers around built-in actions.
- Remote actions are directories or Git repositories that contain a manifest, a DAG entrypoint, and any helper files the action needs.
- Callers use them with `action: owner/repo@version`, `action: name@version`, or `action: source:target@version`.
## Package Layout
```text
dagu-action-notify/
├── dagu-action.yaml
├── workflow.yaml
└── scripts/
└── notify.sh
```
This reference uses `workflow.yaml` as the recommended entrypoint DAG filename to keep it visually distinct from the `dagu-action.yaml` manifest. The `dag` field can point to any safe relative file path inside the package.
`dagu-action.yaml` supports exactly these fields:
- `apiVersion` - required, currently `v1alpha1`
- `name` - required action name
- `dag` - required relative path to the action DAG file
- `inputs` - optional JSON Schema object for the caller's `with:`
- `outputs` - optional JSON Schema object for the action output object
Unknown manifest keys are rejected. The `dag` path must resolve to a file inside the package.
## Manifest Example
```yaml
apiVersion: v1alpha1
name: notify
dag: workflow.yaml
inputs:
type: object
additionalProperties: false
required: [text]
properties:
text:
type: string
outputs:
type: object
additionalProperties: false
required: [messageId]
properties:
messageId:
type: string
status:
type: string
```
`inputs` validates the caller's `with:` object before the action DAG starts. JSON Schema `default` values are validated as schema defaults, but they are not applied to the caller's `with:` object before parameters are passed.
## Action DAG
The action DAG is a normal Dagu workflow. Do not set `working_dir` in the action DAG or local sub-DAGs inside the package; Dagu runs them in the materialized action workspace so relative package files are available.
```yaml
tools:
- jqlang/jq@jq-1.7.1
params:
- text
steps:
- id: send
run: ./scripts/notify.sh "${params.text}"
stdout:
outputs:
fields:
messageId:
decode: json
select: .id
status:
decode: json
select: .status
```
Scalar `with:` fields are passed as runtime parameters and can be read as `${params.text}`. For structured input, pass an explicit JSON string and decode it in the action DAG; do not assume nested YAML/JSON input objects arrive as structured params.
## Tools
If the action DAG invokes portable external CLIs, declare them with top-level `tools` in the action DAG file. Do not put `tools` in `dagu-action.yaml`; unknown manifest keys are rejected.
Caller DAG tools are not inherited by remote actions. The action DAG is a separate DAG run, and the worker running it prepares that DAG's tools in the worker-local tools cache. Built-in-only actions do not need `tools`, but reusable action packages that call binaries such as `jq`, `yq`, or release helpers should pin those dependencies inside the action DAG.
## Returning Outputs
Use `stdout.outputs` when a command emits the action result on stdout:
```yaml
steps:
- id: classify
run: ./classify.sh "${params.text}"
stdout:
outputs: