Skip to main content
Every job runs on the project’s Postgres database, configured by fxtr init (see the Quickstart). Launchers talk to it through the project client, which stores entities and manages jobs, project arrays, and the cache.

Before you launch

  • Commit first. A launch records the commit it runs and checks that the virtual environment matches uv.lock, so it refuses uncommitted changes. Pass allow_staged=True (or --allow-staged on the command line) to include staged changes.
  • Register your modules. Resuming a job, fxtr run, and the viewer import the modules in [tool.fxtr] modules, not your launcher, so every step and workflow a job uses must be defined in one of them.
  • Use a scratch schema for trials. To keep trial jobs and cache entries apart from real results, set schema in fxtr.local.toml to another name while experimenting, and set it back for real runs.

Launch from Python

The simplest launcher calls launch_cli, which opens the client, submits the job, prints its viewer link, waits, and reports the result:
launch.py
anchor=__file__ tells fxtr which project the launcher belongs to. max_concurrency caps how many steps run at once. If the job doesn’t succeed, launch_cli exits with status 1. For more control, such as choosing the root, loading datasets, or reading results, write an async launcher with open_local_client and run_job:
launch.py
  • run_job prints the viewer link before waiting, then the job’s status and result. It raises JobNotFinishedError if the job doesn’t succeed.
  • root is the root workflow’s pathname, and so the start of every cache address in the job. See Caching and reruns.
  • Use handle = await client.submit(...) when you need the job’s ID or want to control the waiting yourself; await wait_for_job(client, handle) provides the same reporting as run_job.
  • In a notebook, call await main() instead of anyio.run(main).

Read results

run_job returns the root workflow’s result as an Array. Entity values in it are bare IDs, so load them with their type:
Keep the client open while loading. Alternatively, pass an async on_result(storage, result) callback to run_job or launch_cli; it replaces the default result printing and runs while storage is open.

Launch from the command line

fxtr run launches a workflow by its registered name:
Inputs are JSON scalars, or @NAME for a snapshot of a project array. Anything richer needs a launcher script.

Resume a stopped job

A job stops when it can’t make further progress: a step raised, a step is held by the cache, or the job was cancelled. Resume it with its ID, which the launcher prints:
Resuming uses the job’s stored inputs and your registered modules, records the current commit, reuses every result the job already has, and retries unfinished work, including steps that raised. From Python:
Resume refuses a job that already succeeded. If another process is running the job, resume waits until that process has been silent for [tool.fxtr] stale_after seconds (30 by default), then takes the job over.

Inspect and cancel jobs

In Python, handle = await client.job(job_id) attaches to an existing job without starting it.

Watch jobs in the viewer

Start the viewer from the project with uv run fxtr view, then open the link a launcher printed, or pick a job in the viewer. Jobs appear as they run. See Viewing results.