RunWisp

Comparison · vs supervisord

RunWisp vs supervisord: process supervision without Python.

A real supervisord.conf [program:*] block next to the equivalent runwisp.toml service. Replicas, restart backoff, graceful stop, reload semantics.

Quick start GitHub

Should you switch?

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

Switch to RunWisp if

  • You'd rather not ship Python on every supervised box.
  • You want every restart as a queryable run row, not a log tail.
  • Replicas should be one config, not N templated programs.
  • Restart backoff needs a curve and a duration-based reset.
  • You also have scheduled jobs that don't fit a [program:*] mould.
  • Graceful-stop default should kill the process group, not just the parent.

Stay on supervisord if

  • supervisorctl reread + update on one program matters.
  • You lean on the event-listener fan-out for lifecycle hooks.
  • Existing tooling speaks the XML-RPC API today.
  • Python's already on the box; one fewer artifact to ship.

Yes / no, at a glance

Yes / no, both columns win on something.

Feature supervisord RunWisp
Runtime-free single binary
needs Python
Always-on services
Scheduled jobs (cron-shaped)
[tasks.*]
Replicas in one config
~ numprocs templating
instances = N
Restart-backoff curve
startretries cap only
Duration-based stability reset
backoff_reset_after
Per-run history rows
Web UI
~ built-in HTTP
TUI
Graceful stop kills process group by default
~ opt-in
Coalesced failure notifications
Surgical per-program reload
daemon-wide
Event-listener fan-out
Documented RPC API
XML-RPC
~ REST
Per-program user identity
~ sudo wrapper
Bounded daemon shutdown
stopwaitsecs serial

Pros & cons

What supervisord still does better

  • supervisorctl reread + update reloads one program without touching the rest.
  • Documented event-listener subprocess protocol for lifecycle side effects.
  • Long tail of XML-RPC clients in every language.
  • Decades of Puppet, Ansible, and SaltStack modules assume it exists.

What RunWisp adds

  • One static Go binary; no Python, no venv, no pip.
  • instances = N as one service with unified per-service logs.
  • Restart backoff as a curve (constant / linear / exponential) plus backoff_reset_after.
  • Every replica start and exit is a row in the Web UI with exit code and captured streams.
  • Tasks (cron, on_overlap, retries) split from services at the schema level.
  • Process-group-wide graceful stop is the default.
  • Slack / Telegram / email notifiers coalesce flapping into one alert plus a summary.
  • Daemon shutdown fans grace windows out in parallel under a single ceiling.

The simple case

Keep one API process alive. Four INI lines vs one TOML stanza.

supervisord.conf
supervisord.conf
; /etc/supervisor/conf.d/api.conf
[program:api]
command = /usr/local/bin/api
user    = api
autostart    = true
autorestart  = true
stdout_logfile = /var/log/api.log
redirect_stderr = true
runwisp.toml
runwisp.toml
# runwisp.toml
[services.api]
run = "/usr/local/bin/api"

Production-grade

Three replicas of a queue worker. Templated programs on the left, one service with instances = 3 on the right.

supervisord.conf (production)
supervisord.conf (production)
; /etc/supervisor/conf.d/api-worker.conf, three replicas, production
[program:api-worker]
command       = /usr/local/bin/worker
process_name  = %(program_name)s_%(process_num)02d
numprocs      = 3
user          = api
autostart     = true
autorestart   = true
startsecs     = 5            ; uptime before considered "successfully started"
startretries  = 5            ; give up after this many start failures
stopsignal    = TERM
stopwaitsecs  = 20           ; finish the in-flight job
stdout_logfile  = /var/log/api-worker_%(process_num)02d.log
redirect_stderr = true
runwisp.toml (production)
runwisp.toml (production)
# runwisp.toml, three replicas, production
[services.api-worker]
description         = "Three always-on workers consuming the same job queue"
instances           = 3
restart_delay       = "2s"
restart_backoff     = "exponential"
backoff_reset_after = "2m"     # this one needs longer to call "stable"
graceful_stop       = "20s"    # leave time to finish the in-flight job
keep_runs           = 500
notify_on_failure   = ["slack-ops"]
run = """
trap 'echo "SIGTERM received, draining and exiting"; exit 0' TERM INT
echo "[$(date -Iseconds)] worker starting up..."
while true; do
  /usr/local/bin/consume-job
done
"""

Migration cheat sheet

supervisord field on the left, runwisp.toml field on the right.

supervisord RunWisp
[program:api] [services.api] (always-on) or [tasks.api]
command = run =
numprocs = 3 instances = 3
%(process_num)s No per-replica index; declare N services if they need to differ
autorestart = true Implicit; services are always-on
startsecs / startretries backoff_reset_after + restart_backoff
stopwaitsecs = 20 graceful_stop = "20s"
stopasgroup / killasgroup Default (process-group-wide)
stdout_logfile = … Captured per run; download from Web UI
user = api Wrap run with sudo -u
supervisorctl status Web UI run list / runwisp tui
supervisorctl reread + update runwisp validate + daemon restart

Gotchas

FAQ

More comparisons: RunWisp vs cron · RunWisp vs systemd timers · RunWisp vs PM2 .

One binary supervising your processes. No Python in sight.

Copy a program block into a services stanza, set instances, restart the daemon. Replicas come up indexed and visible.