RunWisp

Comparison · vs Dagu

RunWisp vs Dagu: not every job needs a DAG.

Dagu orchestrates multi-step pipelines with dependencies, conditionals, and parameter passing. RunWisp schedules independent tasks and babysits long-running services. Different tools, different problems. Pick the one that matches your workload.

Quick start GitHub

Should you switch?

The 10-second version. Skim, decide, scroll down for the receipts.

Consider RunWisp if

  • Every job on your box is independent. No step B waits on step A's output.
  • You also need long-running services supervised alongside scheduled tasks.
  • DST handling, overlap policies, and catch-up after downtime matter more than DAG edges.
  • You want per-run history with stdout/stderr captured automatically.
  • A TUI over SSH is part of your workflow.

Stay on Dagu if

  • Any of your jobs form a pipeline: extract then transform then load.
  • Steps pass outputs to downstream steps via parameters.
  • Conditional execution matters: skip step C if step B produced no rows.
  • You need the DAG visualization in the web UI to reason about your workflow.
  • You're building data pipelines, not running cron jobs.

Yes / no, at a glance

Yes / no, both columns win on something.

Feature Dagu RunWisp
Single binary, no deps
Step dependencies (DAG)
core feature
Conditional execution
preconditions
Parameter passing between steps
output vars
Cron scheduling
5/6-field + aliases
Long-running services
batch only
[services.*]
Run history per firing
SQLite rows
Captures stdout / stderr
Retries with backoff
retryPolicy
constant / linear / exp
Failure notifications
email
Slack / Discord / webhook / +
Overlap policy
~ skip if running
skip / queue / allow
Catch-up after downtime
latest / all / skip
DST handling
deduped / next-valid
Web UI
DAG graph view
run history + logs
TUI
DAG visualization
Config format
~ YAML
~ TOML

Pros & cons

What Dagu does better

  • DAG execution is the whole point. Steps depend on each other, run conditionally, pass outputs downstream.
  • Preconditions let you skip or abort a step based on the output of a previous one.
  • The web UI renders the DAG as a graph. You can see which step failed and why the downstream steps never ran.
  • Parameters and templating let you build reusable workflow definitions.
  • Sub-DAG execution: one DAG can call another DAG as a step.
  • If your workload is a pipeline, Dagu was built for it. RunWisp was not.

What RunWisp adds

  • Long-running services alongside scheduled tasks in one config file and one process tree.
  • DST handling as named behaviour: fall-back deduped, spring-forward fires at the next valid minute.
  • catch_up = "latest" / "all" / "skip" after downtime. Dagu skips missed runs.
  • on_overlap = "skip" / "queue" / "allow" per task.
  • Coalesced failure notifications. Five flaps become one Slack message, not five.
  • runwisp tui streams run history and logs over SSH. No browser needed.
  • Simpler config for the simple case. If every job is independent, a flat TOML stanza per task is less ceremony than a YAML DAG.

The simple case

Run a backup script. Dagu defines a single-step DAG. RunWisp defines a scheduled task. For one independent job, the DAG wrapper is ceremony.

backup.yaml (Dagu)
backup.yaml (Dagu)
# backup.yaml — a single-step Dagu DAG
steps:
  - name: backup
    command: /usr/local/bin/backup.sh
runwisp.toml
runwisp.toml
# runwisp.toml
[tasks.backup]
cron = "0 3 * * *"
run  = "/usr/local/bin/backup.sh"

Production-grade

A four-step ETL pipeline. Dagu expresses the dependency chain: extract, validate, transform, load. RunWisp can run the same four scripts, but they fire independently on the same schedule. If step order matters, Dagu is the right tool here.

etl-pipeline.yaml (Dagu)
etl-pipeline.yaml (Dagu)
# etl-pipeline.yaml — extract, transform, load with dependencies
schedule: "0 4 * * *"
params:
  - DATE: "{{ .Now.Format "2006-01-02" }}"
steps:
  - name: extract
    command: /opt/etl/extract.sh
    output: RAW_FILE
  - name: validate
    command: /opt/etl/validate.sh
    depends:
      - extract
    preconditions:
      - condition: "$RAW_FILE"
        expected: re:.*\.csv$
  - name: transform
    command: /opt/etl/transform.sh $RAW_FILE
    depends:
      - validate
    output: CLEAN_FILE
  - name: load
    command: /opt/etl/load.sh $CLEAN_FILE
    depends:
      - transform
    retryPolicy:
      limit: 3
      intervalSec: 30
runwisp.toml (flat, no deps)
runwisp.toml (flat, no deps)
# runwisp.toml — same four scripts, but no dependency chain
# RunWisp can't express "load depends on transform".
# If your ETL steps MUST run in order, keep Dagu.
# If they're truly independent, RunWisp is simpler:

[tasks.extract]
cron              = "0 4 * * *"
timeout           = "30m"
keep_runs         = 60
notify_on_failure = ["slack-ops"]
run               = "/opt/etl/extract.sh"

[tasks.validate]
cron              = "0 4 * * *"
timeout           = "10m"
keep_runs         = 60
run               = "/opt/etl/validate.sh"

[tasks.transform]
cron              = "0 4 * * *"
timeout           = "45m"
retry_attempts    = 3
retry_delay       = "30s"
retry_backoff     = "exponential"
keep_runs         = 60
run               = "/opt/etl/transform.sh"

[tasks.load]
cron              = "0 4 * * *"
timeout           = "20m"
retry_attempts    = 3
retry_delay       = "30s"
retry_backoff     = "exponential"
keep_runs         = 60
notify_on_failure = ["slack-ops"]
run               = "/opt/etl/load.sh"

Migration cheat sheet

Only migrate if your DAGs are actually independent jobs. If steps depend on each other, keep Dagu.

Dagu RunWisp
schedule: in the YAML cron = on each [tasks.*]
steps[].command run =
steps[].depends No equivalent. If jobs are independent, drop it. If not, keep Dagu.
steps[].output: VAR No equivalent. Write to a file and read it back if needed.
steps[].preconditions Check inside the script itself, exit non-zero to fail.
retryPolicy.limit retry_attempts =
retryPolicy.intervalSec retry_delay = + retry_backoff =
mailOn: failure notify_on_failure = ["slack-ops"]
Dagu web UI (DAG graph) RunWisp web UI (flat run history) or runwisp tui
params: with Go templates Environment variables in env = { ... } or inline in the script

Gotchas

FAQ

More comparisons: RunWisp vs cron · RunWisp vs PM2 · RunWisp vs Ofelia .

Or see what RunWisp is in one page →

Not every job needs a DAG. Some just need to run on time.

If your tasks are independent, a flat TOML stanza per job is all you need. Install the binary, write a schedule, watch it land in the dashboard.