What Whooo Actually Is
Whooo is a lightweight open-source tool for scheduling and orchestrating recurring tasks across distributed environments. It's not a full-blown workflow engine like Airflow, and it's not a simple cron replacement either. It sits in that middle ground where you need something that can handle retries, dependencies between jobs, and a basic dashboard without spinning up a Kubernetes cluster. The project lives on GitHub under the name "whooo-scheduler," and the latest release as of mid-2026 is version 0.8.4. The download link is at github.com/whooo-scheduler/whooo. Installation is straightforward — pip install whoooo, or grab the Docker image from their registry. There's also a prebuilt binary for Linux and macOS if you don't want to deal with Python dependencies.
Setting Up Your First Whooo Instance
I spent about three weeks evaluating Whooo for a team that needed to replace a messy collection of crontabs and systemd timers. Here's how the initial setup goes, and where people typically trip up. First, create a config file. The default location is ~/.whooo/config.yaml. You need at minimum a scheduler section with a database URL and a workers section. Here's what a minimal config looks like: database: postgresql://user:pass@localhost/whooo_db
scheduler:
max_concurrent_jobs: 10
retry_policy: exponential
workers:
- name: default
queue: main
concurrency: 4
After that, run whoooo migrate to set up the schema, then whoooo serve to start the scheduler and the web UI on port 8080 by default. That's it for a basic install. The web UI is functional but not pretty. It shows job status, run history, and lets you manually trigger tasks. Don't expect fancy Gantt charts or drag-and-drop pipelines. If you need visual workflow editing, look elsewhere.
Get the Full Details

How Whooo Handles Job Dependencies
This is where Whooo earns its keep. Most scheduling tools handle single jobs fine. Whooo's dependency system lets you define that Job B runs only after Job A succeeds, with optional timeout and skip-on-failure logic. The dependency syntax uses a YAML DAG definition that gets parsed at startup. Here's a practical example. Say you have a data pipeline where you need to fetch raw data, transform it, and then load it into a warehouse. In Whooo, you define each step as a separate job and wire them together: jobs:
- id: fetch_data
command: python fetch.py
schedule: "0 2 * * *"
- id: transform_data
command: python transform.py
depends_on: fetch_data
on_failure: skip
- id: load_data
command: python load.py
depends_on: transform_data
The on_failure: skip directive on the transform step is important. If the fetch fails, the transform won't run, and the load will be skipped too. Without that directive, Whooo would still attempt the downstream jobs and you'd get a cascade of meaningless errors in your logs.
A Real Problem I Hit and How I Worked Around It
About six months into running Whooo in production, I hit a specific edge case that the documentation doesn't cover. When you have jobs with circular dependencies — which shouldn't happen but does when you're refactoring a DAG — Whooo doesn't error out at startup. It silently accepts the config, then deadlocks at runtime. All dependent jobs stall indefinitely, and the web UI shows them as "pending" with no indication of why. The workaround I ended up using is a pre-flight validation script that runs before starting the scheduler. It parses your config YAML, builds the dependency graph in Python, and checks for cycles using a simple depth-first search. If it finds one, it exits with code 1 and prints the cycle. I run this script as part of my deployment pipeline, so bad configs never make it to production. Here's the script in rough form:

import yaml
from collections import defaultdict
def has_cycle(graph):
visited = set()
rec_stack = set()
def dfs(node):
visited.add(node)
rec_stack.add(node)
for neighbor in graph.get(node, []):
if neighbor not in visited:
if dfs(neighbor):
return True
elif neighbor in rec_stack:
return True
rec_stack.remove(node)
return False
for node in graph:
if node not in visited:
if dfs(node):
return True
return False
with open('whooo_config.yaml') as f:
config = yaml.safe_load(f)
graph = defaultdict(list)
for job in config['jobs']:
if 'depends_on' in job:
deps = job['depends_on'] if isinstance(job['depends_on'], list) else [job['depends_on']]
for dep in deps:
graph[dep].append(job['id'])
if has_cycle(graph):
print("Cycle detected in dependency graph")
exit(1)
else:
print("Graph is valid") This catches the issue before it becomes a production incident. It took me about two days of debugging a stalled pipeline to realize what was happening, so I don't recommend skipping this step.
Where Whooo Falls Short
I need to be honest about the limitations. Whooo is not built for high-throughput event-driven architectures. If you're processing thousands of messages per second, this tool will choke. The single-threaded scheduler becomes a bottleneck around 50-100 concurrent jobs, and adding more workers doesn't help because they all queue against the same scheduler instance. The retry mechanism is also rudimentary. You get fixed-delay and exponential-backoff options, but there's no dead-letter queue. If a job fails after max retries, it's just marked as failed and stays in the database forever unless you manually clean it up. I've seen production databases grow to several gigabytes because failed jobs were never archived or deleted. Monitoring is another weak spot. The built-in web UI gives you basic status info, but there's no native integration with Prometheus, Grafana, or PagerDuty. You can scrape the metrics endpoint, but the documentation for that is sparse. If your team relies on alerting infrastructure, you'll need to build custom exporters or pair Whooo with something like Datadog's agent.
For teams that need any of those features, I'd recommend looking at Apache Airflow for complex DAGs, or Prefect if you want a more modern API-first approach. Both have steeper learning curves, but they handle the scaling and observability problems that Whooo simply doesn't address.

Who Whooo Is Actually For
Whooo fits a very specific niche. It's for small to mid-size teams — maybe 2 to 10 engineers — who have a handful of recurring jobs that need coordination but don't justify the overhead of a full workflow orchestration platform. Think: nightly ETL jobs, periodic cleanup tasks, scheduled report generation, API health checks that need to run in sequence. If your use case matches that description, Whooo will save you time. If you're already managing something with Airflow or a custom cron-heavy setup, the migration is worth considering only if your current system is genuinely painful. Whooo isn't a silver bullet, and it won't solve problems it wasn't designed for. But for its intended scope, it does the job without the bloat. The project is actively maintained with releases every few months. The GitHub repo has open issues and pull requests, and the maintainers respond to questions within a few days. That's about as good as it gets for a project this size.