ADR 041: Durable functions completion (workflow, activity, actor, @scheduled)
ADR 041: Durable functions completion
Section titled “ADR 041: Durable functions completion”Status
Section titled “Status”Accepted (2026-05-23). Supersedes ADR-028.
(Numbering note: this ADR was originally drafted as “ADR-029” in
docs/superpowers/plans/2026-05-23-durable-functions-completion.md Task 7.1,
but ADR-029 was already taken by 029-formal-intent.md — itself renumbered
from 024 on 2026-05-02 — so this entry takes the next free slot, 041.)
Context
Section titled “Context”ADR-028 (proposed 2026-05-01) recommended removing @durable, @scheduled,
workflow, and activity from the public grammar because the 2026-05-01
durability audit found them to be parse-only with no runtime backing.
Between 2026-05-01 and 2026-05-23, the implementation work was completed:
codegen_rust/emit/durability_lower.rsemits real runtime calls (interpret_workflow_durable,journal::execute,spawn_process).- The three runtime symbols the codegen referenced (
current_hir_module,extract_terminal_return,journal::execute) were implemented (Phase 1). - End-to-end and crash-replay tests prove the integration works
(Phases 2–3). The interpreter walks user-defined activity calls via a
newly-added
HirExpr::Tryarm inworkflow/plan.rs:collect_from_expr(caught and fixed during Phase 2 testing). @scheduledgot a persistent scheduler loop with crash-safe state via ascheduled_runsDB table (Phase 4).- Actor handlers auto-wire from generated
main()via a newActorRegistry(Phase 5.2) and theemit_main_bootcodegen (Phase 5.1). HIR embedding is now real (§6(b), landed 2026-05-23). HTTP server boot inemit_main_bootis deferred (§6(c), 2026-05-23) pending a route-emission refactor — seedocs/src/architecture/http-runtime-extraction-2026.md. The productionmain()(emit_maininhttp.rs) still serves HTTP inline, so this affects no user-facing behavior. - A determinism lint blocks non-deterministic ops
(
std.time.now_ms,std.random,std.uuid,std.process.spawn) in workflow bodies (Phase 6).
Decision
Section titled “Decision”-
Retain
@durable,@scheduled,workflow,activity, andactoras public grammar features. -
The durable interpreted runtime is Stable for the body-first subset (
if/match/for/while/?). Generated-Rust durability uses the same engine (VoxDbTracker,__vox_run_workflow, ADR-021 compile gate) and is no longer Preview-until-Phase-6. Mesh /PopuliActivityand nested workflow calls remain out of scope (loud unsupported error, not a silent drop). See true-workflow-durability-design-2026.md. -
Out-of-subset for generated Rust may stay linear. Interpreted
match/ loops are in-scope under the determinism lint. Mesh /PopuliActivityis out of the 2026-09-06 body-first plan (loud unsupported error, not a silent drop). -
The
vox checkCLI gate at the user-facing layer that rejectedworkflow/activitywithE0001/E0002must be removed in this ADR’s enactment (Task 7.2/7.3 will surface it; the gate is incrates/vox-cliand emits an ADR-028-style reservation error). -
Migration of golden examples (
checkout_workflow.vox) from plainfntoworkflow/activitylands alongside this ADR (Task 7.3). -
Three tracked follow-ups from the 2026-05-23 final code review:
a. [Landed 2026-05-23] Scheduler restart deadline derivation. When
vox-workflow-runtime/src/scheduled/runner.rs(lines 88-116) restarts and a registered function’s persistednext_due_at_msis in the future, the in-memoryInstantdeadline was seeded tonow + intervalinstead ofnow + (next_due_at_ms - now). A crash 23 hours into a@scheduled("1d")interval re-armed a full day instead of firing in ~1 hour. Resolved by addingVoxDb::scheduled_runs_next_due_at_ms(name)and having the runner seednow_inst + clamp(persisted - wall_now, 0, interval). Clamping atintervaldefends against clock-skew jumps. Regression test:crates/vox-workflow-runtime/tests/scheduled_basic.rs::scheduler_restart_preserves_partial_interval_wait.b. [Landed 2026-05-23] HIR embedding for
current_hir_module().emit_main_bootnow serializes theHirModuleto JSON at codegen time (HirModule derivesSerialize+Deserialize), embeds it as a raw-stringconst &strin the generated binary, and emits aset_current_hir_module(load_hir_module_from_embedded())call as step 2 of the boot routine — before the scheduler starts or any workflow body runs. Generated binaries now resolvecurrent_hir_module()lookups at runtime instead of panicking on an unsetOnceLock. The raw-string delimiter is chosen dynamically (starts atr#"…"#, escalates hash count if the serialized JSON contains a closing match). Regression test:crates/vox-codegen/tests/main_boot_hir_roundtrip.rs::embedded_hir_roundtrips_through_json. Snapshot lock:crates/vox-codegen/tests/main_boot_snapshot.rs. The Stable tier claim in the README now extends to the codegen-to-compiled-binary path as well as the interpreter path.c. [Deferred 2026-05-23 — see design doc] HTTP server boot in generated
main(). Recon (P8.c) found that the HTTP wiring inemit_main(crates/vox-codegen/src/codegen_rust/emit/http.rs) is tied to per-handler code generation: each@query/@mutation/@serveremits a named free function, plus per-route static items (rate-limit governors) and inlined CORS layers. Avox_http_runtime::serve(db, hir_module) -> Handlecannot register those without either (i) interpreting HIR at runtime or (ii) reshaping emit so handlers register themselves viaBox<dyn Fn>closures. Crucially,emit_main_boot(the durable-onlymain()that carries the §6(c) TODO) is not yet on the production codegen path — production still usesemit_main, which serves HTTP just fine inline. The TODO is therefore harmless today. Full constraints and the two-phase plan to do this properly are indocs/src/architecture/http-runtime-extraction-2026.md. Status: DEFERRED pending route-emission refactor oremit_main↔emit_main_bootconvergence.
These follow-ups do NOT block this ADR’s acceptance.
Consequences
Section titled “Consequences”- The README, the docs site landing page (
index.mdx), and the design system kit can claim durable execution truthfully for the supported subset (Tasks 7.2 and 7.4). - The “Durable Runtime” stability tier moves from 🟡 Preview to 🔵 Stable for the supported subset (Task 7.2 reflects this in the tier table).
- Future durability work (HTTP server-runtime integration for combined boot; unrestricted control-flow replay; mesh-distributed workflow dispatch) is tracked under separate ADRs and follow-up plans.
Related
Section titled “Related”- ADR-019: Durable workflow journal contract v1 — replay contract (Accepted)
- ADR-021: Generated workflow durability parity — design gate (Accepted)
- ADR-028: Remove stub durability/scheduling grammar — superseded
docs/superpowers/plans/2026-05-23-durable-functions-completion.md— implementation plandocs/src/architecture/durability-runtime-audit-2026.md— the 2026-05-01 audit (historical)