Complex machine learning applications often require multi-stage pipelines (e.g., data loading, transforming, training, testing, iterating). Workflows allow you to manage these pipelines as a sequence of Spell runs, offering a lightweight alternative to tools like Airflow and Luigi for managing your model training pipelines.


For an interactive, runnable tutorial showcasing workflows refer to the workflow tutorial in the spell/examples repo.

Anatomy of a workflow

Every workflow consists of one master run and one or more worker runs. The master run executes a workflow script responsible for control flow: that is, determining which worker runs should get executed when, and why. The worker runs then do all of the work required.

Consider the following state diagram:

Workflow state diagram

In this example there is a single long-lived master run and a set of short-lived worker runs. The worker runs are arranged sequentially, with each subsequent run waiting for the previous run to finish before proceeding.

This is a typical arrangement for a simple machine learning pipeline. For example, the three steps might be "download data", "train a model", and "score the model on test data". Although you could potentially do all three parts in a single run, isolating individual steps in a workflow in this manner makes it easier to manage, edit, and reuse the individual steps of your pipeline much easier.

Furthermore, because the workflow script is typically written in Python, you have the full expressiveness of code for managing the dependencies between steps in your pipeline.

Creating a workflow script

The workflow script is the script that will be executed by the master run. The script can use written using either the Spell CLI or the Spell Python API. Here is an example of a basic workflow script written in Python:

# spellml/examples/workflows/
import spell.client
client = spell.client.from_environment()


r1 ="echo Hello World! > foo.txt")
if r1.status != client.runs.COMPLETE:
    raise OSError(f"failed at run {}")

r2 =
    command="cat /mnt/foo.txt",
    attached_resources={f"runs/{}/foo.txt": "/mnt/foo.txt"}
if r2.status != client.runs.COMPLETE:
    raise OSError(f"failed at run {}")

print("Finished workflow!")

To learn more workflow script best practices and about the Python API check out the workflows tutorial in our examples repository.

Running a workflow

Use the spell workflow command to create and run a workflow. To execute the example workflow above you would run:

$ spell workflow create \
    --github-url \
    "python workflows/"

The master run is still just a run under the hood, so the spell workflow command looks and feels like the spell run command. There is one important difference: the addition of the --repo and --github-repo flags.

You can use the --repo/--github-repo flag to pass named local/remote git repositories into your workflow that can be referenced by name when creating runs within the repo with the commit_label argument to This allows you to easily parameterize the code environment in your worker runs right from the command line. To see this feature in action, check out the workflows tutorial in our examples repository. You can also create runs from within a workflow referencing external github repositories with the github_url and github_ref arguments to See more details in the docs for the python API here.

Viewing a workflow

You can view the logs associated with the master run right from the command line using spell logs RUN_ID (where RUN_ID is the master run's run ID). spell ps, which lets you see currently in progress and recently exited runs, can also be very helpful for tracking the progress of your workflows.

However, the easiest place to view and manage your workflows is the workflows page in the web console:

Workflows in the web console

You can use the web console to click through to constituent runs, view their logs, and stop or terminate them.

Interrupting a workflow

To interrupt a workflow, stop or kill its constituent runs. Note that terminating the master run will not stop any child runs that are already in progress—you will need to terminate these yourself. To learn more about the run termination APIs, refer to "Interrupting a run" in the run docs.