Skip to main content

TinySupervisor

Define workflows in Python, run anything you want. Tiny to fit in a container, but with observability built in.

A Python library inspired by Supervisor and workflow APIs like Airflow. It aims to be a simple way to define workflows to be easily included into a single container.

Features

  • Task types: Job (one-shot), CronJob (recurring), RecurrentJob (dependency-driven), Service (long-running)
  • DAG dependencies: wait_for="start" / wait_for="completed"
  • Built-in observability: /health, /state, /metrics HTTP endpoints
  • Per-task log capture: each run's output is written to a log file, optionally streamed to the console
  • Zero config: single container friendly

Install

Requires Python 3.14+.

(soon, not published yet)

uv add tinysupervisor
# or
pip install tinysupervisor

Quick start

A minimal workflow with two jobs — the second waits for the first to complete:

# runnable: quickstart
import os
import sys

from tinysupervisor import Job, init_supervisor


def main() -> int:
    supervisor = init_supervisor()
    supervisor.set_http_port(int(os.environ.get("TINYSUPERVISOR_HTTP_PORT", "8081")))
    supervisor.set_heartbeat_interval("100ms")

    supervisor.register(Job(name="prepare", command="echo preparing"))
    supervisor.register(
        Job(
            name="build",
            command="echo building",
            depends=["prepare"],
            wait_for="completed",
        )
    )

    return supervisor.start()


if __name__ == "__main__":
    sys.exit(main())

The supervisor starts an HTTP server (default port 8081), runs the tasks in dependency order, and exits automatically once all tasks complete:

  • prepare runs first
  • build waits for prepare to complete, then runs
  • The supervisor exits with code 0 (or 1 if any task ends FATAL)

Tasks

Job

A one-shot task that runs once and completes (or fails):

from tinysupervisor import Job

Job(name="backup", command="tar -czf backup.tar.gz /data")

Jobs can also run a Python function via executable:

Job(name="greet", executable=lambda: print("hello"))

CronJob

A task that runs on a recurring interval, optionally bounded by run_until:

# runnable: cron
import os
import sys

from tinysupervisor import Job, CronJob, init_supervisor


def main() -> int:
    supervisor = init_supervisor()
    supervisor.set_http_port(int(os.environ.get("TINYSUPERVISOR_HTTP_PORT", "8081")))
    supervisor.set_heartbeat_interval("100ms")

    supervisor.register(Job(name="init", command="echo init"))
    supervisor.register(
        CronJob(
            name="heartbeat",
            interval="1s",
            run_until=3,
            command="echo beat",
            depends=["init"],
            wait_for="completed",
        )
    )

    return supervisor.start()


if __name__ == "__main__":
    sys.exit(main())

run_until can be an integer (max number of runs) or a duration string like "30s".

Service

A long-running task that the supervisor keeps alive until stopped:

Service(name="server", command="python3 -m http.server 8080")

Services stay running until the supervisor receives SIGINT or SIGTERM. When a service exits unexpectedly the supervisor can optionally restart it (autorestart=True).

Dependencies

Tasks can depend on others. The wait_for parameter controls when the dependent task starts:

Value Meaning
start Start as soon as the dependency has started
completed Wait until the dependency completes (default)
# runnable: run_after
import os
import sys

from tinysupervisor import Job, CronJob, RecurrentJob, init_supervisor


def main() -> int:
    supervisor = init_supervisor()
    supervisor.set_http_port(int(os.environ.get("TINYSUPERVISOR_HTTP_PORT", "8081")))
    supervisor.set_heartbeat_interval("100ms")

    supervisor.register(Job(name="seed", command="echo seed"))
    supervisor.register(
        CronJob(
            name="tick",
            interval="1s",
            run_until=2,
            command="echo tick",
            depends=["seed"],
            wait_for="completed",
        )
    )
    supervisor.register(
        RecurrentJob(
            name="after_tick",
            command="echo after tick",
            depends=["tick"],
        )
    )

    return supervisor.start()


if __name__ == "__main__":
    sys.exit(main())

RecurrentJob runs every time its dependencies produce a new event (by default after each completed run), and completes once all of its dependencies complete. Set trigger_mode="after_start" to run whenever a dependency starts instead.

You can also chain tasks automatically with auto_dependency_mode:

supervisor.auto_dependency_mode(mode="register_order", wait_for="start")
# Each task registered after this call depends on the previous one (start mode).

Environment variables

Pass environment variables to a task with the env parameter. The child process inherits the parent environment with the given values overlaid:

# runnable: env
import os
import sys

from tinysupervisor import Job, init_supervisor


def main() -> int:
    supervisor = init_supervisor()
    supervisor.set_http_port(int(os.environ.get("TINYSUPERVISOR_HTTP_PORT", "8081")))
    supervisor.set_heartbeat_interval("100ms")

    supervisor.register(
        Job(
            name="greet",
            command="echo Hello $GREETING",
            env={"GREETING": "World"},
        )
    )

    return supervisor.start()


if __name__ == "__main__":
    sys.exit(main())

Works with both shell commands and Python callables:

Job(name="work", executable=my_func, env={"API_KEY": "abc123"})

Logs

Every task run is captured to a log file, written as tasks execute in the background so they never block the supervisor. Each process start gets its own file at:

<log_folder>/<task_name>/<run_number>.log

run_number counts process starts, so retries and restarts each get a new file. The default log folder is /tmp/tinysup/log and can be configured with the log_folder option:

from tinysupervisor import init_supervisor

supervisor = init_supervisor(log_folder="/var/log/tinysup")

The task output is mirrored to the main console (in addition to the files). This can be toggled with the stream_logs option. Each line is prefixed with the task name:

supervisor = init_supervisor(stream_logs=True)  # default

The flag can also be overridden per task:

Job(name="too_verbose", command="echo shout", stream_logs=False)
Job(name="quiet", command="echo whisper")  # follows the global default

Both shell commands (stdout and stderr) and Python callables (print to stdout/stderr) are captured. Callable output is captured per-thread, so concurrent callable tasks will have overlapping logs in the console, where each line carries its task prefix. But they also write to their own log files.

Observability

The supervisor exposes an HTTP server with three endpoints:

curl http://localhost:8081/health   # 200 OK or 400 Unhealthy
curl http://localhost:8081/state    # full JSON state of all tasks
curl http://localhost:8081/metrics  # Prometheus metrics

The /state endpoint returns JSON with each task's current state, desired state, run_count, and completed flag, plus the dependency graph:

{
  "processes": {
    "prepare": {"current": "completed", "desired": "completed", "run_count": 1, "completed": true},
    "build":   {"current": "completed", "desired": "completed", "run_count": 1, "completed": true}
  },
  "graph": {
    "nodes": ["prepare", "build"],
    "edges": [{"from": "prepare", "to": "build", "mode": "completed"}]
  }
}

Exit codes

The supervisor's start() returns an exit code:

  • 0 — all tasks completed successfully
  • 1 — at least one task ended in FATAL

By default the supervisor exits automatically when all tasks reach a terminal state (COMPLETED or FATAL). To keep it running (e.g. for long-running services), pass keep_running=True:

supervisor = init_supervisor(keep_running=True)

Verbosity

Control log output via the Verbosity enum or the TINYSUPERVISOR_VERBOSITY environment variable:

Level Behaviour
silent no output
info task start and finish (default)
debug every state transition
from tinysupervisor import Verbosity

supervisor = init_supervisor(verbosity=Verbosity.DEBUG)

More examples

See the examples/ directory for complete runnable scripts:

  • simple_dag.py — cron heartbeat with downstream jobs
  • simple_server.py — file-generation cron + HTTP server

Process states

Tasks transition through these states during their lifecycle:

[starting] <-> [backoff]
 |   |  | \        v
 |   |  |  \___[FATAL]
 |   |  |
 |   | [EXITED]
 |   v   /\
 |  [running] <--> [waiting]
 |
  \> [stopping] -> [COMPLETED]

For more details see docs/.

Metadata

Release files for tinysupervisor 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for tinysupervisor 0.1.0
File Size Uploaded Provenance Actions
tinysupervisor-0.1.0.tar.gz 21.3 kB With provenance Details for tinysupervisor-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for tinysupervisor 0.1.0
File Interpreter ABI Platform Actions
tinysupervisor-0.1.0-py3-none-any.whl Python 3 none any Details for tinysupervisor-0.1.0-py3-none-any.whl

Total release size: 49.1 kB

Release files / tinysupervisor-0.1.0.tar.gz

Download URL tinysupervisor-0.1.0.tar.gz
Size 21.3 kB
Tags Source
SHA-256 checksum
How to use checksums
d3d8f81111a7008bab943615bff1c9cab1487793b056278c281a6bc9e2e20607
BLAKE2b-256 checksum
How to use checksums
142580f19fa4938a7b8bc0778cf0bb7de68a2a0d0f3ada2cf0be5a3d48a6251b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Release files / tinysupervisor-0.1.0-py3-none-any.whl

Download URL tinysupervisor-0.1.0-py3-none-any.whl
Size 27.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
eac557ebd4a85958111ab49d5423848f408ddfb65f3fefa0a73e8d6c06172633
BLAKE2b-256 checksum
How to use checksums
9ae656e098098d7288c7d387fcdf792cb9bf0eb934b2fd813149df82c768dad6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Security for tinysupervisor 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for tinysupervisor-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page