cd /news/developer-tools/show-hn-supervice-process-supervisor… · home topics developer-tools article
[ARTICLE · art-97622] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Show HN: Supervice – process supervisor for agentic processes. zero dependencies

Supervice, a zero-dependency, pure-Python process supervisor for Unix-like systems built on asyncio, has been released on GitHub, offering automatic restart, health checks, process grouping, hot config reload, and a Unix socket RPC interface for Python 3.10+ environments. The tool supports daemon mode, graceful shutdown, process group kill, log rotation, uptime tracking, retry with backoff, and user switching, with a command-line interface (supervicectl) for status, start/stop/restart, group operations, and hot reload. It is positioned as a production-ready alternative to traditional supervisors like supervisord, with no external dependencies beyond the Python standard library.

read9 min views1 publishedAug 15, 2026
Show HN: Supervice – process supervisor for agentic processes. zero dependencies
Image: Michielbdejong (auto-discovered)

A modern, lightweight, and fully async process supervisor for Unix-like systems. Zero dependencies. Pure Python. Production-ready.

Supervice manages long-running processes with automatic restart, health checking, process grouping, hot config reload, and a Unix socket RPC interface — all built on Python's asyncio

with no external packages.

Async-first— Built entirely onasyncio

for efficient I/O multiplexingZero dependencies— Pure Python stdlib; nothing to install beyond Python 3.10+** Process groups**— Organize related processes and control them as a unit** Health checks**— TCP connectivity and script-based health monitoring with auto-restart** Hot reload**— Add/remove programs without restarting the daemon (supervicectl reload

)Daemon mode— Proper double-fork daemonization with PID file locking** Graceful shutdown**— SIGTERM/SIGINT triggers orderly stop of all child processes** Process group kill**— Stops entire process trees, not just the main PID** Log rotation**— Built-inRotatingFileHandler

with configurable size and backup countUptime tracking— Per-process wall-clock uptime displayed in status output** Retry with backoff**— Configurable start retries with automatic FATAL state on exhaustion** User switching**— Run processes as a specific user (requires root)** Type-safe**— Fully type-hinted, passesmypy --strict

Requirements: Python 3.10+ on a Unix-like OS (Linux, macOS).

git clone https://github.com/yourusername/supervice.git
cd supervice
pip install .

pip install -e ".[docs]"
[supervice]
loglevel = INFO
logfile = supervice.log
pidfile = supervice.pid

[program:webapp]
command = python3 -u app.py
autostart = true
autorestart = true
stdout_logfile = webapp.log
stderr_logfile = webapp_err.log

[program:worker]
command = python3 -u worker.py
numprocs = 4
autostart = true
autorestart = true
stdout_logfile = worker_%(process_num)s.log
stderr_logfile = worker_err_%(process_num)s.log
supervice -c supervisord.conf -n

supervice -c supervisord.conf
supervicectl status


supervicectl stop worker:00
supervicectl start worker:00
supervicectl restart worker:00
supervicectl restart worker:00 --force   # SIGKILL instead of graceful

supervicectl stopgroup workers
supervicectl startgroup workers

supervicectl reload

supervicectl -s /var/run/supervice.sock status
Option Default Description
logfile
(stdout)
Daemon log file; empty logs to stdout in foreground, supervice.log when daemonized
loglevel
INFO
Log level: DEBUG , INFO , WARNING , ERROR , CRITICAL
pidfile
supervice.pid
Path to the PID/lock file; set to none (or empty) to disable
socket
(runtime dir)
RPC socket; defaults to $XDG_RUNTIME_DIR/supervice.sock (root: /run/supervice.sock , else ~/.supervice.sock )
shutdown_timeout
30
Seconds to wait for graceful shutdown
log_maxbytes
52428800
Max log file size before rotation (bytes, 0 = no rotation)
log_backups
10
Number of rotated log backup files to keep
Option Default Description
command
(required)
Command to execute (supports shell-style quoting)
numprocs
1
Number of instances to run (creates NAME:00 , NAME:01 , ...)
autostart
true
Start automatically when daemon starts
autorestart
true
Restart automatically when process exits
startsecs
1
Seconds a process must run to be considered successfully started
startretries
3
Max consecutive start attempts before entering FATAL state
stopsignal
TERM
Signal to send when stopping (TERM , INT , QUIT , KILL , etc.)
stopwaitsecs
10
Seconds to wait after stop signal before sending SIGKILL
stdout_logfile
(none)
File for stdout (rotated by the daemon; supports %(process_num)s )
stderr_logfile
(none)
File for stderr (rotated by the daemon; supports %(process_num)s )
stdout_logfile_maxbytes / stderr_logfile_maxbytes
50MB
Child log rotation threshold (0 disables)
stdout_logfile_backups / stderr_logfile_backups
10
Rotated child log backups to keep
pdeathsig
true
Linux/FreeBSD: SIGKILL the direct child if the supervisor dies. One generation only — grandchildren are never covered; see
reconcile
auto
Orphans of a crashed supervisor found at startup: auto , kill , warn , off . Matches on identity, not pid — see
environment
(none)
Environment variables: KEY=VAL,KEY2="val with,comma"
env_file
(none)
Comma-separated KEY=VALUE secrets files (# comments, quotes stripped); read as the supervisor before the privilege drop. Later files win; environment overrides env_file
directory
(none)
Working directory for the process
user
(none)
Run as this user (requires root privileges)
Option Default Description
programs
(required)
Comma-separated list of program names
Option Default Description
healthcheck_type
none
Health check type: none , tcp , script
healthcheck_interval
30
Seconds between health checks
healthcheck_timeout
10
Seconds to wait for health check response
healthcheck_retries
3
Consecutive failures before marking unhealthy
healthcheck_start_period
10
Seconds to wait before starting health checks
healthcheck_port
(none)
TCP port to check (required for tcp type)
healthcheck_host
127.0.0.1
TCP host to check
healthcheck_command
(none)
Script to run (required for script type)

Example with health checks:

[program:api]
command = python3 -u api_server.py
autostart = true
autorestart = true
healthcheck_type = tcp
healthcheck_port = 8080
healthcheck_interval = 15
healthcheck_retries = 3
healthcheck_start_period = 5
STOPPED ──┐
EXITED  ──┼──> STARTING ──> RUNNING ──> STOPPING ──> STOPPED
FATAL   ──┤                    │                        │
BACKOFF ──┘                    │                     EXITED
                               ▼
                           UNHEALTHY (health check failures)
                               │
                               ▼
                         auto-restart (if autorestart=true)
State Description
STOPPED
Process is not running (initial or manually stopped)
STARTING
Process has been spawned, waiting for confirmation
RUNNING
Process is running and healthy
BACKOFF
Process exited too quickly, waiting before retry
STOPPING
Stop signal sent, waiting for process to exit
EXITED
Process has exited (normally or abnormally)
FATAL
Process failed to start after exhausting retries
UNHEALTHY
Process is running but health checks are failing
┌─────────────────────────────────────────────────┐
│                   supervice                      │
│                                                  │
│  ┌──────────┐    ┌───────────┐    ┌──────────┐  │
│  │  Config   │───▶│ Supervisor │───▶│ Process  │  │
│  │  Parser   │    │   (core)   │    │ Manager  │  │
│  └──────────┘    └─────┬─────┘    └────┬─────┘  │
│                        │               │         │
│                   ┌────▼────┐    ┌─────▼─────┐  │
│                   │   RPC    │    │  EventBus  │  │
│                   │  Server  │    │  (pub/sub) │  │
│                   └────┬────┘    └───────────┘  │
│                        │                         │
└────────────────────────┼─────────────────────────┘
                         │ Unix Socket
                    ┌────▼────┐
                    │supervice│
                    │  ctl    │
                    └─────────┘
Platform Status
Linux First-class — full feature set, including pdeathsig via prctl(2) (direct child only)
FreeBSD Supported (15.x, 13.x) — all features, including pdeathsig via procctl(2) (direct child only); see the FreeBSD notes below
macOS Supported for supervision, without — no kernel equivalent exists; supervice logs a warning if you request it. Children survive an abrupt supervisor killpdeathsig

Field notes from the first production FreeBSD deployment live in PORTABILITY-FREEBSD.md.

Run the supervisor itself as root from rc.d

with no ${name}_user

and no daemon -u

— FreeBSD's rc.subr

wraps the whole command in su -m

when ${name}_user

is set, so combining it with daemon -u <user>

runs setuid

twice and fails with EPERM

(and daemon -f

swallows the error). Let supervice drop privileges per program with its user =

directive instead.

A worked /usr/local/etc/rc.d/supervice

unit (adapted from production):

#!/bin/sh

. /etc/rc.subr

name="supervice"
rcvar="supervice_enable"

load_rc_config $name

command="/usr/local/bin/supervice"
command_args="-c /usr/local/etc/supervice.ini"

run_rc_command "$1"
; /usr/local/etc/supervice.ini
[supervice]
logfile = /var/log/supervice/supervice.log
; daemon(8) is NOT used with -p here; let supervice own its pidfile.
; If an outer supervisor already owns it, use: pidfile = none
pidfile  = /var/run/supervice/supervice.pid
socket   = /var/run/supervice/supervice.sock

[program:api]
command = /usr/local/bin/myapp
directory = /usr/local/myapp
user = myapp
startsecs = 3
startretries = 3
stopsignal = TERM
stopwaitsecs = 10
healthcheck_type = tcp
healthcheck_port = 8080

Notes for FreeBSD operators:

  • If something else (e.g. daemon(8) -p

) already writes the pidfile, setpidfile = none

daemon(8)

writes it as root before dropping privileges, so a second writer fails withEPERM

/EACCES

. - If you run supervice under daemon(8)

, start it with(restart on death) and-r

(pidfile holds daemon's-P

ownpid, not the child's): pointing the pidfile at the child meansservice stop

kills supervice and daemon immediately restarts it. - The pidfile and socket parent directories must exist and be writable before supervice

starts; config load now fails with a clear message otherwise. - Export HOME

if your program reads client certificates from$HOME/.postgresql

or similar (asyncpg does; a wrapper script can set it).

macOS has no kernel pdeathsig equivalent. pdeathsig = true

is accepted but inactive, and supervice logs one warning at config load naming the affected programs.

What this means in practice. There are two separate guarantees, and macOS has one of them:

Linux / FreeBSD macOS
Child dies when the supervisor is killed
yes (pdeathsig , direct child only)
no
Orphan is cleaned up at the next start
yes (reconcile )
yes (reconcile )

So after an abrupt kill (SIGKILL

, OOM, panic) the children keep running until you start supervice again. That window is real and unbounded — if the crash happens at 02:00 and nobody restarts until morning, a queue consumer keeps consuming for those hours. There is no kernel mechanism on macOS to close it; the honest mitigation is to make restarts fast and automatic (a launchd

KeepAlive

job), not to assume the gap is small.

What macOS does get is that the restart no longer makes it worse. Reconciliation identifies the orphans and kills them before spawning replacements, so a crash-restart cycle does not accumulate duplicate workers. Measured on Darwin arm64: without it, four orphans became five across a restart; with it, the count returns to one.

This replaces the pkill -u ... -f

launcher snippet previously recommended here, which matched on a command string rather than on identity and would kill any process whose command line merely looked similar. If you still use a launcher, note the exec

— without it the supervisor is a child of the script, adding a generation. The same rule applies to your own program wrappers; see pdeathsig scope.

Testing orphan behaviour on macOS: use a silent child. A program with a stdout_logfile

is reaped by SIGPIPE

when the supervisor dies — by accident, not by pdeathsig — so the obvious test passes while the guarantee is absent. python3 tests/orphan_harness.py

does this correctly and keeps the false-positive case beside it.

Full documentation is available at supervice.readthedocs.io.

python3 -m pytest tests/ -v

mypy --strict supervice/

ruff check supervice/

ruff format supervice/

pip install -e ".[docs]"
cd docs && make html

Apache License 2.0 — see LICENSE for details.

── more in #developer-tools 4 stories · sorted by recency
── more on @supervice 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-supervice-pr…] indexed:0 read:9min 2026-08-15 ·