alterlab-pylabrobot — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited alterlab-pylabrobot (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
PyLabRobot (PLR) is a hardware-agnostic Python SDK for lab automation. Every device is a front end (LiquidHandler, PlateReader, HeaterShaker, ...) plus a backend for a specific instrument, so the same protocol code runs on a simulator, a Hamilton STAR, or an Opentrons OT-2 by swapping one backend line. All device calls are async and must be awaited inside an event loop (asyncio.run(main()), or directly in a Jupyter cell).
| Scenario | Use Instead |
|---|---|
Writing an Opentrons OT-2/Flex protocol with the official Opentrons Python API (apiLevel, opentrons_simulate) | alterlab-opentrons |
| Sending work to a remote cloud lab instead of running your own robot | alterlab-ginkgo-cloud |
| Ordering protein binding/expression assays from the Adaptyv Bio foundry | alterlab-adaptyv |
| Finding, writing, or publishing a human-readable protocol with a DOI | alterlab-protocolsio |
| Area | What it covers | Reference |
|---|---|---|
| Liquid handling | aspirate, dispense, transfer, tips, multichannel moves, serial dilutions, error handling | references/liquid-handling.md |
| Resources | Decks, carriers, plates, tip racks, troughs, indexing, tracking, saving layouts, custom labware | references/resources.md |
| Hardware backends | STAR, Vantage, EVO, OT-2, chatterbox; switching backends | references/hardware-backends.md |
| Analytical equipment | Plate readers (CLARIOstar, BioTek, SpectraMax, Byonoy), scales | references/analytical-equipment.md |
| Material handling | Heater shakers, temperature controllers, incubators, centrifuges, pumps, thermocyclers | references/material-handling.md |
| Visualization | Browser visualizer, simulation-driven testing | references/visualization.md |
import asyncio
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import LiquidHandlerChatterboxBackend # STARBackend() on a real STAR
from pylabrobot.resources import (
PLT_CAR_L5AC_A00, # plate carrier (5 sites)
STARLetDeck,
TIP_CAR_480_A00, # tip carrier (5 sites)
cor_96_wellplate_360uL_Fb,
hamilton_96_tiprack_1000uL_filter,
set_tip_tracking,
set_volume_tracking,
)
async def main():
set_tip_tracking(True) # catch missing tips and over-aspiration in simulation
set_volume_tracking(True)
lh = LiquidHandler(backend=LiquidHandlerChatterboxBackend(), deck=STARLetDeck())
await lh.setup()
try:
# Labware goes into carrier sites; carriers go on deck rails.
tip_car = TIP_CAR_480_A00(name="tip_carrier")
tip_car[0] = tip_rack = hamilton_96_tiprack_1000uL_filter(name="tips_01")
lh.deck.assign_child_resource(tip_car, rails=3)
plt_car = PLT_CAR_L5AC_A00(name="plate_carrier")
plt_car[0] = source = cor_96_wellplate_360uL_Fb(name="source")
plt_car[1] = dest = cor_96_wellplate_360uL_Fb(name="dest")
lh.deck.assign_child_resource(plt_car, rails=15)
for well in source.get_all_items():
well.tracker.set_liquids([(None, 300)])
# 8-channel column copy: "A1:H1" is column 1 (8 wells), one tip per channel
await lh.pick_up_tips(tip_rack["A1:H1"])
await lh.aspirate(source["A1:H1"], vols=[100] * 8)
await lh.dispense(dest["A1:H1"], vols=[100] * 8)
await lh.discard_tips() # to the deck's trash; return_tips() puts them back
print(dest.get_well("A1").tracker.get_used_volume()) # 100.0
finally:
await lh.stop()
asyncio.run(main())To run on hardware, replace the backend (STARBackend(), VantageBackend(), EVOBackend(), OpentronsOT2Backend(host="<robot IP>")) and the deck (STARDeck(), VantageDeck(size=1.3), EVO150Deck(), OTDeck()); the protocol body stays the same apart from deck positions.
plate["A1"] is a one-element list; plate["A1:H1"] is column 1 and plate["A1:A12"] is row A. Use plate.get_well("A1") or tip_rack.get_item("A1") for a single object (for example to reach .tracker).vols=[100] for one channel, vols=[100] * 8 for eight; per-channel options (flow_rates, liquid_height, blow_out_air_volume) are lists too.pick_up_tips(spots), then discard_tips() (trash), return_tips() (back to the rack), or drop_tips(spots) (explicit spots — it has no default).lh.transfer(source.get_well("A1"), dest["A1:H1"], target_vols=[50] * 8) aspirates once and dispenses into each target with the channel-0 tip. source_vol= is the total volume, split across targets (equally or by ratios), not a per-target volume.cor_96_wellplate_360uL_Fb); capitalised legacy names such as Cor_96_wellplate_360ul_Fb warn and will be removed.# 50 uL of diluent into each of A2..A5 from one source well (single channel)
await lh.pick_up_tips(tip_rack["A2"])
await lh.transfer(source.get_well("H12"), dest["A2:A5"], target_vols=[50] * 4)
await lh.discard_tips()
# 2-fold dilution along row A (A1 -> A5): move 50 uL to the next well and mix
await lh.pick_up_tips(tip_rack["A3"])
for col in range(1, 5):
await lh.aspirate(dest[f"A{col}"], vols=[50])
await lh.dispense(dest[f"A{col + 1}"], vols=[50])
for _ in range(3): # mix
await lh.aspirate(dest[f"A{col + 1}"], vols=[40])
await lh.dispense(dest[f"A{col + 1}"], vols=[40])
await lh.discard_tips()from pylabrobot.plate_reading import CLARIOstarBackend, PlateReader
reader = PlateReader(name="clariostar", size_x=0, size_y=0, size_z=0,
backend=CLARIOstarBackend()) # needs pylabrobot[ftdi]
await reader.setup()
await reader.open()
reader.assign_child_resource(dest) # or move the plate there with the robot's gripper
await reader.close()
result = await reader.read_absorbance(wavelength=450, use_new_return_type=True)
od450 = result[0]["data"] # 8 x 12 nested list
await reader.stop()PlateReader has no temperature method; readers that support heating expose it on the backend (for example await reader.backend.set_temperature(37) on BioTek and Molecular Devices backends). See references/analytical-equipment.md for other readers and for scales.
LiquidHandlerChatterboxBackend with tip and volume tracking on, and watch it in the Visualizer, before the first hardware run.lh.deck.save("deck.json") and commit it with the protocol; record the pylabrobot version.| Symptom | Likely cause |
|---|---|
TypeError: Resources must be Containers, got [[Well(...)]] | A list was passed where one well is expected (transfer source); use plate.get_well("A1") |
TypeError: drop_tips() missing ... 'tip_spots' | Use discard_tips() or pass explicit spots |
NoTipError / HasTipError with tracking on | The protocol reuses a tip spot or picks up while holding tips; check tip bookkeeping |
RuntimeError: ... is not installed. Install with: pip install pylabrobot[...] | Install the connection extra named in the message |
ImportError for pylabrobot.temperature_control, pylabrobot.incubation, Trough_100ml | Outdated names; see references/material-handling.md and references/resources.md for the 0.2.2 modules and labware |
Part of the AlterLab Academic Skills suite.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.