spoolctl
A local job queue built for operators that die.
uv tool install spoolctlspoolctl is a local job queue for shell commands. It needs no daemon, no broker, and no server. One SQLite file coordinates everything. If a worker dies in the middle of a job, another worker picks the job up and runs it again.
Why it exists
Most local queue tools expect a person to watch them. With pueue, task-spooler, or nq, a job whose process is killed does not run again, and a failed job is not retried. The person at the terminal does the recovery.
Queues with automatic retry, such as Celery, RQ, and huey, are libraries. Jobs are functions in your application, and you need a broker or an application runtime.
Today, much local shell work comes from processes that cannot watch: coding agents, scripts, and unattended pipelines. They get killed mid-job. They run at the same time without coordination. spoolctl gives their shell commands the reliability of a real job queue, with less setup than either kind of tool.
What you get
- Crash recovery. If a worker is killed, another worker reclaims its job. It reclaims the job only after it confirms that the first worker is dead, so a job never runs on two workers at once.
- Retries and a dead-letter state. Failed jobs retry with exponential
backoff. When the retry budget is spent, the job moves to
dead, where you can inspect it and requeue it. - Timeouts that kill the whole job. A timeout stops the job’s full process group, not only its shell.
- Lanes and scheduling. Delay jobs, set priorities, and use named queues with slot limits to protect a scarce resource such as a GPU.
- Output you can read later. spoolctl keeps the stdout and stderr of every attempt. Any process can read them, not only the one that submitted the job.
- No dependencies. It uses only the Python standard library. You can build it as a single file and copy it into a sandbox.
Quick example
spoolctl add --timeout 600 -- python fetch.py --all
spoolctl add --after 5m --queue gpu -- python train.py
spoolctl work --drain
spoolctl work --queue gpu --slots 1
spoolctl status --json
spoolctl feedback 1 --json
Start as many workers as you like, at any time. They need no coordination.
spoolctl feedback returns one verdict per job: done or not, succeeded or
not, why, and what to do next.
Built for agents
Every command has a --json output with a stable shape and fixed error codes.
spoolctl brief prints a short usage guide for an agent.
spoolctl capabilities and spoolctl schema publish the contract.
spoolctl doctor checks whether the queue is ready without changing it.
Honest limits
- At-least-once, not exactly-once. If a worker dies after a command finished but before spoolctl recorded the result, the command runs again. Make jobs idempotent.
- A hung worker is not reaped. A worker that is alive but stuck keeps its job until you kill it. This is the cost of never running a job twice at once.
- One machine only. It needs a local filesystem. NFS is not supported.
- macOS and Linux only.
If you watch your queue yourself, pueue is a good choice. spoolctl is for work that must survive when nobody is watching.
Works with evalctl
evalctl can send its runner commands to spoolctl with
evalctl run --queue spoolctl. See
one command, three tools for a full example.
Install
spoolctl needs Python 3.10 or later. It is pre-1.0 and licensed under Apache 2.0.
uv tool install spoolctl
You can also use pip install spoolctl. See the
spoolctl documentation for guarantees,
configuration, and the full command reference.