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, extra config files (include), and real crontabs read as live tasks (include_cron). |
[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.