2025-10-06 Obelisk is an open-source deterministic workflow engine that runs, stores, and replays WASM-based workflows using SQLite.
The headline change in v0.25: every function in workflows and activities is now required to be fallible. Obelisk maps execution-failed
errors into the called function’s return type. This ensures traps and errors are handled explicitly instead of silently breaking execution.
Breaking API Changes
Each function invoked by Obelisk, whether a workflow or an activity, must be fallible. More precisely, it means that the return type must be one of following:
result
orresult<T>
whereT
is any success type, e.g.string
orlist<u32>
or even a none type (_
) In Rust, the execution failure willbe represented asErr(())
- `result<T, s…
2025-10-06 Obelisk is an open-source deterministic workflow engine that runs, stores, and replays WASM-based workflows using SQLite.
The headline change in v0.25: every function in workflows and activities is now required to be fallible. Obelisk maps execution-failed
errors into the called function’s return type. This ensures traps and errors are handled explicitly instead of silently breaking execution.
Breaking API Changes
Each function invoked by Obelisk, whether a workflow or an activity, must be fallible. More precisely, it means that the return type must be one of following:
-
result
orresult<T>
whereT
is any success type, e.g.string
orlist<u32>
or even a none type (_
) In Rust, the execution failure willbe represented asErr(())
-
result<T, string>
- the execution failure will be represented asErr("execution-failed")
-
result<T, E>
whereE
is avariant
type (similar to Rust’senum
) that includes anexecution-failed
variant. The execution failure will be represented asErr(MyError::ExecutionFailed)
Previously, functions were free to return any WIT-supported value. However, this flexibility caused issues: -
Activities could fail if a function
trap
ped (panicked) or timed out with no retries left. -
Workflows could fail when a function
trap
ped This was previously mitigated with theexecution-failed
error. The parent execution had to choose between: -
Calling an extension function such as
-invoke
or-await-next
, then handling the error, or -
Calling the child function directly, in which case the error was unhandled. In the latter case, the parent execution itself would abort with an “Unhandled child execution error”.
This design added complexity for both workflow authors and the runtime. Workflow authors had to decide whether a child’s execution error was fatal or if cleanup or compensation should follow.
Mistakingly calling a child function directly could mean resources were never freed, making workflows brittle.
The runtime faced similar complexity, especially around join set closing. It was unclear whether an execution-failed
during a join set close should be treated as fatal for the parent workflow or simply ignored as other unawaited execution results.
With the new design, the runtime maps execution-failed
to one of the supported error types. It builds on the strength of the WASM isolation - effectively making each invocation live in its own “blast zone”, so that even unusual errors like running out of memory will not affect the caller.
When handling such errors, not every activity failure needs to derail the workflow. For example, if an email-sending activity fails with execution-failed
, the email may not be sent, but the workflow can still continue without issue.
If an activity is crucial, the workflow must exit. Making the error handling explicit has one big advantage especially for sagas: Since it is now impossible to overlook this kind of error, the workflow must now choose whether to abort all further actions or whether a compensating logic should be applied. This could include shutting down VMs or other resources that must not be left in an unknown state.
In conclusion, the new requirement for fallible functions simplifies both workflow authoring and runtime behavior. By mapping execution-failed
errors into the called function’s return type, workflows become safer, more predictable, and easier to reason about, allowing developers to handle failures explicitly and reliably without risking hidden resource leaks or unexpected early returns.
Ergonomics changes
Other changes are mostly focused on CLI:
obelisk generate wit-support
was added to export the Obelisk WIT files. These files are required for working with extensions and include workflow support functions.-invoke
extension now accepts a join set label. With execution-error removed, this function was repurposed to attach a label to its one-off join set. This is especially useful when calling the same function with different parameters, as custom join set names make it easier to understand a workflow’s pending state without inspecting the execution log.- A new support function,
close(join-set)
, was added. In garbage-collected languages, join set resources aren’t automatically dropped, so this explicit function in workflow-support ensures proper cleanup. - The
obelisk client execution submit
command now supports the shorthand.../interface-name.fn-name
for specifying functions. - Minor usability improvements have been made to the WebUI.