Skip to content
Pyrula
Pre-release: the packages are not on PyPI yet, so pip install does not resolve. Build from source to try it today.

Runs as Event Streams

A run of a @workflow is one append-only stream of events in the store. The same stream does two jobs: it’s the log a worker replays to recover, and it’s the feed a client reads to follow the run live.

The event stream is the source of truth. If a secondary index disagrees with it, the stream wins.

Events are RunEvent records tagged with an EventKind. Lifecycle kinds describe the run; step kinds describe individual durable operations.

from pyrula.workflows import RunEvent, EventKind, RunStatus
GroupKinds
Lifecyclerun:init, run:complete, run:error, run:cancel, run:continued, run:requeued, run:interrupted, run:resumed, run:sleeping, run:woken, run:pause_requested, run:paused, run:unpaused, run:signal_waiting, run:signal_received
Updatesrun:update_received, run:update_completed, run:update_failed
Stepstep:pending, step:done, step:error
Progressrun:status (from ctx.emit_status(text)), plus whatever you ctx.emit
Internalheartbeat, stream:overflow, patch_marker

RunStatus is derived from the stream: pending, in_progress, complete, cancelled, error, quarantined, interrupted, sleeping, signal_waiting.

The worker’s replay log and a client’s live feed are the same stream. Read it back with store.load_run:

@workflow(name="onboard_user")
async def onboard_user(ctx, email: str) -> str:
account_id = await ctx.step("create_account", lambda: create_account(email))
await ctx.step("send_welcome", lambda: send_welcome_email(account_id))
return account_id
store = MemoryStore()
runner = InlineWorkflowRunner(store=store)
run = await runner.client.submit("onboard_user", {"email": "[email protected]"})
await run.result()
for event in await run.events():
print(event.kind, event.payload.get("result"))
# run:init None
# step:pending None
# step:done acct_42 <- create_account result
# step:pending None
# step:done None <- send_welcome result
# run:complete acct_42

An ordered, append-only log means any worker can rebuild the exact in-memory state of a run by reading it from the start. No separate snapshot, no checkpoint format to keep in sync. That property is what makes replay work.