Skip to main content

Runspool

CI License: MIT Python 3.11+

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, approval for steps with side effects, and JSON output for humans, scripts, and AI agents.

It is built from plugins on a small kernel: the store, the step registry, the task lifecycle, even the CLI's extra commands are plugins, and your own steps, workflows and commands plug in the same way.

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.
  • Careful — a step marked as having side effects (publish, upload, send) waits for you to approve it, and never runs unapproved.
  • Composable — workflows are ordered lists of steps; add steps, workflows and commands as plugins.
  • Scriptable — --json on 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

Recommended for CLI use, with uv:

uv tool install runspool

If you don't have uv yet:

curl -LsSf https://astral.sh/uv/install.sh | sh

Then check the command:

runspool --help

Alternatively, install with pip:

pip install runspool

Or from source (for development):

git clone https://github.com/ethan-sun-dev/runspool
cd runspool
uv sync

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.

See the result (20 seconds, nothing to install)

Don't want to run it? Real, committed output of the quickstart lives in sample-output/. Here's what runspool inspect 1 --json returns after the invoice above completes — a single call that gives a script or AI agent the whole picture:

{
  "id": 1,
  "name": "invoice",
  "status": "completed",
  "current_step": "archive",
  "step_runs": [
    { "step": "ingest_file",        "status": "ok", "duration_ms": 1 },
    { "step": "classify_text",      "status": "ok", "duration_ms": 0 },
    { "step": "normalize_markdown", "status": "ok", "duration_ms": 0 },
    { "step": "summarize_text",     "status": "ok", "duration_ms": 0 },
    { "step": "archive",            "status": "ok", "duration_ms": 0 }
  ],
  "artifacts": [
    "ready/1/classification.json", "ready/1/metadata.json",
    "ready/1/normalized.md",       "ready/1/summary.md", "..."
  ],
  "available_actions": [],
  "suggested_next_action": "Task is complete; no action needed."
}

And the workflow turned a raw invoice.txt into structured artifacts — e.g. ready/1/classification.json:

{ "category": "invoice", "confidence": 1.0,
  "matched_keywords": ["invoice", "amount due", "subtotal", "total", "payment terms"] }

The full snapshot, task list, and every produced file are in sample-output/. Note step_runs (per-step timing — failures are attributable to a step, not just the task) and available_actions / suggested_next_action (the engine tells an agent what it can and should do next). See docs/design-decisions.md for why it's shaped this way.

What it looks like

flowchart LR
    CLI[runspool CLI] -->|add / run / daemon| ENG
    AGENT[AI agent or script] -->|--json| CLI
    subgraph ENG[Engine: core plugins on a small kernel]
        COORD[Coordinator] --> POOL[Worker pool]
        POOL --> RUN[Step runner + approval gate]
        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. A step with side effects waits in awaiting_approval until you runspool approve it. Long jobs run under the daemon; one-shot runs use run.

Task lifecycle

queued → running → (next step) queued → … → completed | partially_completed
                 ↘ queued, same step         (deferred; optionally until a delay passes — `wake` ends it)
                 ↘ failed ──(retry)──↗
                 ↘ manual_required          (retries exhausted; needs you)
                 ↘ awaiting_approval ──approve──→ queued   (side-effect step)
                                     ──reject───→ manual_required
   running → pause_pending → paused → (resume) queued
   non-terminal → terminated   (terminal states refuse further control)

partially_completed means every step ran but one reported it could only do part of its job. Pause and terminate always take effect at a step boundary, and terminate wins; see docs/concepts.md.

CLI

runspool init                     # create config + database
runspool add <input> -w <wf>      # queue a task (default workflow: local_file)
                                  #   --meta KEY=VALUE, --parent <id>, --name, --force
runspool run                      # advance all runnable tasks once (great for demos)
runspool daemon                   # run a resident loop (long-running automation)
runspool daemon-status            # report whether a daemon is running
runspool daemon-stop              # signal a running daemon to stop
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 approve <id>             # let a waiting side-effect step run (this attempt)
runspool reject <id> --reason ... # refuse it; the task needs attention
runspool wake <id>                # run a deferred task now instead of after its delay
runspool set-priority|set-retries|set-step <id> <value>
runspool workflows                # list workflows and their steps
runspool doctor                   # check the environment, plugins and credentials

Plugins add their own commands, shown by runspool -c <profile> --help — e.g. the official WeChat plugin adds runspool wechat preview and runspool wechat token.

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 steps loaded from config; demonstrates manual_required recovery.
creator-publishing-pipeline A content pipeline that builds a multi-platform draft package (never auto-publishes). Its steps come from a plugin package.

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 (optionally for a delay, without counting a failure), return degraded=True when they could only do part of their job, or raise any exception to fail and retry. A step whose effects leave your machine sets side_effect = True and runs only after you approve it. See docs/writing-steps.md.

To ship steps with their own config, default workflow, commands and doctor checks — installable with pip — package them as a plugin; see docs/plugins.md. The official runspool-wechat plugin (lay out Markdown for WeChat Official Accounts and save it as a draft, after approval) is a complete example.

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.

Claiming is atomic. A task is claimed with a single conditional UPDATE ... WHERE task_status = 'queued', and the caller checks rowcount to learn whether it won — never check-then-act. That makes claiming correct even when two processes race for the same task (e.g. a run invocation alongside a live daemon), so exactly one worker ever executes a given step. The state machine is the only place transitions are decided; steps return data and never touch the database. The reasoning behind these boundaries is in docs/design-decisions.md.

Privacy & safety

  • Local-first. All state lives under workspace_root on 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. A plugin that does (like runspool-wechat) refers to secrets by name only; values come from your environment or an owner-only credentials file, never from config, logs or errors.
  • Approval before side effects. A step that publishes, uploads or sends runs only after you approve that attempt; without an approval policy it is refused, never run.
  • Drafts, not auto-publish. Content examples and the WeChat plugin produce drafts; 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

  • runspool watch to follow a task's events live.
  • Optional structured log export (JSONL).
  • Notification plugins that tell you a task awaits approval — and let you approve or reject from the message.
  • runspool-wechat: themes, tables and image compression.

Contributing

Contributions are welcome — see CONTRIBUTING.md and the Code of Conduct.

uv sync
uv run ruff check .
uv run pytest

License

MIT.

Metadata

Release files for runspool 0.2.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 runspool 0.2.0
File Size Uploaded
runspool-0.2.0.tar.gz 189.2 kB Details

Built distribution (wheel)

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

Total release size: 293.6 kB

Release files / runspool-0.2.0.tar.gz

Download URL runspool-0.2.0.tar.gz
Size 189.2 kB
Tags Source
SHA-256 checksum
How to use checksums
62143bb5b5495003ff4407399784aac924831f8761dc69056e48815d0c369ab7
BLAKE2b-256 checksum
How to use checksums
40a0445bab7dd46eedfbfc5496da200000d5e0e0a6f0029fd6873ae07ee8e78e
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 Oct 4, 2026.

Transparency log

Release files / runspool-0.2.0-py3-none-any.whl

Download URL runspool-0.2.0-py3-none-any.whl
Size 104.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d7d5e91b24d59968baaec835f9d8c2ca81d3563b5439c514ccf4db97d605f6c8
BLAKE2b-256 checksum
How to use checksums
645440f6f7ce51d376f07877f0403f2ef4c8c64b9d8242be61c9b02214610a14
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 Oct 4, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.0

2 release files

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