skills/pylabrobot/references/liquid-handling.md
Verified against PyLabRobot 0.2.1 on 2026-07-23. Examples in this reference are planning or chatterbox-only. They are not authorization to connect to a robot.
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.1, 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.
With the software-only backend and already assigned resources:
source.get_well("A1").tracker.set_volume(100.0) # bookkeeping only
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:
use_channels mapping against the physical head and mounted tools;The bundled transfer planner makes the conservative choice of one new tip per CSV row.
transfer() is not the old plate-copy APIIn 0.2.1 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.
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.
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()
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.
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.
There is no stable generic import:
# Invalid in 0.2.1:
# 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
],
)
The keyword above is a STAR backend kwarg, 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.
python3 skills/pylabrobot/scripts/plan_transfers.py \
--manifest skills/pylabrobot/tests/fixtures/protocol_manifest.json \
--transfers skills/pylabrobot/tests/fixtures/transfers.csv
The CSV header is exact and fixed. Unknown columns, duplicate IDs, unsupported tip policies, missing source 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.
Checked 2026-07-23:
uL operations (page metadata
surfaced 2025-01-01; docs version 0.2.1).LiquidHandlerChatterboxBackend.v0.2.1 liquid-handler source
— signatures and import verification; tag dated 2026-03-23.