Skip to content

Jobs

A job is a single execution of a gear. Every call to gear.run() (utility gears) or every analysis gear submission produces a job under the hood. The SDK lets you find, inspect, monitor, cancel, retry, and bulk-submit jobs through the fw.jobs finder and a handful of dedicated methods.

For how to launch a job, see Gears. This page picks up after submission.

Job lifecycle

Every job moves through a small state machine. Three states — complete, failed, and cancelled — are terminal: a job in any of these states will not transition again.

flowchart LR
    pending --> running
    running --> complete
    running --> failed
    pending --> cancelled
    running --> cancelled
  • pending — accepted by the queue, not yet picked up by a worker.
  • running — a worker has started executing the gear.
  • complete — the gear exited successfully.
  • failed — the gear exited non-zero or the worker reported a failure.
  • cancelled — the job was cancelled by a user or by the scheduler.

Job fields

The most-used fields on a Job object:

Field Description
id The job's unique ID
gear_id ID of the gear that produced this job
gear_info.name Name of the gear (use this for filter queries)
state Current lifecycle state
created / modified Timestamps
inputs Inputs the gear ran against, as FileReferences
config Effective config (defaults merged with overrides)
destination Where outputs land — a container or new analysis
tags User-supplied labels, useful for grouping batches
failure_reason Populated only on terminal failed jobs
attempt Retry count; the first run is attempt 1
retried Set to True once this job has been retried; None otherwise
previous_job_id ID of the original job this was retried from; None for the original

Logs do not live on the Job object — fetch them separately with fw.get_job_logs(job_id). See Reading job logs.

Finding jobs

fw.jobs is a Finder, so the standard find / find_first / iter / iter_find methods all apply.

The most useful filter keys:

Key Example
state state=failed
gear_info.name gear_info.name=dicom-qc
gear_id gear_id=000123456789abcdefABCDEF
tags tags=my-batch-tag
origin.id origin.id=<user_id>
parents.project parents.project=000123456789abcdefABCDEF
parents.group parents.group=scientific-solutions

Filter by gear and state

Find failed jobs for a specific gear
jobs = list(fw.jobs.iter_find(f"gear_info.name={gear_name},state=cancelled"))
for job in jobs:
    print(f"{job.id} ({job.state})")

Filter by gear and submission date

Use the created field with a comparison operator to scope a query to a time window. Dates are interpreted as UTC and may be supplied as YYYY-MM-DD or a full ISO-8601 timestamp.

Find jobs for a gear submitted in the last day
gear_name = "dicom-qc"

since = "2026-05-26"  # midnight UTC — anything submitted on or after

jobs = list(fw.jobs.iter_find(f"gear_info.name={gear_name},created>{since}"))
for job in jobs:
    print(f"{job.id} — submitted {job.created}")

Get a job by ID

Get a single job by ID
job = fw.get_job(job_id)
print(f"{job.id} — state: {job.state}, gear: {job.gear_info.name}")

Monitoring a job

Polling job state

Job execution is asynchronous. To wait for a job to finish, poll its state until it reaches a terminal value (complete, failed, or cancelled).

Poll until a job reaches a terminal state
import time

TERMINAL = ("complete", "failed", "cancelled")

job = fw.get_job(job_id)
while job.state not in TERMINAL:
    time.sleep(5)
    job = fw.get_job(job_id)
print(f"Job finished in state: {job.state}")

Tip

Pick a poll interval that matches the gear's expected runtime. Quick utility gears (file conversion, QC) can tolerate a 2–5 second interval; long-running analysis gears are better served by a 1-5 minute interval to avoid hammering the API.

Reading job logs

fw.get_job_logs(job_id) returns a JobLogRecord whose logs attribute is a list of log entries. Each entry has a msg field with the line of text the worker emitted.

Read a job's logs
record = fw.get_job_logs(job_id)
for entry in record.logs:
    print(entry.msg, end="")

Logs may be empty for jobs that have not yet started running. Logs continue to be available after the job reaches a terminal state.

Cancel and retry

Cancel

Cancel transitions a job to the terminal cancelled state. It is only valid from pending or running — once a job is in any terminal state, cancel is a silent no-op.

Cancel a pending or running job
fw.modify_job(job_id, {"state": "cancelled"})

Warning

A cancel against a terminal job (complete, failed, already cancelled) does not raise — the request returns successfully and the job's state does not change. Check job.state first if you need to know whether the cancel took effect.

Retry

Retry is only valid for a job in the failed state. It creates a new job with the same gear, inputs, and config; the original job stays in the failed state for provenance. The new job's ID is the return value.

Note

Retry is useful for issues with timeouts, or when a job operates on an entire session (or container), and the container's contents have changed. Retries will generally not help when an algorithm fails on a specific file. Even if the file is replaced, retry will run the gear with the original file. To run the gear with the new file version, a fresh run of the gear is necessary.

Retry a failed job
failed_job_id = "000123456789abcdefABCDEF"

new_job_id = fw.retry_job(failed_job_id)
print(f"Retry queued as job {new_job_id}")

Pass ignore_state=True to retry a job that is not in the failed state — useful when you need to re-run a complete job against new data.

Use the retried and previous_job_id fields to trace the relationship between an original job and its retry:

Find the retry of a failed job
job_id = "000123456789abcdefABCDEF"

job = fw.get_job(job_id)
if job.state == "failed" and job.retried is not None:
    retry_job = fw.jobs.find_first(f"previous_job_id='{job.id}'")
    print(f"Retried as job {retry_job.id}")

Batch jobs

To launch the same gear against many containers in one submission, use gear.propose_batch(). This is preferred over hand-rolling a gear.run() loop because all jobs share a single batch ID, which makes them findable and cancellable as a unit.

Submit a batch over several acquisitions
proposal = gear.propose_batch(
    [acq_a, acq_b],
    config={"threshold": 0.7},
)
jobs = proposal.run()
print(f"Submitted {len(jobs)} jobs in batch {proposal.id}")

The BatchProposal returned by propose_batch() is not yet running — call .run() to enqueue the jobs and .cancel() to cancel the entire batch.

Batch proposal will try to match files to the gear's inputs based on file types. If the gear finds more than one file of the suggested type present in the container, it will not be able to successfully batch that particular job. Because of this, this is most useful for gears with single (or few) inputs, where the containers generally only have one of the target file type (for example, dcm2niix batch launched on acquisitions: dcm2niix only requires one input dicom, and acquisitions typically only have one dicom file).

For batches where each container needs different config or inputs, OR where the inputs are too complex to be reliably populated simply though type suggestions, fall back to a manual loop. Tag the jobs so you can find them as a group:

Manual fan-out when configs differ
job_ids = []
for container, threshold in [(acq_a, 0.3), (acq_b, 0.7)]:
    job_id = gear.run(
        destination=container,
        config={"threshold": threshold},
        input_file=container.get_file("scan.nii.gz"),
        tags=["my-batch-2026-05-21"],
    )
    job_ids.append(job_id)

Bulk cancel

Cancel many jobs at once by iterating a tag- or state-scoped finder query and calling modify_job on each result.

Cancel all pending jobs by tag
for job in fw.jobs.iter_find(f"tags={tag},state=pending"):
    fw.modify_job(job.id, {"state": "cancelled"})

Note

For hundreds or thousands of jobs the per-job modify_job loop becomes the bottleneck. The Core API exposes a bulk endpoint (PUT /api/jobs/cancel/bulk) for this case, which is not surfaced on the SDK. Call it directly with fw-client when you need it.

Next steps

  • For jobs launched from analysis gears, the output container — an analysis — has its own page: Analyses.
  • For the methods used to discover and inspect gears before launching, see Gears.