Skip to content

Tasks and services

RunWisp runs two kinds of work. A task runs and exits: a backup, a report, a health probe. A service is meant to stay up: a queue worker, an agent, a server. If the command exits on its own when it’s done, it’s a task. If it runs until you stop it, it’s a service.

[tasks.backup-db]
cron = "0 3 * * *"
run = "pg_dump app | gzip > /backups/app.sql.gz"
[services.worker]
instances = 2
run = "node worker.js"

If you find yourself writing while true; do ...; done in a task, make it a service (or see a polling loop below).

Tasks are started by something: a cron schedule, a manual run, or an API call. Only tasks have scheduling (catch_up, jitter, run_on_start), parameters, overlap rules, and retries.

Services start with the daemon and, by default, are restarted when they exit. Only services have instances, restart, healthy_after, and boot ordering (autostart, priority, depends_on).

Putting a key on the wrong kind is a config error. Everything else (logs, timeouts, environment, failures, notify) works the same on both, and every task run and every service instance shows up in run history with its exit code and output.

Tasks and services share one set of names, so a task and a service can’t both be called worker.

A task uses max_concurrent: the most runs that may overlap. A service uses instances: how many copies are always up.

  • max_concurrent = 4 on a thumbnail task that runs every minute. Most runs finish in seconds, but during a backlog up to four run at once.
  • instances = 4 on a queue worker. Four processes stay connected to the queue, and RunWisp restarts any one that exits.

Get this backwards and it goes wrong in a predictable way. A long-lived worker written as a task holds its slot while the queue backs up. A 30-second job written as a service gets restarted over and over.

while true; do work; sleep 60; done is usually better as a task with cron = "* * * * *" and on_overlap = "skip". Each poll then gets its own run, exit code, and log, so you can see exactly which one failed.

Keep it a service when the process holds state worth keeping between rounds: a queue connection, a websocket, a cache it built in memory.

Still a task. Leave out cron and start it with runwisp run <name>, the Run Task button in the Web UI, the TUI, or the REST API.

Tasks already queue by default: a new run waits for the previous one to finish. If two runs must never even line up, use on_overlap = "skip". See Overlapping runs.