This repository contains a working Go prototype of a native durable workflow engine that checkpoints step results in SQLite and resumes without re-running completed side effects.
- Generic durable step primitive:
func Step[T any](ctx *engine.Context, id string, fn func() (T, error)) (T, error)
- Workflow runner:
engine.RunWorkflow(...)
- SQLite-backed persistence (
stepstable) with:workflow_idstep_key(step id + logical sequence)status- serialized
output_json
- Resume behavior:
- Completed steps return cached output and are skipped.
- Parallel step execution in onboarding example.
- Crash simulation and restart proof via CLI.
- Tests for durability, sequence behavior, concurrency safety, zombie-step takeover, and auto-ID generation.
engine/core durable engine (context, step logic, sqlite persistence, runner)examples/onboarding/employee onboarding workflow examplemain/CLI app to start, crash, and resume workflowinternal/errgroup/small local errgroup implementation used for parallel stepsscripts/soak.shrepeated stress runner for rigorous testingqa.shend-to-end QA runner with standard and rigorous modesPrompts.txtprompts used during AI-assisted build
- Go
1.25+ sqlite3binary available inPATH
go run ./main -workflow-id emp-onboard-001Crash format is -crash <step>:<before|after>.
go run ./main -workflow-id emp-onboard-001 -crash provision_laptop:afterThen rerun the same workflow id:
go run ./main -workflow-id emp-onboard-001You should see previously completed steps reported as completed and skipped.
create_record(sequential)provision_laptop(parallel)provision_access(parallel)send_welcome_email(sequential)
Each Context maintains a logical per-step counter:
- First call to
loop_step->loop_step#000001 - Second call to
loop_step->loop_step#000002 - etc.
This means loops and repeated branches can reuse the same human-readable step ID while still getting unique checkpoint keys.
The engine also supports automatic step ID generation if id == "" (bonus requirement), using caller metadata.
Parallel workflow steps are supported. For SQLite safety:
- SQLite is configured with
WALmode andbusy_timeout. - Store operations are synchronized with a mutex (single writer section).
- Write operations include retries for
SQLITE_BUSY/database is locked.
This satisfies the assignment requirement of safe concurrent step execution against SQLite.
A zombie step is a row left in running state after a crash.
Current strategy in this prototype:
- On resume, if a
runningstep belongs to a differentrun_id, the new run takes over and re-executes it. - Side effects in the onboarding sample are implemented idempotently in file-backed mock services.
This keeps the prototype resilient and practical without introducing a separate distributed lease service.
go test ./...Additional rigorous tests are implemented in:
engine/rigorous_test.goTestRandomizedResumeProducesDeterministicOutputsTestHighContentionManyWorkflowsParallelTestCorruptedCachedOutputFailsFastTestZombieTimeoutBlocksImmediateTakeover
These add randomized resume-equivalence checks, high-contention concurrency stress, corrupted-checkpoint handling, and strict zombie-timeout behavior.
Run the full QA suite:
./qa.shRun the rigorous mode (includes extra stress pack and soak loop):
./qa.sh --rigorousRun repeated stress cycles directly:
./scripts/soak.sh --iterations 10Benchmark suite is implemented in:
engine/benchmark_test.goBenchmarkStepColdWriteBenchmarkStepCachedReadBenchmarkStepParallelWritesBenchmarkOnboardingWorkflowE2E
Run all benchmarks:
mkdir -p /tmp/go-cache /tmp/go-tmp
GOCACHE=/tmp/go-cache GOTMPDIR=/tmp/go-tmp go test ./engine -run '^$' -bench . -benchmem -count 1Run benchmark with CPU/memory profile:
GOCACHE=/tmp/go-cache GOTMPDIR=/tmp/go-tmp go test ./engine -run '^$' -bench BenchmarkStepParallelWrites -benchmem -cpuprofile /tmp/cpu.out -memprofile /tmp/mem.out
go tool pprof /tmp/cpu.out
go tool pprof /tmp/mem.out