Time and Sequence

Overview

This section covers the ordering and timing of tasks within a workflow. It explains methods for controlling the sequence and constraining task execution to control exactly when tasks are completed.

Time Constraints

Time constraints define how two tasks are related in terms of their start and end times. They control task sequencing and timing to maintain order in workflows. These constraints are evaluated during planning and replanning.

See Planning and Validation.

Available Constraint Types

  • START_TO_END: The second task cannot finish before the first task starts (ensures gap between start of first and end of second).

  • START_TO_START: The second task cannot start before the first task starts (minimum delay between task starts).

  • END_TO_END: The second task cannot finish before the first task finishes (both constrained to end in order).

  • END_TO_START: The second task cannot start before the first task finishes (enforces sequential execution with optional gap).

  • CONSISTENCY: Ensures timing consistency between two tasks (used in cross-batch constraints).

plate_incubation = ActionTask(...)
dispense_reagent = ActionTask(...)

# Defining the time constraint for incubation
incubation_constraint = TimeConstraint(
    id="incubate-to-dispense-constraint",
    constraint_type=TimeConstraintType.END_TO_START,
    start_task=plate_incubation,
    end_task=dispense_reagent,
    min_duration=600,
    max_duration=900,
    enable_detailed_coverage_calculation=True,
    auto_ignore_if_broken=False,
)

Note

Detailed coverage calculation

enable_detailed_coverage_calculation lets you trade planning detail for speed on large workflows. If a TimeConstraint spans many tasks, leaving this True (default) may slow pausing/planning; set it to False to improve performance.

Time Constraints are added to a Workflow object. See Workflow.

In the event a time constraint is broken (for example due to an error or a large difference between estimated and real time) an error will be raised with mitigation options. See Error Handling.

class linq.workflow.TimeConstraint(*, id: str, constraint_type: TimeConstraintType, start_task: ActionTask, end_task: ActionTask, min_duration: int | None, max_duration: int | None)

A time constraint for a sequence of tasks.

id: str

Unique ID for this time constraint.

constraint_type: TimeConstraintType

Time constraint type.

start_task: ActionTask

First task in the sequence that the constraint applies to.

end_task: ActionTask

Last task in the sequence that the constraint applies to.

min_duration: int | None

Minimum duration allowed for the sequence.

max_duration: int | None

Maximum duration allowed for the sequence.

Max Idle Time

Max idle time sets the maximum duration labware can be left unattended during a workflow. This is important for labware containing sensitive materials like cells or samples that require timely handling to maintain their integrity.

Labware Type Level

Define max idle time as a default for all labware of a specific type:

assay_plate = LabwareType(
   id="nest_flat_bottom_96_well_plate",
   max_idle_time=30  # Max idle time in seconds
)

Labware Instance Level

Override the labware type’s max idle time for a specific labware instance:

sample_plate = Labware(
    id="sample_plate_1",
    labware_type=assay_plate,
    starting_location=LabwareLocation(instrument=stacker, slot=1),
    max_idle_time=60,  # Override: 60 seconds for this instance (takes precedence over type-level)
)

Instance-level max_idle_time takes precedence over the labware type’s setting. If neither is specified, there is no idle time constraint.

Instrument Blocks

Instrument blocks prevent any task from executing on a specific instrument from a specified task until another specified task. This is useful when an instrument’s state is temporarily modified (e.g. holding a cap) meaning it is not strictly being used for a task, but is otherwise occupied.

A typical use case is an uncapping task which requires an instrument to hold a cap until the next task is completed. The specific uncapper can be blocked from use until a task completes.

Instrument blocks are added to a Workflow object. See Workflow.

class linq.workflow.InstrumentBlock(*, start_task: ActionTask, end_task: ActionTask)

An instrument block instruction, keeping the instrument from being used between two tasks.

Dependencies

Task dependencies ensure that certain tasks are completed before others can begin, maintaining proper task order. The first task of a chain of tasks will not have any dependencies. Any kind of task can have a dependency or be the target of a dependency.

Labware input and output creates labware dependencies automatically. In other words, if a given task has specified a previous task as containing labware that it needs as input, a dependency does not need to be specified. See Labware Flow.

Additional dependencies can be manually configured as follows:

dispense_reagent = ActionTask(...)

load_plate = ActionTask(
   id="load_plate",
   # ...
   dependencies=[dispense_reagent],
)

You can have multiple dependencies for the same task. Note that any type of task, e.g. a CodeTask or an ActionTask, can be specified in the dependency array or made dependent on any other task.

dependencies=[dispense_reagent, move_data],

Time Estimates

Time estimates approximate how long each task will take, used by the planner to optimize task scheduling and predict workflow duration.

If the actual time is different from the estimated time for a task when executing on hardware, a replan may be triggered. See Planning and Validation.

dispense_reagent = ActionTask(...)

load_plate = ActionTask(
   id="load_plate",
   # ...
   time_estimate=20,
)

Transport Matrix and Robots

In workflows, transport actions are implicit, meaning they happen automatically between tasks (see Tasks and Transport). The transport matrix allows you to configure estimated timings for movements between workbenches and robots. These timings are used during planning to calculate transport tasks when the workflow is executed.

The default_transport_time serves as the fallback time for any unspecified transport paths. However, you can customize the timing between specific robots by defining transport paths. This lets you control how long it takes to move between robots while accounting for transport layer movements, ensuring more accurate planning.

workcell = Workcell(

   # ...

   transport_matrix=TransportMatrix(
           default_transport_time=20,
           paths=[
               TransportPath(source="robot_1", destination="robot_1", time=55),
               TransportPath(source="robot_2", destination="robot_2", time=20),
               TransportPath(source="robot_1", destination="robot_2", time=90),
               TransportPath(source="robot_2", destination="robot_1", time=100),
           ],
       ),
   )
class linq.workcell.TransportMatrix(*, default_transport_time: int, paths: list[TransportPath])

The transport matrix contains all estimates for transport times between benches.

default_transport_time: int

Default transport time if no specific estimate for a path is given.

paths: list[TransportPath]

List of paths describing the time to transport labware from one bench to another.

Transport Timing with Predefined Configurations

When you reference a predefined transport configuration (instead of using inline timing estimates), the planning and timing behavior changes.

Transport Config Priority and Flexibility

You can define both inline transport_matrix and reference a predefined transport_config in the same Workcell. When both are specified and the planner uses transport_concurrency="transport_v3", the planner uses actual timings from the transport_config instead of estimates from transport_matrix.paths.

This allows for flexible deployment:

  • Start simple: Define workflows with inline transport_matrix estimates

  • Migrate gradually: Add transport_config reference at deployment time without modifying workflow code

  • Use measured timings: When transport_concurrency="transport_v3" is configured, the planner uses the transport config’s actual timings for more accurate scheduling

Impact on Planning

When a transport config is specified in your workcell and planning is configured with transport_concurrency="transport_v3":

  1. Planning uses the config’s timings — The planner uses actual transport timings from the config instead of estimates from transport_matrix.paths

  2. More accurate schedules — V3 configs provide measured timings, reducing planning uncertainty

  3. Consistent behavior — All workflows using the same workcell see identical transport behavior

  4. Versioning support — Changes to transport config create new versions, allowing rollback if needed

Note

When transport_concurrency="transport_v3" is used with both transport_config and transport_matrix.paths specified, the planner uses timings from the transport_config. For other transport_concurrency values, the behavior depends on the planning configuration. See Planning and Validation for details on transport_concurrency options.

For detailed guidance on defining, creating, and managing transport configurations, see Tasks and Transport.