Extension Functions

Workflows can call child executions directly using a regular function call or they can use extension functions to access advanced features like scheduling and asynchronous execution.

-obelisk-ext interface

Obelisk automatically generates extension functions of this interface for each exported workflow and activity function.

This interface simplifies submitting, awaiting, and scheduling child executions from a parent workflow. They streamline common tasks associated with workflow execution and are a key part of Obelisk's structured concurrency model.

The types used in the extension functions (execution-id, join-set-id, schedule-at, execution-error) are defined in the ]obelisk:types WIT files.

Functions submit and await-next enable asynchronous execution. You can submit multiple requests without blocking, and then process results as they become available.

The schedule function allows for time-based execution, enabling use cases like delayed tasks and periodically scheduled jobs.

Obelisk workflows execute in a single-threaded, synchronous manner within their WebAssembly sandbox. This eliminates the complexities often associated with asynchronous programming, such as callbacks, async/await keywords, and the need to manage concurrent threads directly within the workflow code. You write your workflow logic as if it were a simple, sequential program, even when it involves multiple activity calls or sub-workflows. Obelisk handles the underlying asynchronous operations and concurrency for you.

Consider an activity function defined in WIT like this:

package stargazers:llm;

interface llm {
    respond: func(user-prompt: string, settings-json: string) -> result<string, string>;
}

Obelisk will auto-generate the following extension functions in a separate WIT package for each exported function:

// Generated by Obelisk.
package stargazers:llm-obelisk-ext;

interface llm {
    use obelisk:types/execution@1.1.0.{execution-id, join-set-id};
    use obelisk:types/time@1.1.0.{schedule-at};
    use obelisk:types/execution@1.1.0.{execution-error};

    respond-submit: func(join-set-id: borrow<join-set-id>, user-prompt: string, settings-json: string) -> execution-id;

    respond-await-next: func(join-set-id: borrow<join-set-id>) -> result<tuple<execution-id, result<string, string>>, tuple<execution-id, execution-error>>;

    respond-schedule: func(schedule-at: schedule-at, user-prompt: string, settings-json: string) -> execution-id;
}

Let's break down each generated function:

Submitting child executions with -submit

This function is used to submit a request for the original function to be executed. It takes the join-set-id and the original parameters as input. It returns an execution-id, which uniquely identifies this particular execution request. The join-set-id associates this execution with a specific join set , allowing you to manage multiple concurrent executions. This function does not wait for the execution to complete; it simply submits the request.

Awaiting next result with -await-next

This function is used to wait for the next result from a given join-set-id. It takes the join-set-id as input and returns a result. The result contains a tuple with two possible outcomes which must be handled:

  1. The workflow function has finished successfully. In this case, the tuple will hold the execution-id that identifies the finished execution and the original return value.
  2. There was an error during the child execution. In this case, the tuple will contain the execution-id and execution-error as defined in obelisk:types

By repeatedly calling respond-await-next after submitting multiple executions on a join set, you can retrieve results as they become available, in the order they complete.

Scheduling a new execution with -schedule

This function allows you to schedule the execution of the original function at a specific time in the future. It takes a schedule-at (defined in obelisk:types/time) and the original parameters as input. Like respond-submit, it returns an execution-id. The Obelisk runtime will ensure the function is executed at (or as close as possible to) the specified time. It's important to note that scheduled executions do not use join sets. This is because scheduling inherently breaks the parent-child relationship that join sets rely on for structured concurrency. Consequently, a scheduled function's result cannot be directly awaited by the caller. Scheduled functions are "fire-and-forget" from the perspective of the scheduling workflow. If you need to retrieve the result, you would typically use the persistent sleep function instead.

-obelisk-stub interface

Obelisk automatically generates stub functions for each exported activity function in this interface, provided the activity is configured as a stub.

Stub activity functions are not executed by the Obelisk runtime. Instead, the following steps are executed:

  1. The parent workflow calls the stubbed function. Both direct calls (e.g. myactivity()) and async counterparts (e.g. myactivity_submit) are supported.
  2. An external process or workflow stubs the execution result by supplying a return value for the given execution ID.
  3. The parent execution obtains the return value and continues its logic.

The parent execution is agnostic of whether the child activity is a regular WASM activity or a stubbed activity.

Consider two activity functions defined in WIT like this:

package testing:stub-activity;

interface activity {
    foo: func(arg: string) -> string;

    noret: func();
}

Obelisk will auto-generate the -stub function for each:

package testing:stub-activity-obelisk-stub;

interface activity {
    use obelisk:types/execution@1.1.0.{execution-id, stub-error};

    foo-stub: func(execution-id: execution-id, return-value: string) -> result<_, stub-error>;

    noret-stub: func(execution-id: execution-id) -> result<_, stub-error>;

}

The -stub function is used by a workflow that supplies the return value. The return value can also be injected using Web UI or CLI using

obelisk client execution stub <EXECUTION_ID> [RETVAL]

An example stub workflow is available in the repository.