Workflow generator and for loops

Now we will investigate how to generate subworkflows in a loop. There are in principle two cases one can encounter:

  1. The parameters that one wants to loop over are known and given by user input.

  2. The parameters are determined from the result of another task.

Case 1 only requires a static workflow, while case 2 is dynamic in the sense that the tasks in the loop cannot be generated until the initial task that determines the parameters to loop over is done. Since an implementation that can handle the second case also can handle the first case all loops in TaskBlaster are dynamic. For-loops are implemented using the so called dynamic workflow generator. Below we show a simple example:

Start by inititializing a TaskBlaster repo:

$ tb init
Created repository using module "taskblaster.repository" in "/home/myuser/tmprepo".

Make two simple tasks in tasks.py:

def ok(number):
    return number


def plus_two(number):
    return [n for n in range(number + 2)]


Now we want to create a workflow that takes a number as input, and uses the task plus_two to determine a list of numbers to loop over. The task ok should then be executed for all numbers in the list.

First we add the task plus_two to the file workflow.py:

import taskblaster as tb


@tb.workflow
class Workflow:
    number = tb.var()

    @tb.task
    def task1(self):
        return tb.node('plus_two', number=self.number)


def workflow(runner):
    runner.run_workflow(Workflow(number=1))

and run the workflow

$ tb workflow workflow.py
entry:                    add  new      0/0        tree/task1 
$ tb run tree
Starting worker rank=000 size=001
[rank=000 2025-03-17 10:08:40 N/A-0/1] Worker class: —
[rank=000 2025-03-17 10:08:40 N/A-0/1] Required tags: —
[rank=000 2025-03-17 10:08:40 N/A-0/1] Supported tags: —
[rank=000 2025-03-17 10:08:40 N/A-0/1] name: None
    tags: —
    required_tags: —
    resources: None
    max_tasks: None
    subworker_size: None
    subworker_count: None
    wall_time: None
[rank=000 2025-03-17 10:08:40 N/A-0/1] Main loop
[rank=000 2025-03-17 10:08:40 N/A-0/1] Running task1 ...
[rank=000 2025-03-17 10:08:40 N/A-0/1] Task task1 finished in 0:00:00.001452
[rank=000 2025-03-17 10:08:40 N/A-0/1] No available tasks, end worker main loop

Verify using tb view that you have the expected output from the task.

$ tb view .
name: task1
  location:        /home/myuser/tmprepo/tree/task1
  state:           done
  target:          plus_two(…)
  wait for:        0 dependencies
  depth:           0
  source workflow: <root workflow>
  frozen by: (not frozen)

  latest handled inputs:
     None

  handlers:
    []

  handler data:
    <None>

  parents:
    <task has no dependencies>

  input:
    ["plus_two", {"number": 1}]

  output:
    [0, 1, 2]

Run information:
    Worker name: N/A-0/1
    Start time: 2025-03-17 10:08:40
    End time: 2025-03-17 10:08:40
    Duration: 0:00:00
    Error: None

No custom actions defined for this task.

Now you are ready to add the loop. Add the following to the workflow.py file and save it to a new file workflow2.py.

import taskblaster as tb


@tb.workflow
class Workflow:
    number = tb.var()

    @tb.task
    def task1(self):
        return tb.node('plus_two', number=self.number)

    @tb.dynamical_workflow_generator({'results': '*/*'})
    def generated_wfs(self):
        return tb.node('generate_wfs_from_list', inputs=self.task1)


def workflow(runner):
    runner.run_workflow(Workflow(number=1))

We have now added a dynamical_workflow_generator to the workflow. This decorator takes an input argument which specifies how the result is collected. The input argument is a dictionary where the keys corresponds to the name that will be given to the results task and the item is the path to the results that will be collected by the results task. It is possible to specify any number of seperate results collections. Here we simply collect all results to the results task results. The workflow generator returns a dynamical_workflow_generator_task that needs to be specified in the tasks.py file or alternatively imported from an external package.

In this tutorial we will for simplicitly just update the tasks.py file, which is automatically imported by TaskBlaster, even though it is a bit unintuitive to have workflows in this file:

import taskblaster as tb


# -- first_part_start
def ok(number):
    return number


def plus_two(number):
    return [n for n in range(number + 2)]


# -- first_part_end


@tb.dynamical_workflow_generator_task
def generate_wfs_from_list(inputs):
    for inp in inputs:
        wf = SubWorkflow(number=inp)
        name = f'wf_{inp}'
        yield name, wf


@tb.workflow
class SubWorkflow:
    number = tb.var()

    @tb.task
    def generated_task1(self):
        return tb.node('ok', number=self.number)

    @tb.task
    def generated_task2(self):
        return tb.node('ok', number=self.number)

You can notice that we added a dynamical_workflow_generator_task with the name generate_wfs_from_list. Since each generated subworkflow will be given a specific folder the workflow generator task

  1. takes a list as input

  2. loops over the list

  3. Defines which workflow that should be generated and what input arguments it should have and saves it to the variable wf.

  4. Defines a unique name for the subfolder for the generated workflow

  5. Yields the name of the subfolder together with the wf.

This is a minimal example for how you can use a workflow generator, but in principle you can more advanced logic inside the function (e. g. yielding different subworkflows depending on the input arguments etc.). Finally we also need to define the workflow SubWorkflow that should be generated. Here we have a simple workflow with two simple tasks, but it is possible to generate arbitrary complex workflows.

Now let’s see what happens when we run the workflow.

$ tb workflow workflow2.py
entry:                    add  new      1/1        tree/generated_wfs/init 
                          add  new      0/?        tree/generated_wfs/results 
entry:                   have  done     0/0        tree/task1 
$ tb ls
state    info       tags        worker         time     folder
──────── ────────── ─────────── ─────────── ─────────── ─────────────────────────────
new      1/1                                            tree/generated_wfs/init
new      0/?                                            tree/generated_wfs/results
done     0/0                    N/A-0/1        00:00:00 tree/task1

As you can see two tasks were created, the initializer init and the finalizer which we defined as results. Let’s try to run the initializer:

$ tb run tree/generated_wfs/init
Starting worker rank=000 size=001
[rank=000 2025-03-17 10:08:41 N/A-0/1] Worker class: —
[rank=000 2025-03-17 10:08:41 N/A-0/1] Required tags: —
[rank=000 2025-03-17 10:08:41 N/A-0/1] Supported tags: —
[rank=000 2025-03-17 10:08:41 N/A-0/1] name: None
    tags: —
    required_tags: —
    resources: None
    max_tasks: None
    subworker_size: None
    subworker_count: None
    wall_time: None
[rank=000 2025-03-17 10:08:41 N/A-0/1] Main loop
[rank=000 2025-03-17 10:08:41 N/A-0/1] Running generated_wfs/init ...
entry:                    add  new      0/1        tree/generated_wfs/wf_0/generated_task1 
                          add  new      0/1        tree/generated_wfs/wf_0/generated_task2 
entry:                    add  new      0/1        tree/generated_wfs/wf_1/generated_task1 
                          add  new      0/1        tree/generated_wfs/wf_1/generated_task2 
entry:                    add  new      0/1        tree/generated_wfs/wf_2/generated_task1 
                          add  new      0/1        tree/generated_wfs/wf_2/generated_task2 
                       update  new      0/7        tree/generated_wfs/results 
[rank=000 2025-03-17 10:08:41 N/A-0/1] Task generated_wfs/init finished in 0:00:00.014636
[rank=000 2025-03-17 10:08:41 N/A-0/1] No available tasks, end worker main loop
$ tb ls
state    info       tags        worker         time     folder
──────── ────────── ─────────── ─────────── ─────────── ─────────────────────────────
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/init
new      1/7                                            tree/generated_wfs/results
new      1/1                                            tree/generated_wfs/wf_0/generated_task1
new      1/1                                            tree/generated_wfs/wf_0/generated_task2
new      1/1                                            tree/generated_wfs/wf_1/generated_task1
new      1/1                                            tree/generated_wfs/wf_1/generated_task2
new      1/1                                            tree/generated_wfs/wf_2/generated_task1
new      1/1                                            tree/generated_wfs/wf_2/generated_task2
done     0/0                    N/A-0/1        00:00:00 tree/task1

You can now see that three workflows were generated, each containing two tasks. The dependencies of the results task were also correctly updated to 0/6. We can now run the remaining tasks:

$ tb run tree
Starting worker rank=000 size=001
[rank=000 2025-03-17 10:08:41 N/A-0/1] Worker class: —
[rank=000 2025-03-17 10:08:41 N/A-0/1] Required tags: —
[rank=000 2025-03-17 10:08:41 N/A-0/1] Supported tags: —
[rank=000 2025-03-17 10:08:41 N/A-0/1] name: None
    tags: —
    required_tags: —
    resources: None
    max_tasks: None
    subworker_size: None
    subworker_count: None
    wall_time: None
[rank=000 2025-03-17 10:08:41 N/A-0/1] Main loop
[rank=000 2025-03-17 10:08:41 N/A-0/1] Running generated_wfs/wf_0/generated_task1 ...
[rank=000 2025-03-17 10:08:41 N/A-0/1] Task generated_wfs/wf_0/generated_task1 finished in 0:00:00.001390
[rank=000 2025-03-17 10:08:41 N/A-0/1] Running generated_wfs/wf_0/generated_task2 ...
[rank=000 2025-03-17 10:08:41 N/A-0/1] Task generated_wfs/wf_0/generated_task2 finished in 0:00:00.000815
[rank=000 2025-03-17 10:08:41 N/A-0/1] Running generated_wfs/wf_1/generated_task1 ...
[rank=000 2025-03-17 10:08:41 N/A-0/1] Task generated_wfs/wf_1/generated_task1 finished in 0:00:00.000820
[rank=000 2025-03-17 10:08:41 N/A-0/1] Running generated_wfs/wf_2/generated_task2 ...
[rank=000 2025-03-17 10:08:41 N/A-0/1] Task generated_wfs/wf_2/generated_task2 finished in 0:00:00.000821
[rank=000 2025-03-17 10:08:41 N/A-0/1] Running generated_wfs/wf_2/generated_task1 ...
[rank=000 2025-03-17 10:08:41 N/A-0/1] Task generated_wfs/wf_2/generated_task1 finished in 0:00:00.000825
[rank=000 2025-03-17 10:08:41 N/A-0/1] Running generated_wfs/wf_1/generated_task2 ...
[rank=000 2025-03-17 10:08:41 N/A-0/1] Task generated_wfs/wf_1/generated_task2 finished in 0:00:00.001370
[rank=000 2025-03-17 10:08:41 N/A-0/1] Running generated_wfs/results ...
[rank=000 2025-03-17 10:08:41 N/A-0/1] Task generated_wfs/results finished in 0:00:00.000237
[rank=000 2025-03-17 10:08:41 N/A-0/1] No available tasks, end worker main loop
$ tb ls
state    info       tags        worker         time     folder
──────── ────────── ─────────── ─────────── ─────────── ─────────────────────────────
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/init
done     7/7                    N/A-0/1        00:00:00 tree/generated_wfs/results
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_0/generated_task1
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_0/generated_task2
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_1/generated_task1
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_1/generated_task2
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_2/generated_task1
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_2/generated_task2
done     0/0                    N/A-0/1        00:00:00 tree/task1

As you can see all tasks are now in the state done and you can view the results

$ tb view tree/generated_wfs/results
name: generated_wfs/results
  location:        /home/myuser/tmprepo/tree/generated_wfs/results
  state:           done
  target:          define(…)
  wait for:        0 dependencies
  depth:           3
  source workflow: generated_wfs
  frozen by: (not frozen)

  latest handled inputs:
     None

  handlers:
    []

  handler data:
    <None>

  parents:
    generated_wfs/init
    generated_wfs/wf_0/generated_task1
    generated_wfs/wf_0/generated_task2
    generated_wfs/wf_1/generated_task1
    generated_wfs/wf_1/generated_task2
    generated_wfs/wf_2/generated_task1
    generated_wfs/wf_2/generated_task2

  input:
    ["define", {"__tb_implicit__": [["generated_wfs/init", {"__tb_type__": "ref", "index": [], "name": "generated_wfs/init"}]], "obj": {"wf_0/generated_task1": {"__tb_type__": "ref", "index": [], "name": "generated_wfs/wf_0/generated_task1"}, "wf_0/generated_task2": {"__tb_type__": "ref", "index": [], "name": "generated_wfs/wf_0/generated_task2"}, "wf_1/generated_task1": {"__tb_type__": "ref", "index": [], "name": "generated_wfs/wf_1/generated_task1"}, "wf_1/generated_task2": {"__tb_type__": "ref", "index": [], "name": "generated_wfs/wf_1/generated_task2"}, "wf_2/generated_task1": {"__tb_type__": "ref", "index": [], "name": "generated_wfs/wf_2/generated_task1"}, "wf_2/generated_task2": {"__tb_type__": "ref", "index": [], "name": "generated_wfs/wf_2/generated_task2"}}}]

  output:
    {'wf_0/generated_task1': 0, 'wf_0/generated_task2': 0, 'wf_1/generated_task1': 1, 'wf_1/generated_task2': 1, 'wf_2/generated_task1': 2, 'wf_2/generated_task2': 2}

Run information:
    Worker name: N/A-0/1
    Start time: 2025-03-17 10:08:41
    End time: 2025-03-17 10:08:41
    Duration: 0:00:00
    Error: None

No custom actions defined for this task.

Running everything in a single go

In the previous example we did the calculations step by step. It is instructive to see what happens if we try to run everything in a single go. First remove the tree using tb remove tree or alternatively make a new repository in a new folder and copy the files tasks.py and workflow2.py to the new folder:

$ tb remove tree --force
deleting: generated_wfs/results
deleting: generated_wfs/wf_2/generated_task2
deleting: generated_wfs/wf_1/generated_task1
deleting: generated_wfs/wf_0/generated_task1
deleting: generated_wfs/wf_0/generated_task2
deleting: generated_wfs/wf_1/generated_task2
deleting: generated_wfs/wf_2/generated_task1
deleting: generated_wfs/init
deleting: task1
9 task(s) were deleted.

Double check so that all tasks were removed

$ tb ls
state    info       tags        worker         time     folder
──────── ────────── ─────────── ─────────── ─────────── ─────────────────────────────

Now run the workflow and then run all tasks

$ tb workflow workflow2.py
entry:                    add  new      0/0        tree/task1 
                          add  new      0/1        tree/generated_wfs/init 
                          add  new      0/?        tree/generated_wfs/results 
entry:                   have  new      0/0        tree/task1 
$ tb run tree
Starting worker rank=000 size=001
[rank=000 2025-03-17 10:08:42 N/A-0/1] Worker class: —
[rank=000 2025-03-17 10:08:42 N/A-0/1] Required tags: —
[rank=000 2025-03-17 10:08:42 N/A-0/1] Supported tags: —
[rank=000 2025-03-17 10:08:42 N/A-0/1] name: None
    tags: —
    required_tags: —
    resources: None
    max_tasks: None
    subworker_size: None
    subworker_count: None
    wall_time: None
[rank=000 2025-03-17 10:08:42 N/A-0/1] Main loop
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running task1 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task task1 finished in 0:00:00.001382
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/init ...
entry:                    add  new      0/1        tree/generated_wfs/wf_0/generated_task1 
                          add  new      0/1        tree/generated_wfs/wf_0/generated_task2 
entry:                    add  new      0/1        tree/generated_wfs/wf_1/generated_task1 
                          add  new      0/1        tree/generated_wfs/wf_1/generated_task2 
entry:                    add  new      0/1        tree/generated_wfs/wf_2/generated_task1 
                          add  new      0/1        tree/generated_wfs/wf_2/generated_task2 
                       update  new      0/7        tree/generated_wfs/results 
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/init finished in 0:00:00.014037
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_0/generated_task1 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_0/generated_task1 finished in 0:00:00.000803
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_1/generated_task2 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_1/generated_task2 finished in 0:00:00.001255
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_2/generated_task2 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_2/generated_task2 finished in 0:00:00.000748
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_0/generated_task2 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_0/generated_task2 finished in 0:00:00.000714
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_2/generated_task1 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_2/generated_task1 finished in 0:00:00.000764
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_1/generated_task1 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_1/generated_task1 finished in 0:00:00.000757
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/results ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/results finished in 0:00:00.000255
[rank=000 2025-03-17 10:08:42 N/A-0/1] No available tasks, end worker main loop
$ tb ls
state    info       tags        worker         time     folder
──────── ────────── ─────────── ─────────── ─────────── ─────────────────────────────
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/init
done     7/7                    N/A-0/1        00:00:00 tree/generated_wfs/results
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_0/generated_task1
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_0/generated_task2
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_1/generated_task1
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_1/generated_task2
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_2/generated_task1
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_2/generated_task2
done     0/0                    N/A-0/1        00:00:00 tree/task1

As you can see all tasks were generated and run directly.

Using multiple finalizers

We will now see how to collect results from different tasks in different results tasks. First remove the tree:

$ tb remove tree --force
deleting: generated_wfs/results
deleting: generated_wfs/wf_2/generated_task1
deleting: generated_wfs/wf_1/generated_task2
deleting: generated_wfs/wf_2/generated_task2
deleting: generated_wfs/wf_0/generated_task1
deleting: generated_wfs/wf_0/generated_task2
deleting: generated_wfs/wf_1/generated_task1
deleting: generated_wfs/init
deleting: task1
9 task(s) were deleted.

Modify the input to the dynamical_workflow_generator decorator in workflow2.py and save to a new file workflow3.py

    @tb.dynamical_workflow_generator(
        {
            'results': '*/*',
            'results_task1': '*/generated_task1',
            'results_task2': '*/generated_task2',
        }
    )
    def generated_wfs(self):
        return tb.node('generate_wfs_from_list', inputs=self.task1)

We have now defined three seperate finalizers, one pointing to all generated tasks, and two pointing to generated_task1 and generated_task_2 respectively. Run the workflow and run all tasks.

$ tb workflow workflow3.py
entry:                    add  new      0/0        tree/task1 
                          add  new      0/1        tree/generated_wfs/init 
                          add  new      0/?        tree/generated_wfs/results 
                          add  new      0/?        tree/generated_wfs/results_task1 
                          add  new      0/?        tree/generated_wfs/results_task2 
entry:                   have  new      0/0        tree/task1 
$ tb run tree
Starting worker rank=000 size=001
[rank=000 2025-03-17 10:08:42 N/A-0/1] Worker class: —
[rank=000 2025-03-17 10:08:42 N/A-0/1] Required tags: —
[rank=000 2025-03-17 10:08:42 N/A-0/1] Supported tags: —
[rank=000 2025-03-17 10:08:42 N/A-0/1] name: None
    tags: —
    required_tags: —
    resources: None
    max_tasks: None
    subworker_size: None
    subworker_count: None
    wall_time: None
[rank=000 2025-03-17 10:08:42 N/A-0/1] Main loop
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running task1 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task task1 finished in 0:00:00.001409
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/init ...
entry:                    add  new      0/1        tree/generated_wfs/wf_0/generated_task1 
                          add  new      0/1        tree/generated_wfs/wf_0/generated_task2 
entry:                    add  new      0/1        tree/generated_wfs/wf_1/generated_task1 
                          add  new      0/1        tree/generated_wfs/wf_1/generated_task2 
entry:                    add  new      0/1        tree/generated_wfs/wf_2/generated_task1 
                          add  new      0/1        tree/generated_wfs/wf_2/generated_task2 
                       update  new      0/7        tree/generated_wfs/results 
                       update  new      0/4        tree/generated_wfs/results_task1 
                       update  new      0/4        tree/generated_wfs/results_task2 
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/init finished in 0:00:00.015194
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_1/generated_task1 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_1/generated_task1 finished in 0:00:00.001123
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_0/generated_task2 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_0/generated_task2 finished in 0:00:00.000742
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_2/generated_task1 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_2/generated_task1 finished in 0:00:00.000758
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_2/generated_task2 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_2/generated_task2 finished in 0:00:00.000769
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_0/generated_task1 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_0/generated_task1 finished in 0:00:00.000753
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/wf_1/generated_task2 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/wf_1/generated_task2 finished in 0:00:00.000778
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/results ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/results finished in 0:00:00.000226
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/results_task1 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/results_task1 finished in 0:00:00.000259
[rank=000 2025-03-17 10:08:42 N/A-0/1] Running generated_wfs/results_task2 ...
[rank=000 2025-03-17 10:08:42 N/A-0/1] Task generated_wfs/results_task2 finished in 0:00:00.000251
[rank=000 2025-03-17 10:08:42 N/A-0/1] No available tasks, end worker main loop
$ tb ls
state    info       tags        worker         time     folder
──────── ────────── ─────────── ─────────── ─────────── ─────────────────────────────
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/init
done     7/7                    N/A-0/1        00:00:00 tree/generated_wfs/results
done     4/4                    N/A-0/1        00:00:00 tree/generated_wfs/results_task1
done     4/4                    N/A-0/1        00:00:00 tree/generated_wfs/results_task2
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_0/generated_task1
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_0/generated_task2
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_1/generated_task1
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_1/generated_task2
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_2/generated_task1
done     1/1                    N/A-0/1        00:00:00 tree/generated_wfs/wf_2/generated_task2
done     0/0                    N/A-0/1        00:00:00 tree/task1

You can view the new results tasks using tb view

$ tb view tree/generated_wfs/results_task1
name: generated_wfs/results_task1
  location:        /home/myuser/tmprepo/tree/generated_wfs/results_task1
  state:           done
  target:          define(…)
  wait for:        0 dependencies
  depth:           3
  source workflow: generated_wfs
  frozen by: (not frozen)

  latest handled inputs:
     None

  handlers:
    []

  handler data:
    <None>

  parents:
    generated_wfs/init
    generated_wfs/wf_0/generated_task1
    generated_wfs/wf_1/generated_task1
    generated_wfs/wf_2/generated_task1

  input:
    ["define", {"__tb_implicit__": [["generated_wfs/init", {"__tb_type__": "ref", "index": [], "name": "generated_wfs/init"}]], "obj": {"wf_0/generated_task1": {"__tb_type__": "ref", "index": [], "name": "generated_wfs/wf_0/generated_task1"}, "wf_1/generated_task1": {"__tb_type__": "ref", "index": [], "name": "generated_wfs/wf_1/generated_task1"}, "wf_2/generated_task1": {"__tb_type__": "ref", "index": [], "name": "generated_wfs/wf_2/generated_task1"}}}]

  output:
    {'wf_0/generated_task1': 0, 'wf_1/generated_task1': 1, 'wf_2/generated_task1': 2}

Run information:
    Worker name: N/A-0/1
    Start time: 2025-03-17 10:08:42
    End time: 2025-03-17 10:08:42
    Duration: 0:00:00
    Error: None

No custom actions defined for this task.

As you can see the finalizer results_task1 only points to the output of the tasks generated_task1 etc.