Skill Tracer
Skills › Scientific Agent Skills › pylabrobot › liquid-handling.md

liquid-handling.md

229 lines · skills/pylabrobot/references/liquid-handling.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Liquid handling
2
3Verified against **PyLabRobot 0.2.2** on **2026-10-01**. Examples in this
4reference are planning or chatterbox-only. They are not authorization to connect
5to a robot.
6

Liquid handling

Verified against PyLabRobot 0.2.2 on 2026-10-01. Examples in this reference are planning or chatterbox-only. They are not authorization to connect to a robot.

Raw lines 1 to 6
7## Stable frontend and backend
8
9```python
10from pylabrobot.liquid_handling import LiquidHandler
11from pylabrobot.liquid_handling.backends import LiquidHandlerChatterboxBackend
12from pylabrobot.resources.hamilton import STARLetDeck
14lh = LiquidHandler(
15 backend=LiquidHandlerChatterboxBackend(num_channels=8),
16 deck=STARLetDeck(),
18await lh.setup() # prints operations; no hardware transport
19```
21For 0.2.2, the important frontend signatures are:
23```text
24pick_up_tips(tip_spots, use_channels=None, offsets=None, **backend_kwargs)
25drop_tips(tip_spots, use_channels=None, offsets=None,
26 allow_nonzero_volume=False, **backend_kwargs)
27return_tips(use_channels=None, allow_nonzero_volume=False, offsets=None,
28 **backend_kwargs)
29aspirate(resources, vols, use_channels=None, flow_rates=None, offsets=None,
30 liquid_height=None, blow_out_air_volume=None, spread="wide",
31 mix=None, **backend_kwargs)
32dispense(resources, vols, use_channels=None, flow_rates=None, offsets=None,
33 liquid_height=None, blow_out_air_volume=None, spread="wide",
34 mix=None, **backend_kwargs)
35```
37Volumes are microlitres (`uL`), coordinates/heights are millimetres (`mm`), and
38flow rates are `uL/s` unless the exact backend page says otherwise. Use lists
39whose lengths agree with selected resources/channels; do not rely on scalar
40broadcasting copied from an older example.

Stable frontend and backend

from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import LiquidHandlerChatterboxBackend
from pylabrobot.resources.hamilton import STARLetDeck

lh = LiquidHandler(
    backend=LiquidHandlerChatterboxBackend(num_channels=8),
    deck=STARLetDeck(),
)
await lh.setup()  # prints operations; no hardware transport

For 0.2.2, the important frontend signatures are:

pick_up_tips(tip_spots, use_channels=None, offsets=None, **backend_kwargs)
drop_tips(tip_spots, use_channels=None, offsets=None,
          allow_nonzero_volume=False, **backend_kwargs)
return_tips(use_channels=None, allow_nonzero_volume=False, offsets=None,
            **backend_kwargs)
aspirate(resources, vols, use_channels=None, flow_rates=None, offsets=None,
         liquid_height=None, blow_out_air_volume=None, spread="wide",
         mix=None, **backend_kwargs)
dispense(resources, vols, use_channels=None, flow_rates=None, offsets=None,
         liquid_height=None, blow_out_air_volume=None, spread="wide",
         mix=None, **backend_kwargs)

Volumes are microlitres (uL), coordinates/heights are millimetres (mm), and flow rates are uL/s unless the exact backend page says otherwise. Use lists whose lengths agree with selected resources/channels; do not rely on scalar broadcasting copied from an older example.

Raw lines 7 to 41
42## Safe operation shape
44With the software-only backend and already assigned resources:
46```python
47source.get_well("A1").tracker.set_volume(100.0) # bookkeeping only
48destination.get_well("A1").tracker.set_volume(0.0)
50await lh.pick_up_tips(tips["A1"])
51await lh.aspirate(
52 source["A1"],
53 vols=[25.0],
54 use_channels=[0],
55 flow_rates=[50.0],
56 liquid_height=[1.0],
58await lh.dispense(
59 destination["A1"],
60 vols=[25.0],
61 use_channels=[0],
62 flow_rates=[75.0],
63 liquid_height=[2.0],
65await lh.return_tips()
66```
68Before translating this to any physical system, verify:
70- resource and well identity, actual position, orientation, dimensions, and
71 reachability;
72- source fill volume **and dead volume**; destination capacity and headspace;
73- tip model, fitting, filter, capacity, rack state, liquid compatibility, and
74 channel compatibility;
75- 0-based `use_channels` mapping against the physical head and mounted tools;
76- volume, length, rate, and time units;
77- aspiration/dispense height, offset, rate, settling, blowout, mixing, air gaps,
78 surface behavior, and validated liquid class;
79- contamination grouping, filtered-tip requirement, tip reuse prohibition or
80 validated policy, waste route, and carryover controls.
82The bundled transfer planner makes the conservative choice of one new tip per
83CSV row.

Safe operation shape

With the software-only backend and already assigned resources:

source.get_well("A1").tracker.set_volume(100.0)  # bookkeeping only
destination.get_well("A1").tracker.set_volume(0.0)

await lh.pick_up_tips(tips["A1"])
await lh.aspirate(
    source["A1"],
    vols=[25.0],
    use_channels=[0],
    flow_rates=[50.0],
    liquid_height=[1.0],
)
await lh.dispense(
    destination["A1"],
    vols=[25.0],
    use_channels=[0],
    flow_rates=[75.0],
    liquid_height=[2.0],
)
await lh.return_tips()

Before translating this to any physical system, verify:

  • resource and well identity, actual position, orientation, dimensions, and reachability;
  • source fill volume and dead volume; destination capacity and headspace;
  • tip model, fitting, filter, capacity, rack state, liquid compatibility, and channel compatibility;
  • 0-based use_channels mapping against the physical head and mounted tools;
  • volume, length, rate, and time units;
  • aspiration/dispense height, offset, rate, settling, blowout, mixing, air gaps, surface behavior, and validated liquid class;
  • contamination grouping, filtered-tip requirement, tip reuse prohibition or validated policy, waste route, and carryover controls.

The bundled transfer planner makes the conservative choice of one new tip per CSV row.

Raw lines 42 to 84
85## `transfer()` is not the old plate-copy API
87In 0.2.2 the verified signature is:
89```text
90transfer(source: Well, targets: List[Well], source_vol=None, ratios=None,
91 target_vols=None, aspiration_flow_rate=None,
92 dispense_flow_rates=None, **backend_kwargs)
93```
95It represents distribution from one source to multiple targets. Old examples
96that pass parallel `source=source_plate["A1:H12"]`, `dest=...`, and `vols=...`
97do not match this stable signature. For one-to-one transfers, plan explicit
98aspirate/dispense pairs and validate channel/tip state.

transfer() is not the old plate-copy API

In 0.2.2 the verified signature is:

transfer(source: Well, targets: List[Well], source_vol=None, ratios=None,
         target_vols=None, aspiration_flow_rate=None,
         dispense_flow_rates=None, **backend_kwargs)

It represents distribution from one source to multiple targets. Old examples that pass parallel source=source_plate["A1:H12"], dest=..., and vols=... do not match this stable signature. For one-to-one transfers, plan explicit aspirate/dispense pairs and validate channel/tip state.

Raw lines 85 to 99
100## Tip tracking
102Enable tracking before operations:
104```python
105from pylabrobot.resources import set_tip_tracking
107set_tip_tracking(True)
108```
110Tip racks normally start populated; supported factories accept
111`with_tips=False`, and `TipRack.fill()`, `empty()`, and `set_tip_state(...)`
112modify planned state. `return_tips()` depends on operation history. Tip tracking
113can catch inconsistent planned operations, but it cannot detect whether a tip
114is physically present, seated, blocked, damaged, or the expected type.
116Never disable tracking merely to bypass `NoTipError` or `HasTipError`. Reconcile
117the physical deck and planned state instead.

Tip tracking

Enable tracking before operations:

from pylabrobot.resources import set_tip_tracking

set_tip_tracking(True)

Tip racks normally start populated; supported factories accept with_tips=False, and TipRack.fill(), empty(), and set_tip_state(...) modify planned state. return_tips() depends on operation history. Tip tracking can catch inconsistent planned operations, but it cannot detect whether a tip is physically present, seated, blocked, damaged, or the expected type.

Never disable tracking merely to bypass NoTipError or HasTipError. Reconcile the physical deck and planned state instead.

Raw lines 100 to 118
119## Volume tracking is bookkeeping
121```python
122from pylabrobot.resources import set_volume_tracking
124set_volume_tracking(True)
125well.tracker.set_volume(200.0)
126used_uL = well.tracker.get_used_volume()
127free_uL = well.tracker.get_free_volume()
128```
130`set_volume()` and `load_state()` do not validate the initial physical value
131against capacity. Validate finite, nonnegative starting volumes and capacity
132before seeding trackers. The tracker does not track chemical composition:
133deprecated `get_liquids()` returns unknown liquid identity.
135The `VolumeTracker` updates planned volumes and can reject under-aspiration,
136tip overfill, or well overfill. It does **not** measure a meniscus or confirm
137liquid identity. Initial state must come from a trusted preparation record and
138human reconciliation.
140Keep dead volume separate from geometric capacity. The tracker may allow a
141withdrawal that is physically unreliable because of vessel shape, tilt,
142surface tension, foam, viscosity, or required submersion.

Volume tracking is bookkeeping

from pylabrobot.resources import set_volume_tracking

set_volume_tracking(True)
well.tracker.set_volume(200.0)
used_uL = well.tracker.get_used_volume()
free_uL = well.tracker.get_free_volume()

set_volume() and load_state() do not validate the initial physical value against capacity. Validate finite, nonnegative starting volumes and capacity before seeding trackers. The tracker does not track chemical composition: deprecated get_liquids() returns unknown liquid identity.

The VolumeTracker updates planned volumes and can reject under-aspiration, tip overfill, or well overfill. It does not measure a meniscus or confirm liquid identity. Initial state must come from a trusted preparation record and human reconciliation.

Keep dead volume separate from geometric capacity. The tracker may allow a withdrawal that is physically unreliable because of vessel shape, tilt, surface tension, foam, viscosity, or required submersion.

Raw lines 119 to 143
144### Physical liquid detection is backend-specific
146Hamilton STAR liquid-level detection is a separate physical feature. Stable
147STAR docs expose backend kwargs such as `lld_mode`, `immersion_depth`, and
148`surface_following_distance`. It is not portable to all backends and is not
149enabled by volume tracking. Validate the model, sensors, consumables, conductive
150properties, firmware behavior, failure handling, and channel-specific values
151before considering it.

Physical liquid detection is backend-specific

Hamilton STAR liquid-level detection is a separate physical feature. Stable STAR docs expose backend kwargs such as lld_mode, immersion_depth, and surface_following_distance. It is not portable to all backends and is not enabled by volume tracking. Validate the model, sensors, consumables, conductive properties, firmware behavior, failure handling, and channel-specific values before considering it.

Raw lines 144 to 152
153## Liquid classes
155There is no stable generic import:
157```python
158# Invalid in 0.2.2:
159# from pylabrobot.liquid_handling import LiquidClass
160```
162Hamilton liquid classes are vendor-specific:
164```python
165from pylabrobot.liquid_handling.liquid_classes.hamilton import HamiltonLiquidClass
166from pylabrobot.liquid_handling.liquid_classes.hamilton.star import (
167 HighVolumeFilter_Water_DispenseSurface_Part,
170await lh.aspirate(
171 source["A1"],
172 vols=[100.0],
173 hamilton_liquid_classes=[
174 HighVolumeFilter_Water_DispenseSurface_Part
175 ],
177```
179This STAR example is **illustrative and source-verified only**; it was not
180executed on hardware. The keyword is not a universal frontend contract.
181`TecanLiquidClass` and `get_liquid_class` exist under
182`pylabrobot.liquid_handling.liquid_classes.tecan`, but are a different
183vendor-specific system.
185Do not select a class from its name alone. Review liquid, tip, head, volume
186range, jet/surface mode, vessel geometry, calibration curve, flow, settling,
187transport air, blowout, and firmware/model applicability. Custom classes need
188documented gravimetric or assay validation and operator approval.

Liquid classes

There is no stable generic import:

# Invalid in 0.2.2:
# from pylabrobot.liquid_handling import LiquidClass

Hamilton liquid classes are vendor-specific:

from pylabrobot.liquid_handling.liquid_classes.hamilton import HamiltonLiquidClass
from pylabrobot.liquid_handling.liquid_classes.hamilton.star import (
    HighVolumeFilter_Water_DispenseSurface_Part,
)

await lh.aspirate(
    source["A1"],
    vols=[100.0],
    hamilton_liquid_classes=[
        HighVolumeFilter_Water_DispenseSurface_Part
    ],
)

This STAR example is illustrative and source-verified only; it was not executed on hardware. The keyword is not a universal frontend contract. TecanLiquidClass and get_liquid_class exist under pylabrobot.liquid_handling.liquid_classes.tecan, but are a different vendor-specific system.

Do not select a class from its name alone. Review liquid, tip, head, volume range, jet/surface mode, vessel geometry, calibration curve, flow, settling, transport air, blowout, and firmware/model applicability. Custom classes need documented gravimetric or assay validation and operator approval.

Raw lines 153 to 189
190## Mixing, serial dilution, and multichannel work
192- Make every aspirate/dispense pair explicit in the plan.
193- Check the tip's current planned volume before mixing.
194- Keep the mix volume below both tip capacity and usable well volume.
195- For serial dilutions, define where a tip may be reused and where a fresh tip
196 is mandatory; do not infer contamination safety from row order.
197- Confirm well order and channel order. A plate slice is not proof that the
198 physical channels align with those wells.
199- Include residual volume, pre-wet cycles, adsorption, foaming, and carryover in
200 the acceptance criteria.

Mixing, serial dilution, and multichannel work

  • Make every aspirate/dispense pair explicit in the plan.
  • Check the tip's current planned volume before mixing.
  • Keep the mix volume below both tip capacity and usable well volume.
  • For serial dilutions, define where a tip may be reused and where a fresh tip is mandatory; do not infer contamination safety from row order.
  • Confirm well order and channel order. A plate slice is not proof that the physical channels align with those wells.
  • Include residual volume, pre-wet cycles, adsorption, foaming, and carryover in the acceptance criteria.
Raw lines 190 to 201
202## Deterministic preflight
204```bash
205python3 skills/pylabrobot/scripts/plan_transfers.py \
206 --manifest tests/pylabrobot/fixtures/protocol_manifest.json \
207 --transfers tests/pylabrobot/fixtures/transfers.csv
208```
210The CSV header is exact and fixed. Unknown columns, duplicate IDs, unsupported
211tip policies, missing source or destination starting volumes, non-finite numbers, out-of-grid wells,
212unallowlisted liquid classes/tips, excess rates/heights/volumes, channel
213mismatches, dead-volume violations, destination overflow, and insufficient tips
214fail closed.

Deterministic preflight

python3 skills/pylabrobot/scripts/plan_transfers.py \
  --manifest tests/pylabrobot/fixtures/protocol_manifest.json \
  --transfers tests/pylabrobot/fixtures/transfers.csv

The CSV header is exact and fixed. Unknown columns, duplicate IDs, unsupported tip policies, missing source or destination starting volumes, non-finite numbers, out-of-grid wells, unallowlisted liquid classes/tips, excess rates/heights/volumes, channel mismatches, dead-volume violations, destination overflow, and insufficient tips fail closed.

Raw lines 202 to 215
Runs code python3 skills/pylabrobot/scripts/plan_transfers.py :205
216## Sources
218Checked **2026-10-01**:
220- [Hosted basic Hamilton tutorial](https://docs.pylabrobot.org/stable/user_guide/00_liquid-handling/hamilton-star/basic.html)
221 — 0.2.1 historical guide; 0.2.2 signatures verified from released source.
222- [Hosted liquid-handling API](https://docs.pylabrobot.org/stable/api/pylabrobot.liquid_handling.html)
223 — frontend/backend split and `LiquidHandlerChatterboxBackend`.
224- [Hosted tracker guide](https://docs.pylabrobot.org/stable/user_guide/machine-agnostic-features/using-trackers.html)
225 — tip/volume tracker behavior.
226- [Hosted Hamilton liquid classes](https://docs.pylabrobot.org/stable/user_guide/00_liquid-handling/hamilton-star/hamilton-liquid-classes.html)
227 and [STAR liquid-level detection](https://docs.pylabrobot.org/stable/user_guide/00_liquid-handling/hamilton-star/star_lld.html).
228- [released 0.2.2 liquid-handler source](https://pypi.org/project/PyLabRobot/0.2.2/#files)
229 — signatures and import verification; released 2026-07-30.

Sources

Checked 2026-10-01:

Raw lines 216 to 229