Skip to content
cccskills
FreePublished on GitHub

opentrons-integration

Opentrons Protocol API v2 for OT-2/Flex: Python protocols for pipetting, serial dilutions, PCR, plate replication; control thermocycler, heater-shaker, magnetic, temperature modules. Use pylabrobot for multi-vendor.

The English summary is being prepared. Showing the original description.

See how to install

Files included (1)

  • SKILL.md17.4 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Opentrons Integration — Lab Automation

Overview

Opentrons provides a Python-based Protocol API (v2) for programming OT-2 and Flex liquid handling robots. Protocols are structured Python files with metadata and a run() function that controls pipettes, labware, and hardware modules. All protocols can be simulated locally before running on physical hardware.

When to Use

  • Automating liquid handling workflows (pipetting, mixing, distributing)
  • Writing PCR setup protocols with thermocycler control
  • Performing serial dilutions across plates
  • Replicating plates or reformatting between plate types
  • Controlling hardware modules (temperature, magnetic, heater-shaker, thermocycler)
  • Setting up multi-channel pipetting for 96-well plate operations
  • Simulating protocols before running on the robot
  • For multi-vendor automation (Hamilton, Beckman, etc.), use pylabrobot instead
  • For flow cytometry analysis of automated experiment results, use flowio/flowkit

Prerequisites

pip install opentrons
# Simulate protocols locally (no robot needed)
opentrons_simulate my_protocol.py

Protocol API Version: Always use the latest stable API level (currently 2.19). Set apiLevel in protocol metadata. Protocols are forward-compatible within major versions.

Robot Types: Flex (newer, larger deck, 96-channel pipette) vs OT-2 (smaller, 8-channel max). Key differences: deck slot naming (Flex: A1-D3, OT-2: 1-11), available pipettes, and module support.

Quick Start

from opentrons import protocol_api

metadata = {"protocolName": "Quick Transfer", "apiLevel": "2.19"}

def run(protocol: protocol_api.ProtocolContext):
    tips = protocol.load_labware("opentrons_96_tiprack_300ul", "1")
    source = protocol.load_labware("nest_12_reservoir_15ml", "2")
    plate = protocol.load_labware("corning_96_wellplate_360ul_flat", "3")
    pipette = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tips])

    pipette.distribute(50, source["A1"], plate.wells()[:12], new_tip="once")

Core API

1. Protocol Structure

Every Opentrons protocol follows a required structure: metadata dict + run() function.

from opentrons import protocol_api

metadata = {
    "protocolName": "My Protocol",
    "author": "Name <email>",
    "description": "Protocol description",
    "apiLevel": "2.19",
}

# Optional: specify robot type
requirements = {"robotType": "Flex", "apiLevel": "2.19"}

def run(protocol: protocol_api.ProtocolContext):
    # All protocol logic goes here
    protocol.comment("Protocol started")

2. Labware and Deck Layout

Load labware (plates, reservoirs, tip racks) onto deck slots and optionally onto adapters.

def run(protocol: protocol_api.ProtocolContext):
    # Tip racks
    tips_300 = protocol.load_labware("opentrons_96_tiprack_300ul", "1")
    tips_20 = protocol.load_labware("opentrons_96_tiprack_20ul", "4")

    # Plates and reservoirs
    plate = protocol.load_labware("corning_96_wellplate_360ul_flat", "2", label="Sample Plate")
    reservoir = protocol.load_labware("nest_12_reservoir_15ml", "3")

    # Labware on adapter (Flex)
    adapter = protocol.load_adapter("opentrons_flex_96_tiprack_adapter", "B1")
    tips_on_adapter = adapter.load_labware("opentrons_flex_96_tiprack_200ul")

    # Pipettes
    p300 = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tips_300])
    p20 = protocol.load_instrument("p20_single_gen2", "right", tip_racks=[tips_20])

Common pipette names:

  • OT-2: p20_single_gen2, p300_single_gen2, p1000_single_gen2, p20_multi_gen2, p300_multi_gen2
  • Flex: p50_single_flex, p1000_single_flex, p50_multi_flex, p1000_multi_flex

3. Pipette Operations

Basic, compound, and advanced liquid handling operations.

def run(protocol: protocol_api.ProtocolContext):
    # ... (labware loaded above)

    # === Basic operations ===
    p300.pick_up_tip()
    p300.aspirate(100, source["A1"])        # Draw 100 µL
    p300.dispense(100, dest["B1"])          # Expel 100 µL
    p300.drop_tip()

    # === Compound operations (auto tip management) ===
    # Transfer: single source → single dest
    p300.transfer(100, source["A1"], dest["B1"], new_tip="always")

    # Distribute: one source → many dests
    p300.distribute(50, reservoir["A1"],
                    [plate["A1"], plate["A2"], plate["A3"]], new_tip="once")

    # Consolidate: many sources → one dest
    p300.consolidate(50, [plate["A1"], plate["A2"]], reservoir["A1"])

    # === Advanced techniques ===
    p300.pick_up_tip()
    p300.mix(repetitions=3, volume=50, location=plate["A1"])  # Mix in place
    p300.aspirate(100, source["A1"])
    p300.air_gap(20)                        # Prevent dripping
    p300.dispense(120, dest["A1"])
    p300.blow_out(dest["A1"].top())          # Expel residual
    p300.touch_tip(plate["A1"])              # Remove exterior drops
    p300.drop_tip()

4. Well Access and Locations

Navigate wells by name, index, row, or column. Control vertical position within wells.

def run(protocol: protocol_api.ProtocolContext):
    plate = protocol.load_labware("corning_96_wellplate_360ul_flat", "1")

    # Access by name or index
    well = plate["A1"]
    first = plate.wells()[0]           # Same as plate["A1"]

    # Iterate rows/columns
    row_a = plate.rows()[0]            # [A1, A2, ..., A12]
    col_1 = plate.columns()[0]         # [A1, B1, ..., H1]

    # Vertical positions
    pipette.aspirate(100, well.top())          # 1mm below top
    pipette.aspirate(100, well.bottom(z=2))    # 2mm above bottom
    pipette.aspirate(100, well.center())       # Center of well
    pipette.dispense(100, well.top(z=5))       # 5mm above top

5. Hardware Modules

Control temperature, magnetic, heater-shaker, and thermocycler modules.

def run(protocol: protocol_api.ProtocolContext):
    # Temperature module
    temp_mod = protocol.load_module("temperature module gen2", "3")
    temp_plate = temp_mod.load_labware("corning_96_wellplate_360ul_flat")
    temp_mod.set_temperature(celsius=4)
    # temp_mod.temperature → current temp; temp_mod.deactivate()

    # Magnetic module
    mag_mod = protocol.load_module("magnetic module gen2", "6")
    mag_plate = mag_mod.load_labware("nest_96_wellplate_100ul_pcr_full_skirt")
    mag_mod.engage(height_from_base=10)    # Raise magnets (mm)
    mag_mod.disengage()

    # Heater-Shaker module
    hs_mod = protocol.load_module("heaterShakerModuleV1", "1")
    hs_plate = hs_mod.load_labware("corning_96_wellplate_360ul_flat")
    hs_mod.close_labware_latch()
    hs_mod.set_target_temperature(celsius=37)
    hs_mod.wait_for_temperature()
    hs_mod.set_and_wait_for_shake_speed(rpm=500)
    hs_mod.deactivate_shaker()
    hs_mod.deactivate_heater()
    hs_mod.open_labware_latch()

    # Thermocycler (auto-assigned to slots)
    tc_mod = protocol.load_module("thermocyclerModuleV2")
    tc_plate = tc_mod.load_labware("nest_96_wellplate_100ul_pcr_full_skirt")
    tc_mod.open_lid()
    tc_mod.close_lid()
    tc_mod.set_lid_temperature(celsius=105)
    tc_mod.set_block_temperature(95, hold_time_seconds=180)

    profile = [
        {"temperature": 95, "hold_time_seconds": 15},
        {"temperature": 60, "hold_time_seconds": 30},
        {"temperature": 72, "hold_time_seconds": 60},
    ]
    tc_mod.execute_profile(steps=profile, repetitions=30, block_max_volume=50)
    tc_mod.deactivate_lid()
    tc_mod.deactivate_block()

6. Protocol Control and Utilities

Pause, delay, comment, liquid tracking, and simulation detection.

def run(protocol: protocol_api.ProtocolContext):
    # Execution control
    protocol.pause(msg="Replace tip box and resume")
    protocol.delay(seconds=60)
    protocol.delay(minutes=5)
    protocol.comment("Starting serial dilution")
    protocol.home()

    # Liquid tracking (visual in Opentrons App)
    water = protocol.define_liquid(name="Water", description="Ultrapure water",
                                    display_color="#0000FF")
    reservoir["A1"].load_liquid(liquid=water, volume=50000)
    plate["B1"].load_empty()

    # Check simulation vs real run
    if protocol.is_simulating():
        protocol.comment("Simulation mode")

    # Flow rate control (µL/s)
    pipette.flow_rate.aspirate = 150
    pipette.flow_rate.dispense = 300
    pipette.flow_rate.blow_out = 400

Key Concepts

Protocol File Structure

All Opentrons protocols are Python files with this required structure:

┌─ metadata dict ──────────────── protocolName, apiLevel, author
├─ requirements dict (optional) ── robotType
└─ def run(protocol): ─────────── All robot commands

The run() function receives a ProtocolContext object — all labware loading, pipette operations, and module control happen through this single entry point. Protocols cannot import arbitrary packages for execution on the robot.

OT-2 vs Flex Differences

FeatureOT-2Flex
Deck slots1-11 (numeric)A1-D3 (grid)
PipettesGen2 (p20, p300, p1000)Flex (p50, p1000, 96-channel)
Max channels8-channel multi96-channel
ModulesGen1/Gen2V2 modules
AdaptersNot supportedSupported (tiprack, flat)

Multi-Channel Pipette Behavior

When using multi-channel pipettes, referencing a single well accesses the entire column:

multi = protocol.load_instrument("p300_multi_gen2", "left", tip_racks=[tips])
# This transfers from ALL wells in column 1 of source to column 1 of dest
multi.transfer(100, source["A1"], dest["A1"])

Common Workflows

Workflow: Serial Dilution

from opentrons import protocol_api

metadata = {"protocolName": "Serial Dilution", "apiLevel": "2.19"}

def run(protocol: protocol_api.ProtocolContext):
    tips = protocol.load_labware("opentrons_96_tiprack_300ul", "1")
    reservoir = protocol.load_labware("nest_12_reservoir_15ml", "2")
    plate = protocol.load_labware("corning_96_wellplate_360ul_flat", "3")
    p300 = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tips])

    # Add diluent to columns 2-12
    p300.transfer(100, reservoir["A1"], plate.rows()[0][1:])

    # Serial dilution across row A
    p300.transfer(
        100,
        plate.rows()[0][:11],
        plate.rows()[0][1:],
        mix_after=(3, 50),
        new_tip="always",
    )

Workflow: PCR Setup with Thermocycler

from opentrons import protocol_api

metadata = {"protocolName": "PCR Setup", "apiLevel": "2.19"}

def run(protocol: protocol_api.ProtocolContext):
    tc_mod = protocol.load_module("thermocyclerModuleV2")
    tc_plate = tc_mod.load_labware("nest_96_wellplate_100ul_pcr_full_skirt")
    tips = protocol.load_labware("opentrons_96_tiprack_300ul", "1")
    reagents = protocol.load_labware("opentrons_24_tuberack_nest_1.5ml_snapcap", "2")
    p300 = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tips])

    tc_mod.open_lid()
    # Distribute master mix
    p300.distribute(20, reagents["A1"], tc_plate.wells()[:8], new_tip="once")
    # Add samples
    for i in range(8):
        p300.transfer(5, reagents.wells()[i + 1], tc_plate.wells()[i], new_tip="always")

    # Run PCR
    tc_mod.close_lid()
    tc_mod.set_lid_temperature(105)
    tc_mod.set_block_temperature(95, hold_time_seconds=180)  # Initial denaturation
    profile = [
        {"temperature": 95, "hold_time_seconds": 15},
        {"temperature": 60, "hold_time_seconds": 30},
        {"temperature": 72, "hold_time_seconds": 30},
    ]
    tc_mod.execute_profile(steps=profile, repetitions=35, block_max_volume=25)
    tc_mod.set_block_temperature(72, hold_time_minutes=5)  # Final extension
    tc_mod.set_block_temperature(4)  # Hold
    tc_mod.deactivate_lid()
    tc_mod.open_lid()

Workflow: Magnetic Bead Cleanup

  1. Load magnetic module with deep-well plate, reservoir with wash buffers
  2. Engage magnets → aspirate supernatant → dispense to waste
  3. Disengage magnets → add wash buffer → mix → engage → remove wash (repeat 2x)
  4. Disengage → add elution buffer → mix → engage → transfer eluate to clean plate

Key Parameters

ParameterFunctionDefaultRangeEffect
volumeaspirate, dispense, transfer—1–1000 µLLiquid volume
new_tiptransfer, distribute, consolidate"always""always", "once", "never"Tip change strategy
mix_aftertransferNone(reps, vol) tuplePost-dispense mixing
mix_beforetransferNone(reps, vol) tuplePre-aspirate mixing
blow_outtransferFalseTrue/FalseBlow out after dispense
touch_tiptransferFalseTrue/FalseTouch tip after dispense
air_gaptransfer00–pipette max µLAir gap volume
flow_rate.aspiratepipette propertyvaries1–1000 µL/sAspirate speed
flow_rate.dispensepipette propertyvaries1–1000 µL/sDispense speed
height_from_basemag_module.engage—0–20 mmMagnet engagement height

Best Practices

  1. Always simulate first: Run opentrons_simulate my_protocol.py before uploading to the robot. Catches labware conflicts, volume errors, and tip shortages without wasting consumables.

  2. Use compound operations over basic: Prefer transfer(), distribute(), consolidate() over manual pick_up_tip/aspirate/dispense/drop_tip sequences — they handle tip management automatically.

  3. Anti-pattern — ignoring tip count: A protocol that runs out of tips will error mid-run. Count total tip uses vs rack capacity before running.

  4. Track liquids for setup validation: Use define_liquid() and load_liquid() to enable volume tracking in the Opentrons App.

  5. Anti-pattern — hardcoding deck slots across robot types: Flex uses grid coordinates (A1-D3), OT-2 uses numbers (1-11). Write separate metadata or use requirements["robotType"] to ensure compatibility.

  6. Control flow rates for difficult liquids: Reduce aspirate speed for viscous solutions (glycerol, PEG), increase for water-like liquids.

  7. Use pauses for manual intervention: protocol.pause(msg=...) is safer than protocol.delay() when you need user action (e.g., adding reagent, sealing plate).

Common Recipes

Recipe: Plate Replication

from opentrons import protocol_api

metadata = {"protocolName": "Plate Replication", "apiLevel": "2.19"}

def run(protocol: protocol_api.ProtocolContext):
    tips = protocol.load_labware("opentrons_96_tiprack_300ul", "1")
    source = protocol.load_labware("corning_96_wellplate_360ul_flat", "2")
    dest = protocol.load_labware("corning_96_wellplate_360ul_flat", "3")
    p300 = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tips])
    p300.transfer(100, source.wells(), dest.wells(), new_tip="always")

Recipe: Reagent Distribution with Multi-Channel

from opentrons import protocol_api

metadata = {"protocolName": "Multi-Channel Distribution", "apiLevel": "2.19"}

def run(protocol: protocol_api.ProtocolContext):
    tips = protocol.load_labware("opentrons_96_tiprack_300ul", "1")
    reservoir = protocol.load_labware("nest_12_reservoir_15ml", "2")
    plate = protocol.load_labware("corning_96_wellplate_360ul_flat", "3")
    multi = protocol.load_instrument("p300_multi_gen2", "left", tip_racks=[tips])

    # Fill all 96 wells: 12 columns × 8 rows via multi-channel
    multi.transfer(100, reservoir["A1"], plate.rows()[0], new_tip="once")

Troubleshooting

ProblemCauseSolution
OutOfTipsErrorProtocol needs more tips than availableAdd multiple tip racks to tip_racks= list, or reload tips with pipette.reset_tipracks()
Labware collision on deckTwo items assigned to overlapping slotsCheck deck map — thermocycler auto-occupies multiple slots; use protocol.deck to inspect
Volume exceeds pipette capacityAttempting to aspirate/dispense > max volumeUse distribute() which auto-splits volumes, or switch to a larger pipette
LabwareNotFoundErrorWrong labware API nameCheck names at labware.opentrons.com; use exact API name strings
Protocol works in simulation but fails on robotHardware-specific timing issueAdd protocol.delay() between temperature changes; increase magnet engage time
Inaccurate volumesPipette calibration or air bubblesRecalibrate pipette; pre-wet tips with mix(); adjust flow rates for viscous liquids
ModuleNotAttachedErrorModule not connected or wrong model stringVerify module serial connection; use exact model strings ("temperature module gen2")

Related Skills

  • pylabrobot — multi-vendor lab automation (Hamilton, Beckman, Tecan) for cross-platform protocols
  • biopython-molecular-biology — sequence design and primer tools for PCR protocol inputs

References

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

API + Python SDK for ordering cell-free protein expression and binding assays. Submit sequences for expression (10–100 µg), measure binding affinity (KD) against targets, track status, and retrieve results programmatically — no wet-lab setup. Built for ML-guided directed evolution and antibody/nanobody optimization. Requires Adaptyv account and API key.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

aeon

Free

scikit-learn compatible Python toolkit for time series ML: classify, cluster, regress, segment, transform with 30+ algorithms (ROCKET, InceptionTime, KNN-DTW, HIVE-COTE, WEASEL). Handles panel, multivariate, and unequal-length series. Maintained successor to sktime. Alternatives: sktime (larger ecosystem), tslearn (fewer algorithms), catch22 (features only).

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

AiZynthFinder retrosynthetic route planning (CASP) from AstraZeneca Molecular AI. Monte Carlo tree search guided by a template-based neural expansion policy recursively disconnects a target SMILES until precursors are found in a purchasable stock. Covers config.yml (v4 format), aizynthcli batch screening, the AiZynthFinder/AiZynthExpander Python API, one-step disconnections, custom stocks via smiles2stock, scorers, Retro*/breadth-first/DFPN search alternatives, and reading output.json.gz / trees.json. Use for synthesis route planning, synthesizability screening, and building-block/precursor search. For reaction barriers use neb-irc-activation-energy; for 2D reaction scheme drawing use rdkit-chemdraw-cdxml.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

Access AlphaFold DB's 200M+ predicted structures by UniProt ID. Download PDB/mmCIF, analyze pLDDT/PAE, bulk-fetch proteomes via Google Cloud. For experimental structures use PDB; for prediction use ColabFold or ESMFold.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

Annotated matrices for single-cell genomics. Stores X with obs/var metadata, layers, embeddings (obsm/varm), graphs (obsp/varp), uns. Use for .h5ad/.zarr I/O, concatenation, scverse integration. For analysis use scanpy; for probabilistic models use scvi-tools.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

GRN inference from expression via GRNBoost2 (gradient boosting) or GENIE3 (Random Forest). Load matrix, filter by TFs, infer TF-target-importance links, save network. Dask-parallelized to single-cell scale. Core SCENIC component.

The English summary is being prepared. Showing the original description.

jaechang-hits/SciAgent-Skills381Updated Oct 16, 2026

See all skills by jaechang-hits

Report a problem with this skill