Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
282 changes: 194 additions & 88 deletions src/user-guide/writing-workflows/scheduling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,29 @@ string>` which use a special syntax to define the dependencies between tasks:
* logical operators ``&`` (AND) and ``|`` (OR) can be used to write
:term:`conditional dependencies <conditional dependency>`.

The left side of a dependency arrow shows a logical combination of one or more task
outputs, and the right side shows which tasks to trigger when those outputs get
completed:
Comment on lines +30 to +32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should definately be two sentences.

Perhaps

Suggested change
The left side of a dependency arrow shows a logical combination of one or more task
outputs, and the right side shows which tasks to trigger when those outputs get
completed:
The left side of a dependency arrow shows a logical combination of one or more task outputs. When those outputs are complete the right side shows which tasks to trigger.


For example:

.. code-block:: cylc-graph

# baz will not be run until both foo and bar have succeeded
# run baz when both foo and bar have succeeded
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that sentence order matching time and logic order makes this easier to read.

However you can argue that framing the sentence in a different order may help people with different brains.

Suggested change
# run baz when both foo and bar have succeeded
# When foo AND bar have succeeded, run baz

foo:succeeded & bar:succeeded => baz

However, the ``:succeeded`` output is so important that Cylc allows a plain task name on
the left as shorthand:
Comment on lines +41 to +42
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that the first sentence is necessary.

Suggested change
However, the ``:succeeded`` output is so important that Cylc allows a plain task name on
the left as shorthand:
Requiring the ``:succeeded`` output is a sensible default. Cylc uses a task name without
an explicit output as a shorthand for ``:succeeded``:


.. code-block:: cylc-graph

# run baz when both foo and bar have succeeded
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# run baz when both foo and bar have succeeded
# When both foo AND bar have succeeded, run baz

foo & bar => baz


(Task outputs can appear on the right of a dependency as well, in which case the
expression declares task optionality as well as triggering - see below for more).
Comment on lines +50 to +51
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
(Task outputs can appear on the right of a dependency as well, in which case the
expression declares task optionality as well as triggering - see below for more).
(Task outputs can appear on the right of a dependency, in which case the
expression declares task optionality as well as triggering - see below for more).
Suggested change
(Task outputs can appear on the right of a dependency as well, in which case the
expression declares task optionality as well as triggering - see below for more).
.. seealso::
Task outputs can appear on the right of a dependency, in which case the
expression declares task optionality as well as triggering - see
:ref:`explict_outputs_on_the_right`.


Graph strings are configured under the :cylc:conf:`[scheduling][graph]` section
of the :cylc:conf:`flow.cylc` file:

Expand Down Expand Up @@ -1574,13 +1590,12 @@ Family triggers are also provided for task expiry:
.. warning::

The scheduler can only determine that a task has expired once it
enters the :term:`n=0 window <n-window>`.

This means that at least one of a task's prerequisites must be satisfied
before the task may expire.
enters the :term:`n=0 window <n-window>` - i.e., after its first
prerequisite gets satisfied.

So in the following example, the task ``b`` will only expire, **after**
the task ``a`` has succeeded:
In the following example, task ``b`` will only expire **after**
``a`` has succeeded, even though the expiry date is several
decades ago.

.. code-block:: cylc

Expand All @@ -1606,163 +1621,254 @@ This is a substantial topic, documented separately
in :ref:`Section External Triggers`.



.. _User Guide Required Outputs:
.. _required outputs:
.. _User Guide Optional Outputs:
.. _optional outputs:

Required Outputs
----------------
Required and Optional Outputs
-----------------------------

.. versionadded:: 8.0.0

:term:`Task outputs <task output>` in the :term:`graph` can be
:term:`required <required output>` (the default) or
:term:`optional <optional output>` (marked with ``?`` in the graph).
:term:`Task outputs <task output>` can be :term:`required <required output>`,
by default; or :term:`optional <optional output>`, if marked with ``?``.
Comment on lines +1634 to +1635
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
:term:`Task outputs <task output>` can be :term:`required <required output>`,
by default; or :term:`optional <optional output>`, if marked with ``?``.
:term:`Task outputs <task output>` can be :term:`required <required output>`,
by default; or :term:`optional <optional output>`.
Outputs are made optional by the addition of ``?`` in the graph.


Here, ``foo:succeed``, ``bar:x``, and ``baz:fail`` are all required outputs:

.. code-block:: cylc-graph

foo:succeeded # or "foo" for short, when referring to outputs
bar:x
baz:fail

And here, they are all optional outputs:

.. code-block:: cylc-graph

foo:succeeded? # or "foo?" for short
bar:x?
baz:fail?

Tasks are expected to complete required outputs at runtime, but
they don't have to complete optional outputs.

This allows the scheduler to correctly diagnose
:ref:`workflow completion`. [2]_
Optional outputs do not have to be completed by tasks at runtime. They are
primarily used for :ref:`Graph Branching`.

Tasks that achieve a :term:`final status` without completing their
outputs [3]_ are retained in the :term:`n=0 window <n-window>` pending user
intervention, e.g. to be retriggered after a bug fix.
Required outputs are expected to be completed at run time, which allows the
scheduler to correctly diagnose :ref:`Workflow Completion`. [2]_
Tasks that fail to complete required outputs [3]_
are retained in the :term:`n=0 window <n-window>` pending user intervention,
which will stall the workflow if there is nothing else to run.

.. note::
Tasks that achieve a final status without completing their outputs will
raise a warning and stall the workflow when there is nothing else for
the scheduler to run (see :ref:`workflow completion`). They also count
toward the :term:`runahead limit`.

This graph says task ``bar`` should trigger if ``foo`` succeeds:
To allow the workflow to continue normally, incomplete outputs can be
completed manually with ``cylc set``, or naturally by triggering the
tasks to rerun after fixing the underlying problem.

Incomplete tasks can also be removed with ``cylc remove``, which tells
the scheduler it no longer needs to run them - and, by implication,
anything downstream of them in the graph.


Interpreting Outputs in Dependencies
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Dependencies like ``foo:x => bar`` show which *tasks* (on the right) to trigger
off of which *task outputs* (on the left), and whether those outputs are
required or optional:

.. code-block:: cylc-graph

foo => bar # short for "foo:succeed => bar"
# trigger bar off of foo:x, AND foo:x is required:
foo:x => bar

Additionally, ``foo`` is required to succeed, because its success is not marked
as optional. If ``foo`` achieves a :term:`final status` without succeeding the
scheduler will not run ``bar``, and ``foo`` will be retained
in :term:`n=0 <n-window>` pending user intervention.
# trigger bar off of foo:y, AND foo:y is optional:
foo:y? => bar

Here, ``foo:succeed``, ``bar:x``, and ``baz:fail`` are all required outputs:
The left side shows *task outputs*, not tasks, but for convenience Cylc infers
the ``:succeeded`` output for plain task names on the left:

.. code-block:: cylc-graph

foo
bar:x
baz:fail
# This implies that foo:succeeded is required:
foo => ... # short for foo:succeeded => ...

# This implies that foo:succeeded is optional:
foo? => ... # short for foo:succeeded? => ...

Tasks that appear with only custom outputs in the graph are also required to succeed.
Here, ``foo:succeed`` is a required output, as well as ``foo:x``, unless it is
marked as optional elsewhere in the graph:
The right side shows *tasks* to trigger, not outputs, so we do not infer
the ``:succeeded`` output for plain task names on the right (however, see
:ref:`Explicit Outputs on the Right`):

.. code-block:: cylc-graph

foo:x => bar
# This DOES NOT imply that bar:succeeded is required:
... => bar


If a task generates multiple custom outputs, they should be "required" if you
expect them all to be completed every time the task runs. Here,
``model:file1``, ``model:file2``, and ``model:file3`` are all required outputs:
Outputs must be used consistently throughout the graph. The following graph fails
validation because ``foo:x`` can't be both required and optional:

.. code-block:: cylc-graph

model:file1 => proc1
model:file2 => proc2
model:file3 => proc3
# ERROR: foo:x can't be both required and optional:
foo:x => bar
foo:x? => baz


.. _optional outputs:
.. _User Guide Optional Outputs:
.. note::

Optional Outputs
----------------
Optional outputs do not alter triggering behaviour, they just tell the
scheduler what to do with the task if it does not complete the output
at runtime.
Comment on lines +1722 to +1724
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that you might be trying to say:

Suggested change
Optional outputs do not alter triggering behaviour, they just tell the
scheduler what to do with the task if it does not complete the output
at runtime.
A required output tells the scheduler that failure to emit that output will cause a stall.
An optional output tells that scheduler that failure to emit that output need not cause a stall.
It does not alter triggering behaviour.

I'm not sure about my alternative. But I think that you should say what the scheduler does with the information.

Copy link
Member Author

@hjoliver hjoliver Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've already explained above this what the scheduler does with incomplete outputs.

Here I'm primarily trying to emphasize that the triggering is exactly the same in both cases, which is commonly misunderstood by users.

Then I also go on to (re)explain just below exactly what difference it does make.

However, I'll try to rephrase this a bit - see what you think in the follow-up PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'm rethinking this ... maybe it's not helpful to explain it in that way.

In a sense an optional output on the left does make the triggering "optional", which is arguably "affecting triggering"...


.. versionadded:: 8.0.0
This dependency triggers ``bar`` only if ``foo:x`` is completed at runtime:

.. code-block:: cylc-graph

Optional outputs are marked with ``?``. They may or may not be completed by the
task at runtime.
foo:x => bar # foo:x is required

Like the first example above, the following graph says task ``bar`` should
trigger if ``foo`` succeeds:
This dependency also triggers ``bar`` only if ``foo:x`` is completed at runtime:

.. code-block:: cylc-graph

foo:x? => bar # foo:x is optional

The only difference is that in the first case ``foo`` will be retained as an
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retained?

Suggested change
The only difference is that in the first case ``foo`` will be retained as an
The only difference is that in the first case ``foo`` will be retained in the task pool as an

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(In the "n=0 window" these days, but yes).

incomplete task if it does not complete ``:x`` at runtime; and in the second
case, it will not.

.. code-block:: cylc-graph

foo? => bar # short for "foo:succeed? => bar"
Success and Failure Outputs
^^^^^^^^^^^^^^^^^^^^^^^^^^^

But now ``foo:succeed`` is optional so we might expect it to fail sometimes.
And if it does fail, it will not be retained in the
:term:`n=0 window <n-window>` as incomplete.
The ``:succeeded`` and ``:failed`` outputs have several special properties.

Here, ``foo:succeed``, ``bar:x``, and ``baz:fail`` are all optional outputs:
Firstly, success is required *by default* if not declared as required or optional
anywhere in the graph:

.. code-block:: cylc-graph

foo?
bar:x?
baz:fail?
# This does not imply bar:succeeded is required, but it is required by default:
... => bar

# foo:x is required, and foo:succeeded is also required by default:
foo:x => ...


Success and failure (of the same task) are mutually exclusive, so they must
both be optional if one is optional, or if they both appear in the graph:
Secondly, success and failure of a task are mutually exclusive opposites so
either one or the other can be required or they must both be optional:
Comment on lines +1760 to +1761
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Secondly, success and failure of a task are mutually exclusive opposites so
either one or the other can be required or they must both be optional:
Tasks **cannot** succeed **and** fail! Only one can be required. Both may be optional:


.. code-block:: cylc-graph

# OK: foo:succeeded is required, foo:failed not used:
foo => bar

# OK: foo:succeeded and foo:failed are both optional:
foo? => bar
foo:fail? => baz

# ERROR: foo:succeeded and foo:fail can't both be required:
foo => bar
foo:fail => baz

.. warning::
# ERROR: foo:fail can't be optional if foo:succeeded is required:
foo => bar
foo:fail? => baz


Custom Outputs
^^^^^^^^^^^^^^

If a task generates multiple related custom outputs they should all be required
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a bit misleading - it's totally right if you want to have

model:first_hour => standard_post_proc1
model:second_hour => standard_post_proc2

or

model:unstable? => high_detail_convection
model:stable? => fog_model

but what if you have both?

I think what this boils downs to is "please think carefully about whether custom outputs should stall the workflow if not emitted".

if you expect them all to be completed every time the task runs, or all optional
if you do not expect them to be completed every time:

Optional outputs must be marked as optional everywhere they appear in the
graph, to avoid ambiguity.
Here,
``:file1``, ``:file2``, and ``:file3`` are all required outputs of ``model``:

.. code-block:: cylc-graph

model:file1 => proc1
model:file2 => proc2
model:file3 => proc3


If a task generates multiple custom outputs, they should all be declared optional
if you do not expect them to be completed every time the task runs:
And here ``:x``, ``:y``, and ``:z`` are all optional outputs:

.. code-block:: cylc-graph

# model:x, :y, and :z are all optional outputs:
model:x? => proc-x
model:y? => proc-y
model:z? => proc-z

This is an example of :term:`graph branching` from optional outputs. Whether a
This is an example of :ref:`Graph Branching` from optional outputs. Whether a
particular branch is taken or not depends on which optional outputs are
completed at runtime. For more information see :ref:`Graph Branching`.
completed at runtime.


.. _explicit outputs on the right:

Explicit Outputs on the Right
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The right side of a dependency shows *tasks* to trigger, not outputs, so
we don't infer ``:succeeded`` for plain task names on the right.

Leaf tasks (with nothing downstream of them) can have optional outputs. In the
following graph, ``foo`` is required to succeed, but it doesn't matter whether
``bar`` succeeds or fails:
However, *explicit* outputs can be used on right sides if you like.
They must be consistent without all other mentions of the same output
throughout the graph.

.. code-block:: cylc-graph

foo => bar?
# trigger bar; AND bar:succeeded is required:
<outputs> => bar:succeeded

# trigger bar; AND bar:succeeded is optional
<outputs> => bar:succeeded?

# trigger bar; AND bar:succeeded is optional
<outputs> => bar?


.. note::

Optional outputs do not affect *triggering*. They just tell the scheduler
what to do with the task if it reaches a :term:`final status` without
completing the output.
Outputs on the right make dependencies harder to interpret because the
syntax suggests triggering an output rather than a task, which doesn't
make sense.

This graph triggers ``bar`` if ``foo`` succeeds, and does not trigger
``bar`` if ``foo`` fails:
If you see this, keep in mind that the syntax primarily shows what tasks
to trigger, and right side outputs, if present, are just a separate
declaration of output optionality.

.. code-block:: cylc-graph

foo => bar
It is never necessary to put outputs on the right of a dependency. The same
output will be declared elsewhere on the left if anything triggers off of it;
and if not, you can declare it with a lone node (no dependency arrow).

And so does this graph:
For example you don't need ``:y?`` on the right here:

.. code-block:: cylc-graph
.. code-block:: cylc-graph

foo:x => bar:y?

If ``bar:y?`` appears on the left elsewere in the graph:

foo? => bar
.. code-block:: cylc-graph

foo:x => bar
...
bar:y? => ... # (elsewhere)

The only difference is whether or not the scheduler regards ``foo`` as
incomplete if it fails.

And if it it does not appear elsewhere, just declare it separately with
no confusing dependency arrow:

.. code-block:: cylc-graph

foo:x => bar
bar:y?


Finish Triggers
Expand Down
Loading