SkillAgentSearch skills...

pylabrobot

Hardware-agnostic Python liquid-handler library: portable scripts run on Hamilton STAR, Tecan Freedom EVO, Opentrons OT-2, or a simulator without vendor lock-in. For protocol automation, method dev, plate reformatting, serial dilutions, and Python lab workflows.

Install / Use

npx skills add jaechang-hits/SciAgent-Skills --skill pylabrobot

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

91/100

Category

Automation

Supported Platforms

Universal

Our assessment of pylabrobot

pylabrobot scores 91/100 on our quality scale, 1095th of 2,866 Automation skills we index (top 39%).

Its SKILL.md is 16 KB long, well organised into 43 sections with 17 code examples: a thorough specification that gives an agent plenty to work with.

It has 367 GitHub stars, a meaningful sign that others use it.

Substance
30/30
Structure
20/20
Description
15/15
Adoption
11/20
Freshness
15/15

Maintenance, license and trust

  • The repository was last updated 37 days ago, so pylabrobot is actively maintained.
  • No license is declared. By default that means all rights are reserved: you can read it, but reusing or redistributing it is not clearly permitted. Ask the author before building on it commercially.
  • Its trust signals score 88/100, with 1 caution from licensing, adoption, age or documentation. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.

Safety scan

No issues found

Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands.

Automated pattern scan on 2026-10-05. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.

pylabrobot compared with similar skills

All 4 of these similar skills score higher than pylabrobot; compare them before choosing.

SkillScoreStarsUpdatedFormat
pylabrobot (this skill)by jaechang-hits9136737d agoSKILL.md
Agent-Reachby Panniantong10090.8k19d agoCLAUDE.md
headroomby headroomlabs-ai10074.4ktodayCLAUDE.md
Scraplingby D4Vinci10085.7ktodayMCP Server
crawl4aiby unclecode10084.8k9d agoMCP Server

Frequently asked questions

How do I install pylabrobot?
Run npx skills add jaechang-hits/SciAgent-Skills --skill pylabrobot. The install tabs above show the steps for each supported agent.
Which AI agents does pylabrobot work with?
It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
Is pylabrobot safe to use?
Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. It declares no license and scores 88/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
Is pylabrobot still maintained?
The repository was last updated 37 days ago, so pylabrobot is actively maintained.

name: "pylabrobot" description: "Hardware-agnostic Python liquid-handler library: portable scripts run on Hamilton STAR, Tecan Freedom EVO, Opentrons OT-2, or a simulator without vendor lock-in. For protocol automation, method dev, plate reformatting, serial dilutions, and Python lab workflows." license: "MIT"

pylabrobot

Overview

PyLabRobot is an open-source Python library that abstracts liquid handling robot hardware behind a unified API. Write a protocol once and run it on any supported robot — Hamilton STAR, Tecan Freedom EVO, Opentrons OT-2, or a simulated backend — without changing the protocol code. PyLabRobot handles deck layout, resource management, and aspirate/dispense operations through a clean, async-first interface.

When to Use

  • Writing portable liquid handling protocols: You want a single Python script that works across Hamilton, Tecan, Opentrons, and a simulator without code changes.
  • Developing and testing protocols before robot time: Use the simulation backend to validate logic, volumes, and deck layouts without occupying physical hardware.
  • Automating plate reformatting and cherry-picking: Transfer specific wells between plates based on upstream data (e.g., hit compounds from a screen).
  • Building serial dilution curves: Systematically aspirate and dispense across a plate with precise volume steps.
  • Integrating liquid handling into Python data pipelines: Trigger robot actions from analysis code, LIMS queries, or machine learning models.
  • Rapid method development: Iterate quickly in Python rather than in vendor-specific scripting environments.
  • For Opentrons-specific features (temperature module control, built-in app integration), use the opentrons Python SDK instead; for multi-vendor portability use PyLabRobot.

Prerequisites

  • Python packages: pylabrobot
  • Optional backends: pylabrobot[hamilton] for Hamilton STAR, pylabrobot[opentrons] for OT-2
  • Data requirements: None — deck resources are defined in Python
  • Environment: Python 3.9+; physical robot drivers installed separately per vendor docs
pip install pylabrobot
pip install "pylabrobot[hamilton]"   # add Hamilton USB driver
pip install "pylabrobot[opentrons]"  # add Opentrons REST driver

Quick Start

import asyncio
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import SimulatorBackend
from pylabrobot.resources import Deck, Cos_96_Rd, HTF_L

async def main():
    backend = SimulatorBackend(open_browser=False)
    lh = LiquidHandler(backend=backend, deck=Deck())
    await lh.setup()

    plate = Cos_96_Rd(name="plate")
    tips  = HTF_L(name="tips")
    lh.deck.assign_child_resource(plate, rails=2)
    lh.deck.assign_child_resource(tips,  rails=5)

    await lh.pick_up_tips(tips["A1"])
    await lh.aspirate(plate["A1"], vols=50)
    await lh.dispense(plate["B1"], vols=50)
    await lh.drop_tips(tips["A1"])
    await lh.stop()
    print("Transfer complete: 50 uL from A1 -> B1")

asyncio.run(main())

Core API

Module 1: LiquidHandler — Setup and Teardown

The LiquidHandler class is the central controller. It wraps a backend and a Deck.

import asyncio
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import SimulatorBackend
from pylabrobot.resources import Deck

async def main():
    backend = SimulatorBackend(open_browser=False)
    lh = LiquidHandler(backend=backend, deck=Deck())
    await lh.setup()      # connect to hardware / start simulator
    print("LiquidHandler ready:", lh)
    await lh.stop()       # disconnect cleanly

asyncio.run(main())
# Connecting to a real Hamilton STAR
from pylabrobot.liquid_handling.backends.hamilton import STAR

async def main():
    backend = STAR()
    lh = LiquidHandler(backend=backend, deck=Deck())
    await lh.setup()
    # lh is now connected to physical hardware
    await lh.stop()

Module 2: Deck and Resource Assignment

Resources (plates, tip racks, reservoirs) are placed on the deck by rail position.

from pylabrobot.resources import (
    Deck,
    Cos_96_Rd,                  # Corning 96-well round-bottom plate
    Cos_384_Sq,                 # Corning 384-well plate
    HTF_L,                      # Hamilton tip rack (filtered, large)
    Trough_1_Row_1_Col_4,       # 4-channel reservoir
)

deck = Deck()
plate_96  = Cos_96_Rd(name="sample_plate")
plate_384 = Cos_384_Sq(name="assay_plate")
tips      = HTF_L(name="tip_rack")
reservoir = Trough_1_Row_1_Col_4(name="buffer")

deck.assign_child_resource(plate_96,  rails=1)
deck.assign_child_resource(plate_384, rails=4)
deck.assign_child_resource(tips,      rails=8)
deck.assign_child_resource(reservoir, rails=11)

print("Deck resources:", [r.name for r in deck.children])

Module 3: Tip Operations

Pick up and drop tips before and after liquid operations.

# Pick up tips from the first column of the tip rack
await lh.pick_up_tips(tips["A1:H1"])   # all 8 tips in column 1

# After liquid operations, drop tips back
await lh.drop_tips(tips["A1:H1"])

# Single tip
await lh.pick_up_tips(tips["A1"])
await lh.drop_tips(tips["A1"])
print("Tip operations complete")

Module 4: Aspirate Operations

Aspirate liquid from wells. Accepts single wells, ranges, or lists.

# Aspirate 100 uL from a single well
await lh.aspirate(plate["A1"], vols=100)

# Aspirate different volumes from multiple wells simultaneously
await lh.aspirate(
    plate["A1:A4"],
    vols=[50, 75, 100, 125],
)
print("Aspiration complete")
from pylabrobot.resources import Coordinate

# Aspirate with flow rate and liquid height control
await lh.aspirate(
    plate["A1"],
    vols=50,
    flow_rates=100,                    # uL/s
    offsets=Coordinate(0, 0, 1),       # 1 mm above well bottom
)

Module 5: Dispense Operations

Dispense liquid into target wells.

# Dispense 100 uL into a single well
await lh.dispense(plate["B1"], vols=100)

# Multi-well dispense with different volumes
await lh.dispense(
    plate["B1:B4"],
    vols=[50, 75, 100, 125],
)
print("Dispense complete")

Module 6: Transfer — High-Level Convenience

transfer combines aspirate and dispense for simple source-to-destination moves.

# Transfer 50 uL from A1 -> B1
await lh.transfer(plate["A1"], plate["B1"], transfer_volume=50)

# Multi-well pairwise transfer
sources      = plate["A1:A8"]
destinations = plate["B1:B8"]
await lh.transfer(sources, destinations, transfer_volume=75)
print("Transfer complete")

Module 7: Simulation Backend

The SimulatorBackend runs a browser-based visualizer for protocol debugging.

from pylabrobot.liquid_handling.backends import SimulatorBackend

# With visual browser (default — opens http://localhost:2121)
backend = SimulatorBackend(open_browser=True)

# Headless simulation (CI/testing)
backend = SimulatorBackend(open_browser=False)

# After setup(), liquid movements are visualized in real time
await lh.setup()
# Check browser for visual confirmation before running on real hardware
print("Simulator running at http://localhost:2121")

Key Concepts

Async-First Design

All robot operations (setup, aspirate, dispense, transfer) are Python async coroutines. Run them inside an async def function using asyncio.run() or Jupyter's top-level await syntax.

import asyncio

async def run_protocol(lh, plate, tips):
    await lh.pick_up_tips(tips["A1"])
    await lh.aspirate(plate["A1"], vols=50)
    await lh.dispense(plate["B1"], vols=50)
    await lh.drop_tips(tips["A1"])
    print("Protocol complete")

asyncio.run(run_protocol(lh, plate, tips))

Well Addressing

Wells are addressed by alphanumeric position ("A1") or slice notation ("A1:H1" for a column, "A1:A12" for a row).

well    = plate["A1"]             # single well
col1    = plate["A1:H1"]          # 8 wells in column 1
row_a   = plate["A1:A12"]         # 12 wells in row A
print(f"Single: {well.name}")
print(f"Column: {len(col1)} wells")
print(f"Row:    {len(row_a)} wells")

Common Workflows

Workflow 1: 96-Well Serial Dilution

Goal: Perform a 2-fold serial dilution across a 96-well plate.

import asyncio
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import SimulatorBackend
from pylabrobot.resources import Deck, Cos_96_Rd, HTF_L, Trough_1_Row_1_Col_4

async def serial_dilution():
    backend = SimulatorBackend(open_browser=False)
    lh = LiquidHandler(backend=backend, deck=Deck())
    await lh.setup()

    plate   = Cos_96_Rd(name="plate")
    tips    = HTF_L(name="tips")
    diluent = Trough_1_Row_1_Col_4(name="diluent")
    lh.deck.assign_child_resource(plate,   rails=1)
    lh.deck.assign_child_resource(tips,    rails=5)
    lh.deck.assign_child_resource(diluent, rails=9)

    # Add 100 uL diluent to columns 2-12
    for col in range(2, 13):
        col_label = f"A{col}:H{col}"
        await lh.pick_up_tips(tips[f"A{col}:H{col}"])
        await lh.aspirate(diluent["A1:H1"], vols=100)
        await lh.dispense(plate[col_label], vols=100)
        await lh.drop_tips(tips[f"A{col}:H{col}"])

    # Serial transfer: col 1 -> 2 -> ... -> 11
    for col in range(1, 12):
        src = f"A{col}:H{col}"
        dst = f"A{col+1}:H{col+1}"
        await lh.pick_up_tips(tips[f"A{col}:H{col}"])
        await lh.aspirate(plate[src], vols=100)
        await lh.dispense(plate[dst], vols=100)
        await lh.drop_tips(tips[f"A{col}:H{col}"])

    print("Serial dilution complete: 12 columns, 2-fold steps")
    await lh.stop()

asyncio.run(serial_dilution())

Workflow 2: Cherry-Picking from a Hit List

Goal: Transfer compounds from specified source wells to a destination plate based on a CSV hit list.

import asyncio
import pandas as pd
from pylabrobot.liquid_handling import LiquidHandler
from pylabrobot.liquid_handling.backends import SimulatorBackend
from pylabrobot.resources import Deck, Cos_96_Rd, HTF_L

async def cherry_pick(hit_list_csv: str, volume: float = 50.0):
    # CSV must have columns: source_well, dest_well
    hits = pd.read_csv(hit_list_csv)
    print(f"Cherry-picking {len(hits)} hits at {volume} uL each")

    backend = SimulatorBackend(open_browser=False)
    lh = LiquidHandler(backend=backend, deck=Deck())
    await lh.setup()

    src  = Cos_96_Rd(name="source")
    dst  = Cos_96_Rd(name="destination")
    tips = HTF_L(name="tips")
    lh.deck.assign_child_resource(src,  rails=1)
    lh.deck.assign_child_resource(dst,  rails=4)
    lh.deck.assign_child_resource(tips, rails=8)

    # Get all well names from tip rack
    tip_wells = [w.name for w in tips.wells]
    for i, row in hits.iterrows():
        await lh.pick_up_tips(tips[tip_wells[i]])
        await lh.transfer(src[row["source_well"]], dst[row["dest_well"]],
                          transfer_volume=volume)
        await lh.drop_tips(tips[tip_wells[i]])

    print(f"Cherry-pick complete: {len(hits)} transfers done")
    await lh.stop()

# asyncio.run(cherry_pick("hits.csv", volume=50))

Key Parameters

| Parameter | Module | Default | Range / Options | Effect | |-----------|--------|---------|-----------------|--------| | vols | aspirate / dispense | required | 0 – robot max (µL) | Volume to aspirate or dispense per well | | flow_rates | aspirate / dispense | backend default | 10 – 1000 µL/s | Speed of liquid movement | | blow_out_air_volume | dispense | 0 | 0 – 30 µL | Air volume blown after dispense to empty tip | | offsets | aspirate / dispense | Coordinate(0,0,0) | Any Coordinate | Positional offset from well center (x, y, z mm) | | open_browser | SimulatorBackend | True | True, False | Open browser-based visual simulator on setup | | rails | deck assignment | required | 1 – max deck rails | Ph

Truncated for display — read the full file on GitHub.

Related Skills

View on GitHub
GitHub Stars367
CategoryAutomation
Updated1mo ago
Forks36

Languages

Python

Trust signals

88/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

1 medium