Configuration overview
Everything RunWisp does is configured in one file: runwisp.toml. It’s
the sole source of truth for what runs and how — the REST API and Web
UI can read and trigger, but they never touch your task definitions.
Edited the file? Run runwisp reload (or send SIGHUP) to pick it up;
a few daemon-wide settings need a full restart.
The file breaks into a handful of sections, and most of them are
optional. You’ll usually want at least one [tasks.*] or [services.*]
table with a run key — otherwise there’s nothing for the daemon to
run.
A complete example
Section titled “A complete example”# Disk-usage safeguards[storage]max_size = "5gb"min_free_space = "500mb"
# Global defaults applied to every task unless overridden[defaults]timeout = "1h"log_max_size = "100mb"log_on_full = "drop_old"keep_runs = 50keep_for = "30d"
[tasks.backup-db]group = "Backups"description = "Nightly database backup"cron = "0 2 * * *"timeout = "30m"on_overlap = "skip"keep_runs = 30run = "pg_dump mydb | gzip > /backups/mydb-$(date +%F).sql.gz"
[tasks.process-event-queue]description = "Worker that retries with exponential backoff"cron = "*/10 * * * *"on_overlap = "queue"retry_attempts = 3retry_delay = "2s"retry_backoff = "exponential"run = "/usr/local/bin/process-queue"
[services.metrics-daemon]description = "Always-on metrics collector"run = "/usr/local/bin/metrics-agent"Run runwisp in a directory that doesn’t have a runwisp.toml yet and
it’ll offer to write a minimal starter for you — the
Quick start walks through that.
Section by section
Section titled “Section by section”| Section | Purpose | Reference |
|---|---|---|
[storage] | Disk-usage limits — caps total bytes used and reserves headroom on the data partition. | [storage] |
[daemon] | Daemon-wide settings — shutdown budget, public URL for notification links, the metrics endpoint. | [daemon] |
[scheduler] | Scheduler-wide settings — the default timezone every cron expression is evaluated in. | Timezone |
[defaults] | Defaults inherited by every task and service unless explicitly overridden. | [defaults] |
[tasks.<name>] | Scheduled or manually-triggered units of work. Cron expression, concurrency, retries, timeout, retention, per-execution parameters. | [tasks.*] |
[services.<name>] | Always-on processes. One or more instances, exponential restart backoff, graceful shutdown. | [services.*] |
[compose.<alias>] | Imports services from an existing docker-compose.yml so each becomes an observable RunWisp service. | [compose.*] |
[[notifier]] | Declares one outbound channel. Repeated for each channel. | Notifications model |
[[notification_route]] | Sends events (run.failed, run.timeout, …) to one or more channels. One rule, many tasks. | Notification rules |
[notify] | Global notification settings — bell channels, retry budget, history retention, coalescing. | Global settings |
notify_on_failure | Sets the channels to notify when one specific task ends failed, timeout, or crashed. | Per-task notifications |
One rule cuts across every section: any string value can pull from an
env var or a file with ${VAR} / ${file:path} —
${...} substitution has the details.
Reload semantics
Section titled “Reload semantics”Edit runwisp.toml, then run runwisp reload (or send the daemon a
SIGHUP) to pick up the change without bouncing the process. Tasks get
added, changed, and removed live; in-flight runs finish under the
definition they started with. It’s always explicit — RunWisp never
watches the file and reloads on its own. The reload is validate-first, so
a typo can’t take your scheduler down, and a few daemon-wide settings
still need a full restart. The Reload page has the
details.
For everything reload won’t do live — changing [storage], the scheduler
timezone, the bind port, or upgrading the binary — restart instead: stop
the daemon, make the change, and run runwisp daemon again (or bounce it
through whatever supervisor is managing the process). In-flight runs are
cancelled on the way down — they end as stopped if they bow out within
the [daemon] shutdown_timeout window, or get reconciled as crashed on
the next boot if they don’t — then the database reopens and the schedule
comes back up from the new file.
One thing to watch: a parse error at startup kills the boot. The daemon
exits non-zero before it ever opens its port, and by then your previous
instance is already gone. The safe pre-flight is to run runwisp validate against the new file before you restart. (A bad edit picked up
by runwisp reload is safe by contrast — it’s rejected and the running
daemon keeps going.)
What the schema considers a breaking change
Section titled “What the schema considers a breaking change”This is pre-1.0, so any release might change the TOML schema, the REST API, or the on-disk layout — and an upgrade may wipe run history. Anything like that gets flagged in the CHANGELOG; the semver guarantees kick in at 1.0.