Embedded durable workflow runtime: a deterministic Rust/WebAssembly kernel with replaceable transactional storage.
See the codeRunspore is an embedded durable workflow runtime designed around a deterministic Rust/WebAssembly graph kernel and replaceable transactional storage.
Status: semantics 0.2. One binary, spore, with a SQLite store and a Rust host that runs the kernel through Wasmtime. A workflow is a folder holding workflow.dot and settings.json; spore build seals it into a workflow artifact. A step can be done by an actor outside the worker or by a job that outlives it, a step that gives up pauses the run for a person, and a run can be cancelled. Node and Bun are verified at kernel level only; there is no JS host. The supported setup is one machine, one OS user and one database per checkout, with no authorization beyond file permissions. Not implemented yet: timers, fork and join, the workflow node type, compensation, reconcilable effects, a YAML frontend, PostgreSQL, a JS host or SDK, browser and Cloudflare profiles, secrets, telemetry and the rest listed in spec/README.md. Website: runspore.com.
event + state → deterministic kernel → new state + commands
Hosts atomically persist the transition and dispatch commands as activities. Non-deterministic outcomes are recorded; ambiguous external effects require idempotency or reconciliation.
On macOS or Linux (arm64 or x86_64):
curl -fsSL https://raw.githubusercontent.com/Web-tree/runspore/main/install.sh | sh
install.sh downloads the newest release, checks that SHA256SUMS is signed by the release key below and that the archive matches it, checks the build-provenance attestation when gh is signed in, and puts spore in ~/.local/bin. The release key:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGy3mjvtp10UZgB9Y8u3KNBOh66DHPo3bdbZEszh9b1k runspore-release
When the line reads PENDING, no key has been made yet: there is no signed release and install.sh refuses to run, so build from source. Assets, formats and how to check them by hand: spec/release.md. To build from source:
cargo install --git https://github.com/Web-tree/runspore --locked runspore-cli
release-please keeps a release PR open that bumps the version and adds the merged changes to CHANGELOG.md. Merging it cuts the release: release-please tags v<version>, and the same workflow run builds, signs, attests and uploads the assets. Every push to main builds the four archives too, without publishing them. PR titles are conventional commits (feat(R4): …, fix: …), since release-please reads them after the squash merge.
Before the first release, a repository admin runs scripts/new-release-key.sh once and merges the key it pins; the release PR cannot be merged before that. Once the 0.2 build runs a spore whose ticket workflow writes conventional PR titles, set the repository variable PR_TITLE_STRICT to true so a wrong title fails its check instead of only warning. Details: spec/release.md §8.
From the repository root, build the binary and work in a scratch directory with copies of the example workflow folders:
cargo build --release -p runspore-cli
export PATH="$PWD/target/release:$PATH"
demo=$(mktemp -d) && cp -R examples/hello examples/review-loop examples/unsafe-deploy "$demo" && cd "$demo"
Run a workflow of two steps to completion. spore reads the folder, builds its artifact in memory and records it with the run. The database is ./runspore.db. Run IDs are random, so yours differ.
spore run hello --key hello-1
2026-10-16T04:32:20Z hello-1 step greet started, attempt 1
2026-10-16T04:32:22Z hello-1 greet returned ok -> shout
2026-10-16T04:32:22Z hello-1 step shout started, attempt 1
2026-10-16T04:32:25Z hello-1 shout returned ok -> done
2026-10-16T04:32:25Z hello-1 completed at done
run run_cc275ce70f71ce5c2325b8b74b91f77e
start key hello-1
status completed
revision 3
package sha256:73dcd360fa0dd84ab10b63262bb15267cab451f627f397dff1b31536ea71511a
artifact sha256:5eac69a8d6d15f5634a5cf42510a25f64e42f43089288f6b83cc1bad86b8f057
end done, from shout on ok
result {"output":{"message":"hello"}}
Kill the process during the second step and run the same key again. The first step does not run again; the second is retried after its lease (10 seconds by default) runs out.
spore run hello --key hello-3 & sleep 3; kill -KILL $!; wait $!
spore run hello --key hello-3
2026-10-16T04:33:19Z hello-3 step shout attempt 1 failed: attempt.expired: the attempt's lease ran out
2026-10-16T04:33:19Z hello-3 step shout started, attempt 2
2026-10-16T04:33:21Z hello-3 shout returned ok -> done
2026-10-16T04:33:21Z hello-3 completed at done
run run_0be544f12f0c92d34656ef907b87a55a
start key hello-3
status completed
revision 4
...
A run that waits for a signal exits 3 and prints the commands that answer it. Deliver the signal from any process, then spore continue:
spore run review-loop --key review-1; echo "exit $?"
spore signal run_04f51c4db6397978f2958d917e176b95 approval --outcome approved --id approve-1
spore continue run_04f51c4db6397978f2958d917e176b95
...
run run_04f51c4db6397978f2958d917e176b95
start key review-1
status waiting
...
waiting for signal approval since 2026-10-16T04:33:26Z, for 0s
outcomes approved
rejected
answer spore signal run_04f51c4db6397978f2958d917e176b95 approval --outcome approved
spore signal run_04f51c4db6397978f2958d917e176b95 approval --outcome rejected
exit 3
signal approval applied (message ID approve-1)
2026-10-16T04:33:32Z review-1 approve returned approved -> done
2026-10-16T04:33:32Z review-1 completed at done
run run_04f51c4db6397978f2958d917e176b95 completed at done
An unsafe step interrupted after it started is never repeated on its own. The run parks in needs-intervention (exit 4) until an operator resolves it:
spore run unsafe-deploy --key deploy-1 & sleep 1; kill -KILL $!; wait $!
spore run unsafe-deploy --key deploy-1; echo "exit $?"
spore resolve run_e9b5a9c8589b644e0ddb6e1238f1cc9c --complete ok --id fix-1
spore continue run_e9b5a9c8589b644e0ddb6e1238f1cc9c
cat deploy.log
2026-10-16T04:33:48Z deploy-1 step deploy attempt 1 failed: attempt.expired: the attempt's lease ran out
2026-10-16T04:33:48Z deploy-1 needs intervention at deploy: the outcome of attempt 1 is unknown
...
needs a person: step deploy lost its last attempt's lease with no result since 2026-10-16T04:33:48Z, for 0s
resolve spore resolve run_e9b5a9c8589b644e0ddb6e1238f1cc9c --retry
spore resolve run_e9b5a9c8589b644e0ddb6e1238f1cc9c --complete ok
spore resolve run_e9b5a9c8589b644e0ddb6e1238f1cc9c --fail <message>
exit 4
resolution applied (request ID fix-1)
2026-10-16T04:33:56Z deploy-1 deploy returned ok -> done
2026-10-16T04:33:56Z deploy-1 completed at done
run run_e9b5a9c8589b644e0ddb6e1238f1cc9c completed at done
deployed inv_d05e1c780633ee634db3bb6504984c13
A run you no longer want is cancelled; its history stays, and a step in progress is stopped:
spore start review-loop --key review-2
spore cancel run_1cfdd0c834eff27d0dc75ec2bb686f4b --reason "not needed"
created run run_1cfdd0c834eff27d0dc75ec2bb686f4b (start key review-2)
run run_1cfdd0c834eff27d0dc75ec2bb686f4b cancelled (request ID cancel_74de1eca15fc8496)
Inside a repository, spore init makes the project folder .spore/, which holds its workflows and its run database, and every spore call from any subfolder uses that database. Commands, exit codes and JSON output: spec/cli.md.
spore worker prints each change of each run, spore list shows the runs as a tree, spore status says what a run waits for and how to move it on, and onIntervention sends a notification when a run stops for a person or fails.spore and claude.Read the architecture index and delivery plan. docs/ is the target architecture: Rust, Node, Bun, browser and Cloudflare profiles; typed graphs; effects; SQLite and PostgreSQL adapters; security; OpenTelemetry. spec/ is what spore does today, and wins where the two differ. The vocabulary is in CONTEXT.md. The Claude Code integration is the ml-workflow plugin in the max-skills repository, specified there.
The contracts directory includes the reducer and telemetry WIT drafts, native async store interface, example activity binding, and 22 conformance scenario definitions. Validation status records what has and has not been checked.
conformance/js/check.sh.spore binary, aborted at each of 19 failpoints and killed at 40 points through a run, recovers: no completed step runs again, and an interrupted unsafe step parks the run until an operator resolves it. Run cargo test -p runspore-cli.Not yet done: a soak test, a security review, any target beyond one host with one SQLite file.
This is a general workflow substrate developed under WebTree. The skills workflow epic is an optional integration profile rather than the runtime's governing format.
MIT. See LICENSE. Third-party dependencies keep their own licences; all current Rust dependencies are permissive (MIT, Apache-2.0, BSD, Zlib, Unlicense).
Embedded durable workflow runtime: a deterministic Rust/WebAssembly kernel with replaceable transactional storage.
See the codeRunspore is an embedded durable workflow runtime designed around a deterministic Rust/WebAssembly graph kernel and replaceable transactional storage.
Status: semantics 0.2. One binary, spore, with a SQLite store and a Rust host that runs the kernel through Wasmtime. A workflow is a folder holding workflow.dot and settings.json; spore build seals it into a workflow artifact. A step can be done by an actor outside the worker or by a job that outlives it, a step that gives up pauses the run for a person, and a run can be cancelled. Node and Bun are verified at kernel level only; there is no JS host. The supported setup is one machine, one OS user and one database per checkout, with no authorization beyond file permissions. Not implemented yet: timers, fork and join, the workflow node type, compensation, reconcilable effects, a YAML frontend, PostgreSQL, a JS host or SDK, browser and Cloudflare profiles, secrets, telemetry and the rest listed in spec/README.md. Website: runspore.com.
event + state → deterministic kernel → new state + commands
Hosts atomically persist the transition and dispatch commands as activities. Non-deterministic outcomes are recorded; ambiguous external effects require idempotency or reconciliation.
On macOS or Linux (arm64 or x86_64):
curl -fsSL https://raw.githubusercontent.com/Web-tree/runspore/main/install.sh | sh
install.sh downloads the newest release, checks that SHA256SUMS is signed by the release key below and that the archive matches it, checks the build-provenance attestation when gh is signed in, and puts spore in ~/.local/bin. The release key:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGy3mjvtp10UZgB9Y8u3KNBOh66DHPo3bdbZEszh9b1k runspore-release
When the line reads PENDING, no key has been made yet: there is no signed release and install.sh refuses to run, so build from source. Assets, formats and how to check them by hand: spec/release.md. To build from source:
cargo install --git https://github.com/Web-tree/runspore --locked runspore-cli
release-please keeps a release PR open that bumps the version and adds the merged changes to CHANGELOG.md. Merging it cuts the release: release-please tags v<version>, and the same workflow run builds, signs, attests and uploads the assets. Every push to main builds the four archives too, without publishing them. PR titles are conventional commits (feat(R4): …, fix: …), since release-please reads them after the squash merge.
Before the first release, a repository admin runs scripts/new-release-key.sh once and merges the key it pins; the release PR cannot be merged before that. Once the 0.2 build runs a spore whose ticket workflow writes conventional PR titles, set the repository variable PR_TITLE_STRICT to true so a wrong title fails its check instead of only warning. Details: spec/release.md §8.
From the repository root, build the binary and work in a scratch directory with copies of the example workflow folders:
cargo build --release -p runspore-cli
export PATH="$PWD/target/release:$PATH"
demo=$(mktemp -d) && cp -R examples/hello examples/review-loop examples/unsafe-deploy "$demo" && cd "$demo"
Run a workflow of two steps to completion. spore reads the folder, builds its artifact in memory and records it with the run. The database is ./runspore.db. Run IDs are random, so yours differ.
spore run hello --key hello-1
2026-10-16T04:32:20Z hello-1 step greet started, attempt 1
2026-10-16T04:32:22Z hello-1 greet returned ok -> shout
2026-10-16T04:32:22Z hello-1 step shout started, attempt 1
2026-10-16T04:32:25Z hello-1 shout returned ok -> done
2026-10-16T04:32:25Z hello-1 completed at done
run run_cc275ce70f71ce5c2325b8b74b91f77e
start key hello-1
status completed
revision 3
package sha256:73dcd360fa0dd84ab10b63262bb15267cab451f627f397dff1b31536ea71511a
artifact sha256:5eac69a8d6d15f5634a5cf42510a25f64e42f43089288f6b83cc1bad86b8f057
end done, from shout on ok
result {"output":{"message":"hello"}}
Kill the process during the second step and run the same key again. The first step does not run again; the second is retried after its lease (10 seconds by default) runs out.
spore run hello --key hello-3 & sleep 3; kill -KILL $!; wait $!
spore run hello --key hello-3
2026-10-16T04:33:19Z hello-3 step shout attempt 1 failed: attempt.expired: the attempt's lease ran out
2026-10-16T04:33:19Z hello-3 step shout started, attempt 2
2026-10-16T04:33:21Z hello-3 shout returned ok -> done
2026-10-16T04:33:21Z hello-3 completed at done
run run_0be544f12f0c92d34656ef907b87a55a
start key hello-3
status completed
revision 4
...
A run that waits for a signal exits 3 and prints the commands that answer it. Deliver the signal from any process, then spore continue:
spore run review-loop --key review-1; echo "exit $?"
spore signal run_04f51c4db6397978f2958d917e176b95 approval --outcome approved --id approve-1
spore continue run_04f51c4db6397978f2958d917e176b95
...
run run_04f51c4db6397978f2958d917e176b95
start key review-1
status waiting
...
waiting for signal approval since 2026-10-16T04:33:26Z, for 0s
outcomes approved
rejected
answer spore signal run_04f51c4db6397978f2958d917e176b95 approval --outcome approved
spore signal run_04f51c4db6397978f2958d917e176b95 approval --outcome rejected
exit 3
signal approval applied (message ID approve-1)
2026-10-16T04:33:32Z review-1 approve returned approved -> done
2026-10-16T04:33:32Z review-1 completed at done
run run_04f51c4db6397978f2958d917e176b95 completed at done
An unsafe step interrupted after it started is never repeated on its own. The run parks in needs-intervention (exit 4) until an operator resolves it:
spore run unsafe-deploy --key deploy-1 & sleep 1; kill -KILL $!; wait $!
spore run unsafe-deploy --key deploy-1; echo "exit $?"
spore resolve run_e9b5a9c8589b644e0ddb6e1238f1cc9c --complete ok --id fix-1
spore continue run_e9b5a9c8589b644e0ddb6e1238f1cc9c
cat deploy.log
2026-10-16T04:33:48Z deploy-1 step deploy attempt 1 failed: attempt.expired: the attempt's lease ran out
2026-10-16T04:33:48Z deploy-1 needs intervention at deploy: the outcome of attempt 1 is unknown
...
needs a person: step deploy lost its last attempt's lease with no result since 2026-10-16T04:33:48Z, for 0s
resolve spore resolve run_e9b5a9c8589b644e0ddb6e1238f1cc9c --retry
spore resolve run_e9b5a9c8589b644e0ddb6e1238f1cc9c --complete ok
spore resolve run_e9b5a9c8589b644e0ddb6e1238f1cc9c --fail <message>
exit 4
resolution applied (request ID fix-1)
2026-10-16T04:33:56Z deploy-1 deploy returned ok -> done
2026-10-16T04:33:56Z deploy-1 completed at done
run run_e9b5a9c8589b644e0ddb6e1238f1cc9c completed at done
deployed inv_d05e1c780633ee634db3bb6504984c13
A run you no longer want is cancelled; its history stays, and a step in progress is stopped:
spore start review-loop --key review-2
spore cancel run_1cfdd0c834eff27d0dc75ec2bb686f4b --reason "not needed"
created run run_1cfdd0c834eff27d0dc75ec2bb686f4b (start key review-2)
run run_1cfdd0c834eff27d0dc75ec2bb686f4b cancelled (request ID cancel_74de1eca15fc8496)
Inside a repository, spore init makes the project folder .spore/, which holds its workflows and its run database, and every spore call from any subfolder uses that database. Commands, exit codes and JSON output: spec/cli.md.
spore worker prints each change of each run, spore list shows the runs as a tree, spore status says what a run waits for and how to move it on, and onIntervention sends a notification when a run stops for a person or fails.spore and claude.Read the architecture index and delivery plan. docs/ is the target architecture: Rust, Node, Bun, browser and Cloudflare profiles; typed graphs; effects; SQLite and PostgreSQL adapters; security; OpenTelemetry. spec/ is what spore does today, and wins where the two differ. The vocabulary is in CONTEXT.md. The Claude Code integration is the ml-workflow plugin in the max-skills repository, specified there.
The contracts directory includes the reducer and telemetry WIT drafts, native async store interface, example activity binding, and 22 conformance scenario definitions. Validation status records what has and has not been checked.
conformance/js/check.sh.spore binary, aborted at each of 19 failpoints and killed at 40 points through a run, recovers: no completed step runs again, and an interrupted unsafe step parks the run until an operator resolves it. Run cargo test -p runspore-cli.Not yet done: a soak test, a security review, any target beyond one host with one SQLite file.
This is a general workflow substrate developed under WebTree. The skills workflow epic is an optional integration profile rather than the runtime's governing format.
MIT. See LICENSE. Third-party dependencies keep their own licences; all current Rust dependencies are permissive (MIT, Apache-2.0, BSD, Zlib, Unlicense).