Skip to content

Coming from Temporal

This page covers how a Hyperspool workflow runs, the rules for writing one, and what happens when you change code while runs are in flight.

A workflow is a normal program the engine can run more than once. Each durable call (step, sleep, wait signal, wait event, activity wait) is a checkpoint whose result is recorded the first time it completes. When the run resumes, the script executes again from the top; calls already in the log return their recorded results at once, and execution runs through to where it left off.

Like this:

Terminal window
spool step build -- make
spool sleep 7d
spool wait signal approve
spool step deploy -- ./deploy.sh

It can be written in any language the agent can run, and the durable calls are a subprocess away (spool step …) or a socket write away (the Python SDK, which the managed environments import as hyperspool). See workflows in your language for the other languages.

Suspension is a real process exit. When a script sleeps or waits, its process ends and the agent is freed, so a run waiting a month holds no worker and no memory.

Five of them; the authoring model is the full treatment:

  • Wrap side effects and nondeterministic reads in a step. Anything touching the outside world, and anything a later decision depends on that could change between passes, belongs inside a step.
  • Make steps idempotent where you can. A step that dies after acting but before its result is recorded will run again.
  • Keep between-step code cheap and repeatable. It re-runs on every pass, so branch on values you captured in a step.
  • A step name is a place in the script. Reaching it again is an ordinary loop, and each pass records its own checkpoint, so nothing needs a per-iteration name.
  • Some names stay unique per run, because something refers back to them: an activity (activity wait retrieves it), a bare lock <group>, and a state cas / state incr --op name.

If a replay reaches a different durable call than the first pass recorded at that position, the run fails with a determinism error instead of quietly re-running work. SPOOL_REPLAY_MODE=lenient in an agent’s environment turns that check off for everything the agent runs, so it is available only on agents you run yourself; managed environments always check. When replay goes wrong is the page to read next: a real determinism error, how to trace it back to the branch that caused it, and what the engine doesn’t catch.

A run replays against the definition it started with. Shipping new workflow code leaves in-flight runs alone: they finish on the definition they were submitted with, and new runs pick up the change. The definition is stored with the run, so editing a workflow doesn’t touch your agents.

The pin covers the submitted file only. Commands a script calls (./deploy.sh, a binary on the agent) and the spool binary itself are whatever is on the agent when the run resumes.

The cost: a long-lived run finishes on the logic it started with, so a bug fix reaches it only if you cancel and resubmit.

Your word Here Notes
workflow workflow a script, in whichever language you like
activity step spool step name -- cmd, or step_fn() in the Python SDK
worker agent claims work matching its advertised capabilities; see agents
task queue requires: a job goes only to agents carrying those capabilities
timer spool sleep the run is asleep and holds nothing
signal signal spool wait signal <name>; the approval job kind uses one
child workflow child run spool call -w <name>, or the workflow: job kind
event history run history what the run page renders
retry policy retry:, spool activity run --attempts per job or per activity-backed node; see retry a flaky call
workflow id run id <org>:<project>:run-<random>
search attributes tags spool submit --tag, filterable in the run list
schedule schedule spool schedule; each tick is a fresh run
cancellation spool run cancel, cancel_on: cancel_on: cancels a run when a matching event arrives
side effect API a step the recorded result is the same on every pass
patching / versioning nothing to do a run is pinned to its definition
continue-as-new a schedule, or a graph that calls itself each pass is a fresh run with no accumulated log
query state cell spool state get, entity-scoped for cross-run reads
workflow id reuse / idempotency key spool submit --idempotency-key, spool state add the first dedups a submit; the second claims a key inside a run
saga a script that records undo steps see the saga recipe

Durable patterns has recipes for sagas, timers that an event cuts short, approvals with escalation, fan-out and retries.

The engine can’t catch a rule violation for you. Code between steps that misbehaves (an unwrapped random() steering a branch, a side effect outside a step) produces a run that is plausible and wrong rather than a loud error. The divergence check fires when the sequence of durable calls changes, and that is the only guard rail.

Two more things to size up front:

  • Step results live in the run’s history. Record a path, an id or a summary, and put a build artifact in spool artifact put.
  • Pick the loop shape deliberately. A while loop around spool sleep re-executes every unwrapped command above it on each pass. Cron-shaped work is its own primitive where each tick is a fresh run, and a graph that has to iterate until a condition calls itself. See Repeating work.
  • Queries and updates as a protocol. There is no request-response handler you register on the workflow, so you can’t ask a run a question it didn’t prepare an answer for. Reading what it did record works: a run’s durable state cells are readable from outside it, live or long finished (see reading a cell from outside the run). You decide in advance what to publish; the run writes the cell and the reader fetches it.
  • Compensation as a first-class construct. Sagas are yours to write; the saga recipe shows the shape.
  • A replay test harness.