A local-first CLI workflow engine for reliable personal automation.
Project description
Runspool
Local-first CLI workflows for reliable personal automation.
Runspool turns scripts, files, and manual checklists into resumable, observable workflows — with SQLite state, retries, logs, pause/resume controls, step plugins, and JSON output for humans, scripts, and AI agents.
It runs entirely on your machine. No hosted service, no account, no data leaving your laptop by default.
简体中文 README · Docs · Examples
Why Runspool
Personal automation usually starts as a shell script and slowly turns into a mess: when it dies halfway through, you don't know what ran; re-running redoes work; there's no history; and pausing or retrying means editing the script.
Runspool gives that automation a backbone:
- Resumable — every task is a row in SQLite; a crash or reboot loses nothing.
- Observable — every state change is an event; every step run is timed.
- Controllable — pause, resume, retry, terminate, reprioritize from the CLI.
- Composable — workflows are ordered lists of steps; add your own as plugins.
- Scriptable —
--jsonon every read command, built for shell and AI agents.
It is not an AI tool, and it is not a cloud workflow platform. It is a small, dependable engine for turning local scripts, files, and checklists into workflows you can trust.
Install
pip install runspool
Or from source:
git clone https://github.com/ethan-sun-dev/runspool
cd runspool
pip install -e ".[dev]"
Requires Python 3.11+. Core dependencies: Typer, Pydantic, PyYAML (SQLite is in the standard library).
Quickstart (about 3 minutes, no setup)
# 1. Create a config and database.
runspool init
# 2. Queue a task. The default `local_file` workflow uses only built-in steps.
echo "Invoice #42 Total amount due: 1320 Payment terms: net 30" > invoice.txt
runspool add ./invoice.txt
# 3. Advance every task to completion, once.
runspool run
# 4. Look at the result.
runspool status
runspool inspect 1
You'll see the task flow through five steps and land its artifacts under
workspace/ready/1/ (normalized Markdown, a summary, a classification, and
metadata). That's a complete workflow with persisted state, logs, and a step
timeline — and it ran with zero external dependencies.
What it looks like
flowchart LR
CLI[runspool CLI] -->|add / run / daemon| ENG
AGENT[AI agent or script] -->|--json| CLI
subgraph ENG[Engine]
COORD[Coordinator] --> POOL[Worker pool]
POOL --> RUN[Step runner]
RUN --> STEPS[Step registry\nbuilt-in + plugins]
end
ENG <--> DB[(SQLite\ntasks · events · step_runs)]
RUN --> FS[(Workspace\nartifacts)]
A task carries an input through a workflow — an ordered list of
steps. The coordinator claims queued tasks (respecting per-step
concurrency quotas), the worker pool runs each step, and the state
machine records every transition. Long jobs run under the daemon; one-shot
runs use run.
Task lifecycle
queued → running → (next step) queued → … → completed
↘ failed ──(retry)──↗
↘ manual_required (retries exhausted; needs you)
running → pause_pending → paused → (resume) queued
non-terminal → terminated (completed / terminated refuse further control)
CLI
runspool init # create config + database
runspool add <input> -w <wf> # queue a task (default workflow: local_file)
runspool run # advance all runnable tasks once (great for demos)
runspool daemon # run a resident loop (long-running automation)
runspool status [<id>] # list tasks, or show one in detail
runspool inspect <id> # agent-friendly snapshot + suggested next action
runspool logs <id> # event history for a task
runspool overview # counts by status
runspool pause|resume|retry|terminate <id>
runspool set-priority|set-retries|set-step <id> <value>
runspool workflows # list workflows and their steps
runspool doctor # check the local environment
Every read command supports --json:
runspool status --json
runspool inspect 1 --json
runspool logs 1 --json
runspool overview --json
runspool workflows --json
runspool doctor --json
See docs/cli.md for the full reference.
Built for AI agents and scripts
runspool inspect <id> --json returns exactly what an automated caller needs to
decide what to do next — current state, the last error, the artifacts produced,
the actions that are valid right now, and a plain-language suggestion:
{
"id": 1,
"status": "manual_required",
"workflow": "client_intel",
"current_step": "collect_sources",
"last_error": "FileNotFoundError: Missing required source(s): requirements.md",
"retry_count": 1,
"max_retries": 0,
"recent_events": [],
"artifacts": [],
"available_actions": ["retry", "set-step", "set-retries", "terminate"],
"suggested_next_action": "FileNotFoundError: Missing required source(s): requirements.md. Resolve the cause, then run `runspool retry 1`."
}
An agent can poll inspect --json, act on available_actions, fix the cause,
and call runspool retry 1 — no screen-scraping required. See
docs/agent-json-output.md.
Examples
Three runnable examples, each with its own README and sample data:
| Example | What it shows |
|---|---|
| local-file-pipeline | The quickstart. Built-in steps only; runs offline in minutes. |
| client-intel-brief | A real consulting workflow: sources → briefing package. Custom plugin steps; demonstrates manual_required recovery. |
| creator-publishing-pipeline | A content pipeline that builds a multi-platform draft package (never auto-publishes). |
Write a custom step
A step is a small class. It reads the task, does work, writes artifacts, and returns a result:
from runspool.engine.step import Step, StepContext, StepResult
class GreetStep(Step):
name = "greet"
def run(self, ctx: StepContext) -> StepResult:
ctx.heartbeat("working") # optional progress
name = ctx.task.get("name") or "world"
return StepResult(message=f"hello, {name}")
Load it from config and use it in a workflow:
plugin_paths: [steps] # directories added to sys.path (relative to this config)
steps:
greet:
import: "my_steps:GreetStep"
workflows:
hello:
steps: [greet, archive]
Steps can also raise StepDeferred to wait for a precondition (retry next tick
without counting a failure), or raise any exception to fail and retry. See
docs/writing-steps.md.
Concurrency
Runspool runs steps in a bounded thread pool. The daemon keeps the pool busy
across many ticks, so long steps don't block the queue, and a per-step
concurrency quota caps how many of a given step run at once. A crashed or
silent worker's task is reclaimed by heartbeat timeout. See
docs/concepts.md.
Privacy & safety
- Local-first. All state lives under
workspace_rooton your machine. There is no hosted service and nothing is uploaded by default. - No secrets required. The engine and built-in steps need no API keys.
- Drafts, not auto-publish. Content examples produce drafts and checklists; publishing is always a deliberate, manual step.
Non-goals
- Not a hosted/cloud workflow platform.
- Not a distributed scheduler or a replacement for heavyweight orchestrators.
- Not an AI product (though it is deliberately AI-agent-friendly).
- No web UI in scope for now — the CLI and JSON are the interface.
Roadmap
- Atomic multi-coordinator claiming (single-process is solid today).
runspool watchto follow a task's events live.- Optional structured log export (JSONL).
- A small library of community step plugins.
- Opt-in publish adapters for the creator example (draft submission only).
Contributing
Contributions are welcome — see CONTRIBUTING.md and the Code of Conduct.
pip install -e ".[dev]"
ruff check .
pytest
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file runspool-0.1.0.tar.gz.
File metadata
- Download URL: runspool-0.1.0.tar.gz
- Upload date:
- Size: 66.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
46d827f528c30c631cdba1bf0423e7ffe788b3de85531f438bb0ef5d44f2b3a4
|
|
| MD5 |
eda86ab17b73865cbc182a13c71828ba
|
|
| BLAKE2b-256 |
dcef7daab7ebc968af9fb98cbca994a5c6630ac0cc46aef365d98c2a1ef06342
|
Provenance
The following attestation bundles were made for runspool-0.1.0.tar.gz:
Publisher:
publish.yml on ethan-sun-dev/runspool
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
runspool-0.1.0.tar.gz -
Subject digest:
46d827f528c30c631cdba1bf0423e7ffe788b3de85531f438bb0ef5d44f2b3a4 - Sigstore transparency entry: 2004747991
- Sigstore integration time:
-
Permalink:
ethan-sun-dev/runspool@79051647808b83477c5fb8e01a675879bed2f35b -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ethan-sun-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@79051647808b83477c5fb8e01a675879bed2f35b -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file runspool-0.1.0-py3-none-any.whl.
File metadata
- Download URL: runspool-0.1.0-py3-none-any.whl
- Upload date:
- Size: 46.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a2b5a7dc02a59297affacfe13b7b91902e7356a37834c0e4e4adc830f0ac47b6
|
|
| MD5 |
e7edbf98b61ce223eca207fd034b8981
|
|
| BLAKE2b-256 |
7ba8a275ecfea5230cca694bcbdd67f6ea6dcda47043d5c3cbc80a97cdafaca0
|
Provenance
The following attestation bundles were made for runspool-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on ethan-sun-dev/runspool
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
runspool-0.1.0-py3-none-any.whl -
Subject digest:
a2b5a7dc02a59297affacfe13b7b91902e7356a37834c0e4e4adc830f0ac47b6 - Sigstore transparency entry: 2004748052
- Sigstore integration time:
-
Permalink:
ethan-sun-dev/runspool@79051647808b83477c5fb8e01a675879bed2f35b -
Branch / Tag:
refs/heads/main - Owner: https://github.com/ethan-sun-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@79051647808b83477c5fb8e01a675879bed2f35b -
Trigger Event:
workflow_dispatch
-
Statement type: