[tasks.*]
A task is a unit of work that runs and exits. The TOML key (backup-db
in [tasks.backup-db]) is the task name — it appears in the CLI,
the API, the Web UI, and the on-disk log path. Names must be unique
across both [tasks.*] and [services.*].
Only two fields are truly required: the table itself and run.
Everything else inherits from [defaults]
or has a sensible built-in default.
Minimum example
Section titled “Minimum example”[tasks.heartbeat]cron = "*/5 * * * *"run = "/usr/local/bin/heartbeat"That’s a complete, valid task: it fires every five minutes, captures stdout/stderr to a log file, and persists each run to SQLite.
Identity & metadata
Section titled “Identity & metadata”| Key | Type | Default | What it does |
|---|---|---|---|
| table key | string | required | The task name. Used in CLI (runwisp exec <name> / runwisp run-task <name>), API, log paths. |
run | string | required | Shell command. Multi-line OK with TOML triple-quotes ("""…"""). Trusted input — see note. |
description | string | (empty) | Human-readable description shown in the UI and TUI. |
group | string | "Tasks" | UI grouping label. Set to share a section header with related tasks. |
api_trigger | bool | true | Allow manual triggers from CLI / API / UI. Set false to make the task cron-only. |
Scheduling
Section titled “Scheduling”| Key | Type | Default | What it does |
|---|---|---|---|
cron | string | (none) | 5-field cron expression. Supports @hourly, @every 1h30m. |
timezone | string | inherited | IANA timezone for this task’s cron evaluation (e.g. "America/New_York"). Falls back to [scheduler] timezone, which itself defaults to UTC. |
catch_up | enum | "latest" | What to do for missed firings on startup: latest, all, or skip. |
Without a cron, the task is manual-only — it runs only when
triggered explicitly. That’s a valid pattern for one-shot deploy hooks
and similar.
See How scheduling works for the full cron grammar and DST behaviour, and the catchup table.
Concurrency
Section titled “Concurrency”| Key | Type | Default | What it does |
|---|---|---|---|
parallelism | int | 1 | Maximum concurrent runs of this task. Must be > 0. |
on_overlap | enum | "queue" | What happens when a new firing arrives at the parallelism limit: queue/skip/terminate. |
Most cron-style work wants parallelism = 1. See
Concurrency policies for when each policy is
right.
Retries & timeout
Section titled “Retries & timeout”| Key | Type | Default | What it does |
|---|---|---|---|
retry_attempts | int | 0 | Additional attempts after the initial failure. Must be >= 0. |
retry_delay | duration | 5s (when retries are on) | Base delay between attempts. Go duration string. |
retry_backoff | enum | "constant" | Curve applied to retry_delay: constant, linear, or exponential. |
timeout | duration | inherited from [defaults] | Per-attempt wall-clock cap. 0 or unset means no timeout. |
A retry only fires for failed / timeout / crashed. Manual stops
and on_overlap = "terminate" end the chain. Full table of formulas
on the Retries & timeouts page.
restart may be set to "never" (default) or "on_failure". Setting
restart = "always" on a task is rejected at config load — use
[services.*] for an always-on process.
Logs & retention
Section titled “Logs & retention”| Key | Type | Default | What it does |
|---|---|---|---|
log_max_size | byte size | 100MB | Per-run log cap. Units: b, kb, mb, gb, tb. 0 is unbounded. |
log_on_full | enum | "drop_old" | What to do at the cap: drop_new, drop_old, kill_task. |
keep_runs | int | inherited from [defaults] | Keep the N most recent runs for this task. 0 (or omitted) inherits the default; -1 is explicit unlimited; positive N keeps N. |
keep_for | duration | inherited from [defaults] | Delete runs older than this. Accepts d and w units (30d, 2w). Omit to inherit; "unlimited" is explicit unlimited. |
Both keep_runs and keep_for apply if both are set; the stricter one
wins in practice. See Logs & retention for the full
picture.
Notifications
Section titled “Notifications”| Key | Type | Default | What it does |
|---|---|---|---|
notify_on_failure | string[] | (none) | Notifier IDs to alert on run.failed / run.timeout / run.crashed. |
notify_on_success | string[] | (none) | Notifier IDs to alert on run.succeeded. |
Each entry is a notifier id declared in a [[notifier]]
block. Whatever channels are listed in [notify] default_notifiers
(default: ["inapp"]) are appended automatically — set
default_notifiers = [] to opt out, or ["slack-ops"] to make every
failure page Slack instead. See
Per-task notification sugar
for how this expands.
What’s rejected on tasks
Section titled “What’s rejected on tasks”The config loader refuses these at startup so they can’t fail silently later:
restart = "always"— use[services.*].instances = N— services-only field.- A task name that’s also used by
[services.*]— names share one namespace. - Empty or missing
run.
Worked example
Section titled “Worked example”[tasks.process-event-queue]group = "Workers"description = "Drain the queue every 10 minutes with retries"cron = "*/10 * * * *"on_overlap = "skip" # never two queue drainers at oncetimeout = "9m" # die before the next firingretry_attempts = 3retry_delay = "2s"retry_backoff = "exponential"keep_runs = 200keep_for = "14d"notify_on_failure = ["slack-ops"]run = """set -euecho "Draining queue at $(date -Iseconds)"/usr/local/bin/process-queue"""The full annotated example file lives at
apps/runwisp/runwisp.example.toml
or you can drop a fresh copy with runwisp init.
Where to next
Section titled “Where to next”[services.*]reference — the always-on counterpart.[defaults]reference — what fields fall through.- Concurrency policies, Retries & timeouts, Logs & retention — the conceptual pages behind the keys.