Skip to content

Workflows

Workflows are durable, long-running operations that survive crashes and restarts. They provide reliability guarantees for critical business processes like payments, order fulfillment, and multi-step approvals.

Functions are ephemeral - if the host crashes, in-flight work is lost. Workflows persist their state:

AspectFunctionsWorkflows
StateIn memoryPersisted
CrashLost workResumes
DurationSeconds to minutesHours to months
CompletionBest effortGuaranteed

Workflow code looks like regular Lua code:

local funcs = require("funcs")
local time = require("time")
local result = funcs.call("app.api:charge_card", payment)
time.sleep("24h")
local status = funcs.call("app.api:check_status", result.id)
if status == "failed" then
funcs.call("app.api:refund", result.id)
end

The workflow engine intercepts calls and records results. If the process crashes, execution replays from history - same code, same results.

Wippy handles determinism automatically. Operations like funcs.call(), time.sleep(), uuid.v4(), and time.now() are intercepted and their results recorded. On replay, recorded values are returned instead of re-executing.

Compensate on failure:

local funcs = require("funcs")
local inventory = funcs.call("app.inventory:reserve", items)
if inventory.error then
return nil, inventory.error
end
local payment = funcs.call("app.payments:charge", amount)
if payment.error then
funcs.call("app.inventory:release", inventory.id)
return nil, payment.error
end
local shipping = funcs.call("app.shipping:create", order)
if shipping.error then
funcs.call("app.payments:refund", payment.id)
funcs.call("app.inventory:release", inventory.id)
return nil, shipping.error
end
return {inventory = inventory, payment = payment, shipping = shipping}

Wait for external events (approval decisions, webhooks, user actions):

local funcs = require("funcs")
funcs.call("app.approvals:submit", request)
local inbox = process.inbox()
local msg = inbox:receive() -- blocks until signal arrives
if msg.approved then
funcs.call("app.orders:fulfill", request.order_id)
else
funcs.call("app.notifications:send_rejection", request)
end
Use CaseChoose
HTTP request handlingFunctions
Data transformationFunctions
Background jobsProcesses
User session stateProcesses
Real-time messagingProcesses
Payment processingWorkflows
Order fulfillmentWorkflows
Multi-day approvalsWorkflows

Workflows are spawned the same way as processes - using process.spawn() with a different host:

-- Spawn workflow on temporal worker
local pid = process.spawn("app.workflows:order_processor", "app:temporal_worker", order_data)
-- Send signals to workflow
process.send(pid, "update", {status = "approved"})

From the caller’s perspective, the API is identical. The difference is the host: workflows run on a temporal.worker instead of a process.host.

When a workflow spawns children via process.spawn(), they become child workflows on the same provider, maintaining durability guarantees.

Processes can run as supervised services using process.service:

# Process definition
- name: session_handler
kind: process.lua
source: file://session_handler.lua
method: main
# Supervised service wrapping the process
- name: session_manager
kind: process.service
process: app:session_handler
host: app:processes
lifecycle:
auto_start: true
restart:
max_attempts: 10

Workflows don’t use supervision trees - they’re automatically managed by the workflow provider (Temporal). The provider handles persistence, retries, and recovery.

Process definition (spawned dynamically):

- name: order_processor
kind: workflow.lua
source: file://order_processor.lua
method: main
modules:
- funcs
- time

Workflow provider:

- name: temporal_worker
kind: temporal.worker
client: app:temporal_client
task_queue: "orders"
lifecycle:
auto_start: true

See Temporal for production workflow infrastructure.