diff --git a/docs/conduit_filters_bypass.svg b/docs/conduit_filters_bypass.svg
new file mode 100644
index 0000000..d72f01b
--- /dev/null
+++ b/docs/conduit_filters_bypass.svg
@@ -0,0 +1 @@
+
diff --git a/docs/conduit_filters_bypass.ymmsl b/docs/conduit_filters_bypass.ymmsl
new file mode 100644
index 0000000..4e46bc6
--- /dev/null
+++ b/docs/conduit_filters_bypass.ymmsl
@@ -0,0 +1,42 @@
+ymmsl_version: v0.2
+
+description: macro-meso-micro-pico with bypass conduits needing filters
+
+models:
+ macro_meso_micro_pico_filtered:
+ components:
+ macro:
+ ports:
+ o_i: bc_out to_micro to_pico
+ s: bc_in from_micro from_pico
+ description: ''
+ meso:
+ ports:
+ f_init: init_in
+ o_i: bc_out
+ s: bc_in
+ o_f: final_out
+ description: ''
+ micro:
+ ports:
+ f_init: init_in bypass_in
+ o_i: bc_out
+ s: bc_in
+ o_f: bypass_out final_out
+ description: ''
+ pico:
+ ports:
+ f_init: init_in bypass_in
+ o_f: bypass_out final_out
+ description: ''
+ conduits:
+ macro.bc_out: meso.init_in
+ meso.final_out: macro.bc_in
+ meso.bc_out: micro.init_in
+ micro.final_out: meso.bc_in
+ micro.bc_out: pico.init_in
+ pico.final_out: micro.bc_in
+ macro.to_micro: repeat micro.bypass_in
+ micro.bypass_out: last macro.from_micro
+ macro.to_pico: repeat repeat pico.bypass_in
+ pico.bypass_out: last last macro.from_pico
diff --git a/docs/describing_models.rst b/docs/describing_models.rst
index 531ab77..56ce615 100644
--- a/docs/describing_models.rst
+++ b/docs/describing_models.rst
@@ -132,6 +132,246 @@ separated by periods. Depending on the context, this may represent a name in a n
or an attribute of an object (as we will see below with Conduits).
+Timelines
+`````````
+
+Different components of a coupled simulation typically run at their own pace: a fast,
+detailed micro model may take many small steps for every single step of the macro model
+driving it, and a meso model may sit somewhere in between the two. yMMSL captures this
+idea of "running at a different pace" as a *timeline*.
+
+Timelines are determined separately for each model under ``models``, and are named
+relative to that model. Each component has two timelines associated with it:
+
+- Its *parent timeline* is the timeline of whatever calls it, i.e. the timeline on which
+ the messages to its ``f_init`` ports are sent and the messages from its ``o_f`` ports
+ are received. For a component that isn't called by any other component in the model,
+ the parent timeline is empty.
+- Its *component timeline* is the timeline it runs on itself. Its name is the name of
+ the parent timeline followed by the name of the component, joined with a colon. A
+ component that isn't called by anything therefore gets a timeline named after itself.
+
+A component is called by another one through a call-and-release coupling, in which the
+caller's ``o_i`` port sends to the callee's ``f_init`` port and the callee's ``o_f``
+port sends back to the caller's ``s`` port. The caller's component timeline then becomes
+the callee's parent timeline, so the callee's component timeline is nested inside the
+caller's. Every level of nesting adds one more name, giving each timeline in the model
+an addressable path, a bit like a folder structure. A dispatch coupling, in which one
+component's ``o_f`` port sends to the next component's ``f_init`` port, does not add a
+level: the second component gets the same parent timeline as the first, so the two end
+up side by side.
+
+Components and their ports are related to timelines in slightly different ways. A
+component's ``o_i`` and ``s`` ports send and receive during its run, so they are on its
+component timeline. Its ``f_init`` and ``o_f`` ports sit at the beginning and the end of
+the component timeline, where the component hands over to and from its caller, so the
+messages they receive and send belong to the parent timeline.
+
+To make a valid conduit, you should connect two ports whose messages live on the same
+timeline. :ref:`Conduit filters` and :ref:`Matching timelines` relax this rule
+in specific cases.
+
+Take a macro model that calls a meso model in a loop, and where that meso model in turn
+calls a micro model in its own loop:
+
+.. literalinclude:: timelines_macro_meso_micro.ymmsl
+ :caption: ``docs/timelines_macro_meso_micro.ymmsl``
+ :language: yaml
+
+.. figure:: timelines_macro_meso_micro.svg
+ :align: center
+ :alt: macro connects to meso through F_INIT/O_F and O_I/S ports, and meso connects to
+ micro the same way, producing three nested timelines.
+
+ The same model, visualized with `ymmsl2svg
+ `_. The order of the boxes in the figure,
+ from top to bottom, mirrors the nesting in time: ``macro`` first, then ``meso``
+ below it, then ``micro`` below ``meso``.
+
+``macro`` isn't called by anything, so its parent timeline is empty and its
+component timeline is ``macro``. ``macro`` calls ``meso``, so ``meso``'s parent
+timeline is ``macro`` and its component timeline is ``macro:meso``. Likewise,
+``micro`` has parent timeline ``macro:meso`` and component timeline
+``macro:meso:micro``.
+
+The conduit from ``macro.bc_out`` to ``meso.init_in`` is valid because ``bc_out`` is an
+``o_i`` port on ``macro``'s component timeline ``macro``, and the messages received by
+the ``f_init`` port ``init_in`` are on ``meso``'s parent timeline, which is also
+``macro``. The same reasoning applies to the other three conduits.
+
+In a dispatch coupling, by contrast, no extra level is added. Here, ``macro`` calls
+``solver``, which hands its result over to ``analysis``, which in turn returns to
+``macro``:
+
+.. literalinclude:: timelines_dispatch.ymmsl
+ :caption: ``docs/timelines_dispatch.ymmsl``
+ :language: yaml
+
+.. figure:: timelines_dispatch.svg
+ :align: center
+ :alt: macro connects through its O_I port to solver's F_INIT port, solver's O_F port
+ connects to analysis's F_INIT port, and analysis's O_F port connects back to
+ macro's S port. solver and analysis are drawn side by side below macro.
+
+ The same model, visualized with `ymmsl2svg
+ `_. ``solver`` and ``analysis`` are drawn
+ side by side below ``macro``, since they share the same parent timeline.
+
+``solver`` is called by ``macro``, so its parent timeline is ``macro``. ``analysis``
+receives its ``f_init`` message from ``solver``'s ``o_f`` port, and those messages are on
+``solver``'s parent timeline, so ``analysis`` gets that same parent timeline ``macro``.
+The two components therefore end up side by side, on component timelines
+``macro:solver`` and ``macro:analysis``.
+
+None of these timelines are written in the yMMSL file itself: yMMSL works them out
+automatically from how the components are wired together with conduits.
+
+A single component can also be connected to more than one timeline at once, for example
+when it drives two other components that run at different rates. ``macro`` calling
+``micro1`` in one loop and ``micro2`` in a separate loop puts ``micro1`` and ``micro2`` on
+two independent sub-timelines of ``macro``. The following example shows how you can
+use ``timeline :`` to indicate that the ports connecting to ``micro1`` belong to
+a different subtimeline than the ports connecting to ``micro2``:
+
+.. literalinclude:: timelines_two_subtimelines.ymmsl
+ :caption: ``docs/timelines_two_subtimelines.ymmsl``
+ :language: yaml
+
+.. figure:: timelines_two_subtimelines.svg
+ :align: center
+ :alt: macro has two separate pairs of O_I/S ports, one connecting down to micro1 and
+ one connecting down to micro2, side by side.
+
+ The same model, visualized with `ymmsl2svg
+ `_. ``macro``'s two named timelines are drawn
+ side by side beneath it, each with its own pair of ports, one leading to ``micro1``
+ and the other to ``micro2``.
+
+A named sub-timeline is written as the component name, a period, and the annotation.
+Here, ``macro``'s first pair of O_I and S ports is on timeline ``macro.tl1`` and its
+second pair on ``macro.tl2``, which puts ``micro1`` on ``macro.tl1:micro1`` and
+``micro2`` on ``macro.tl2:micro2``. A timeline annotation must be a single name without
+periods, so ``timeline tl1:`` is fine but ``timeline sub.tl1:`` is not.
+
+Matching timelines
+^^^^^^^^^^^^^^^^^^^
+
+The timeline hierarchy above is worked out automatically from how the ``f_init``/``o_f``
+and ``o_i``/``s`` ports are wired together, and a conduit is only valid if the messages
+on both of its ends are on the same timeline. Sometimes, though, two components step
+through the same time points without one being nested inside the other's timeline.
+``matching_timelines`` lets you declare the timelines of such components equivalent, so
+that a conduit can still connect their ports directly:
+
+.. code-block:: yaml
+ :caption: Declaring matching timelines
+
+ components:
+ left:
+ ports:
+ o_i: out
+ s: in
+ description: Left side of the domain
+ right:
+ ports:
+ o_i: out
+ s: in
+ description: Right side of the domain
+
+ matching_timelines:
+ main: left right
+
+ conduits:
+ left.out: right.in
+ right.out: left.in
+
+``left`` and ``right`` each run on their own component timeline, ``left`` and
+``right``, and their ``o_i`` and ``s`` ports are on those timelines. While running, they
+exchange messages with each other directly through these ports, in an interact coupling.
+Because the two timelines are different, a conduit between these ports would not be
+allowed. The components do however step through the same time points, so their timelines
+are equivalent even though they are not the same. The entry under ``matching_timelines``
+declares exactly that, so that the conduits connecting them are valid after all.
+
+Each entry has a *head*, written on the left of the colon, and the timelines that match
+it, written on the right. The head doesn't have to be one of the component timelines:
+here it is a separate name, ``main``, so that neither ``left`` nor ``right`` is singled
+out as the one the other follows. If one of the timelines does lead, as in the time
+bridge example below, you can use that timeline as the head instead.
+
+Matching timelines are also what makes a time bridge work. A time bridge lets two
+components ``a`` and ``b`` that take different time steps exchange messages while they
+run. The bridge sits in between and converts the messages from one side to the other,
+so that each component receives messages with time stamps it can use. To do so, the
+bridge runs on timelines of its own: a named sub-timeline for each side, on which it
+follows the time points of the component on that side:
+
+.. code-block:: yaml
+ :caption: Two components on different timelines, connected through a time bridge
+
+ components:
+ a:
+ ports:
+ o_i: state_out
+ s: state_in
+ description: A model with its own time steps
+ b:
+ ports:
+ o_i: state_out
+ s: state_in
+ description: A model with different time steps
+ bridge:
+ ports:
+ timeline a_side:
+ o_i: a_out
+ s: a_in
+ timeline b_side:
+ o_i: b_out
+ s: b_in
+ description: Time bridge that converts messages between a and b
+
+ matching_timelines:
+ a: bridge.a_side
+ b: bridge.b_side
+
+ conduits:
+ a.state_out: bridge.a_in
+ bridge.a_out: a.state_in
+ b.state_out: bridge.b_in
+ bridge.b_out: b.state_in
+
+Like any component, the bridge runs on its own timelines, ``bridge.a_side`` and
+``bridge.b_side``, which differ from those of ``a`` and ``b``. Without
+``matching_timelines``, none of the conduits in this example would therefore be valid.
+But each side of the bridge does step through the same time points as the component on
+that side, and the two entries under ``matching_timelines`` declare exactly that:
+``bridge.a_side`` is equivalent to ``a``, and ``bridge.b_side`` to ``b``.
+
+Deeper timelines are matched by writing out their full path, relative to the model that
+contains the ``matching_timelines`` declaration. In the two-subtimelines example above,
+for instance, ``micro1`` and ``micro2`` could be declared equivalent from within
+``two_subtimelines_model`` with ``macro.tl1:micro1: macro.tl2:micro2``. Matching timelines are taken into account after
+applying any conduit filters, so a conduit with a ``repeat`` or ``last`` filter can also
+connect to a timeline that matches the one it is filtered to.
+
+On the Python side, ``matching_timelines`` is a list of
+:class:`.ymmsl.v0_2.MatchingTimelines` objects, each representing a set of equivalent
+timelines, with a ``head`` attribute and a ``matches`` attribute holding the full set,
+including the head.
+
+A head can have more than one match, as ``main`` does in the first example. The matches
+can be written as a whitespace-separated string, as above, or, equivalently, as a YAML
+list:
+
+.. code-block:: yaml
+ :caption: The same matching timelines, as a YAML list
+
+ matching_timelines:
+ main:
+ - left
+ - right
+
+
Conduits
````````
@@ -186,6 +426,65 @@ sender:
print(conduits[0]) # output: Conduit(sender.port -> receiver1.port)
print(conduits[1]) # output: Conduit(sender.port -> receiver2.port)
+Conduit filters
+^^^^^^^^^^^^^^^
+
+A conduit connects two components that call each other directly, for example ``macro``
+and ``meso``, or ``meso`` and ``micro``. ``macro`` and ``micro`` are not directly
+connected in this sense: ``meso`` sits between them. Connecting ``macro`` and ``micro``
+directly, bypassing ``meso``, means their pace no longer matches: ``micro`` is still
+called many times for every step ``macro`` takes, and still produces a message on
+every one of those calls, even though there is no longer a ``meso`` in between to
+absorb the difference. A conduit filter reconciles that mismatch.
+
+Extending the macro-meso-micro example from :ref:`Timelines` with a fourth level,
+``pico``, called by ``micro``, and adding conduits that bypass the levels in between
+shows both kinds of filters in use, including combinations of them:
+
+.. literalinclude:: conduit_filters_bypass.ymmsl
+ :caption: ``docs/conduit_filters_bypass.ymmsl``
+ :language: yaml
+
+.. figure:: conduit_filters_bypass.svg
+ :align: center
+ :alt: macro, meso, micro and pico are nested inside each other. Extra pairs of
+ conduits connect macro directly to micro, bypassing meso, and macro directly
+ to pico, bypassing meso and micro.
+
+ The same model, visualized with `ymmsl2svg
+ `_.
+
+``macro`` produces the ``to_micro`` message once, but ``micro`` is called many times
+for every step of ``macro`` and needs the message on each of those calls. The conduit
+from ``macro.to_micro`` to ``micro.bypass_in`` uses a ``repeat`` filter for this: the
+single message ``macro`` sends is resent to ``micro`` every time it runs, without
+``meso`` having to relay it.
+
+The reverse happens on the way back: ``micro`` produces a ``bypass_out`` message on
+every one of its many runs, but ``macro`` still expects only one message per step. The
+conduit from ``micro.bypass_out`` to ``macro.from_micro`` uses a ``last`` filter to
+reduce those many messages down to the single most recently produced one.
+
+- ``repeat`` and ``pad`` go from the shallower side to the deeper one: a single message
+ is repeated, or followed by empty messages, to match every time the deeper side
+ receives.
+- ``last`` goes from the deeper side back to the shallower one: of the many messages
+ produced, only the last one is passed on.
+
+Filters are written in front of the receiver, and each filter bridges exactly one level
+of nesting. To skip more than one level, you combine multiple filters on the same
+conduit. The conduits between ``macro`` and ``pico`` skip both ``meso`` and ``micro``,
+so each of them needs two filters. On the way down, ``repeat repeat`` repeats
+``macro``'s single message for every call of ``micro`` inside ``meso``, and then again
+for every call of ``pico`` inside ``micro``. On the way back up, ``last last`` first
+reduces ``pico``'s many messages to the last one per call of ``micro``, and then those
+to the last one per call of ``meso``, so that ``macro`` again receives a single message.
+
+The number of filters has to equal the number of levels skipped: with only a single
+``repeat`` on the conduit to ``pico``, the timelines on its two ends would not match and
+the model would be rejected.
+
+
Nesting models
``````````````
diff --git a/docs/timelines_dispatch.svg b/docs/timelines_dispatch.svg
new file mode 100644
index 0000000..edadc4b
--- /dev/null
+++ b/docs/timelines_dispatch.svg
@@ -0,0 +1 @@
+
diff --git a/docs/timelines_dispatch.ymmsl b/docs/timelines_dispatch.ymmsl
new file mode 100644
index 0000000..7f51dcd
--- /dev/null
+++ b/docs/timelines_dispatch.ymmsl
@@ -0,0 +1,26 @@
+ymmsl_version: v0.2
+
+description: A dispatch coupling inside a call-and-release loop
+
+models:
+ dispatch_model:
+ components:
+ macro:
+ ports:
+ o_i: state_out
+ s: state_in
+ description: Macro model
+ solver:
+ ports:
+ f_init: init_in
+ o_f: final_out
+ description: Computes a new state
+ analysis:
+ ports:
+ f_init: init_in
+ o_f: final_out
+ description: Analyses the new state
+ conduits:
+ macro.state_out: solver.init_in
+ solver.final_out: analysis.init_in
+ analysis.final_out: macro.state_in
diff --git a/docs/timelines_macro_meso_micro.svg b/docs/timelines_macro_meso_micro.svg
new file mode 100644
index 0000000..f19f6b6
--- /dev/null
+++ b/docs/timelines_macro_meso_micro.svg
@@ -0,0 +1 @@
+
diff --git a/docs/timelines_macro_meso_micro.ymmsl b/docs/timelines_macro_meso_micro.ymmsl
new file mode 100644
index 0000000..f815651
--- /dev/null
+++ b/docs/timelines_macro_meso_micro.ymmsl
@@ -0,0 +1,29 @@
+ymmsl_version: v0.2
+
+description: A macro-meso-micro model, three levels of timelines
+
+models:
+ macro_meso_micro_model:
+ components:
+ macro:
+ ports:
+ o_i: bc_out
+ s: bc_in
+ description: ''
+ meso:
+ ports:
+ f_init: init_in
+ o_i: bc_out
+ s: bc_in
+ o_f: final_out
+ description: ''
+ micro:
+ ports:
+ f_init: init_in
+ o_f: final_out
+ description: ''
+ conduits:
+ macro.bc_out: meso.init_in
+ meso.final_out: macro.bc_in
+ meso.bc_out: micro.init_in
+ micro.final_out: meso.bc_in
diff --git a/docs/timelines_two_subtimelines.svg b/docs/timelines_two_subtimelines.svg
new file mode 100644
index 0000000..4126c23
--- /dev/null
+++ b/docs/timelines_two_subtimelines.svg
@@ -0,0 +1 @@
+
diff --git a/docs/timelines_two_subtimelines.ymmsl b/docs/timelines_two_subtimelines.ymmsl
new file mode 100644
index 0000000..221da3d
--- /dev/null
+++ b/docs/timelines_two_subtimelines.ymmsl
@@ -0,0 +1,31 @@
+ymmsl_version: v0.2
+
+description: One component (macro) connected to two independent timelines
+
+models:
+ two_subtimelines_model:
+ components:
+ macro:
+ ports:
+ timeline tl1:
+ o_i: micro1_out
+ s: micro1_in
+ timeline tl2:
+ o_i: micro2_out
+ s: micro2_in
+ description: ''
+ micro1:
+ ports:
+ f_init: init_in
+ o_f: final_out
+ description: ''
+ micro2:
+ ports:
+ f_init: init_in
+ o_f: final_out
+ description: ''
+ conduits:
+ macro.micro1_out: micro1.init_in
+ micro1.final_out: macro.micro1_in
+ macro.micro2_out: micro2.init_in
+ micro2.final_out: macro.micro2_in