Skill Tracer
Skills › Scientific Agent Skills › opentrons-integration › protocol_authoring.md

protocol_authoring.md

355 lines · skills/opentrons-integration/references/protocol_authoring.md at 5f2bc03

Open on GitHub
1# Protocol Authoring Guide
2
3Use this guide to turn a wet-lab method into a reviewable Opentrons Python
4protocol. It targets Protocol API v2 and assumes the protocol will be imported
5into the Opentrons App.
6

Protocol Authoring Guide

Use this guide to turn a wet-lab method into a reviewable Opentrons Python protocol. It targets Protocol API v2 and assumes the protocol will be imported into the Opentrons App.

Raw lines 1 to 6
7## 1. Convert the Method into Explicit Inputs
8
9Create a protocol specification before writing code.

1. Convert the Method into Explicit Inputs

Create a protocol specification before writing code.

Raw lines 7 to 10
11### Hardware
13- Robot model and installed software.
14- Pipette load name, mount, channels, and calibrated volume range.
15- Required modules and generations.
16- Flex Gripper, Stacker, waste chute, staging slot, and trash-bin requirements.
17- Whether any operator action requires opening the door or moving labware.

Hardware

  • Robot model and installed software.
  • Pipette load name, mount, channels, and calibrated volume range.
  • Required modules and generations.
  • Flex Gripper, Stacker, waste chute, staging slot, and trash-bin requirements.
  • Whether any operator action requires opening the door or moving labware.
Raw lines 11 to 18
19### Labware and consumables
21- Exact API load name, namespace, and definition version.
22- Adapter or module beneath each item.
23- Tip capacity, filter status, and number of racks.
24- Lids, seals, caps, and manual removal steps.
25- Custom labware JSON and how its dimensions were verified.

Labware and consumables

  • Exact API load name, namespace, and definition version.
  • Adapter or module beneath each item.
  • Tip capacity, filter status, and number of racks.
  • Lids, seals, caps, and manual removal steps.
  • Custom labware JSON and how its dimensions were verified.
Raw lines 19 to 26
27### Liquids and assay constraints
29- Initial source volume and dead volume.
30- Per-transfer volume, number of destinations, and destination capacity.
31- Viscosity, volatility, foaming risk, cells or beads that settle, and
32 temperature sensitivity.
33- Required pre-wet, mix, air-gap, delay, touch-tip, blowout, or push-out
34 behavior.
35- Contamination boundaries and whether tip reuse is permitted.

Liquids and assay constraints

  • Initial source volume and dead volume.
  • Per-transfer volume, number of destinations, and destination capacity.
  • Viscosity, volatility, foaming risk, cells or beads that settle, and temperature sensitivity.
  • Required pre-wet, mix, air-gap, delay, touch-tip, blowout, or push-out behavior.
  • Contamination boundaries and whether tip reuse is permitted.
Raw lines 27 to 36
37### Operations
39- Runtime-configurable values and safe defaults.
40- Required user pauses and setup messages.
41- Module temperatures, speeds, cycle profiles, and hold behavior.
42- Data input and output files.
43- Dry-run and acceptance criteria.
45Record every assumption that has not been confirmed by the operator.

Operations

  • Runtime-configurable values and safe defaults.
  • Required user pauses and setup messages.
  • Module temperatures, speeds, cycle profiles, and hold behavior.
  • Data input and output files.
  • Dry-run and acceptance criteria.

Record every assumption that has not been confirmed by the operator.

Raw lines 37 to 46
47## 2. Choose the API Level Deliberately
49The API level controls both available methods and behavior. It is not merely a
50documentation label.
521. Check the robot's maximum supported level in the App.
532. Identify the minimum level for each required feature.
543. Use the highest of those minimums.
554. Raise the level further only for a specific fix or behavior that the method
56 needs.
58At the 2026-10-01 baseline:
60- Flex documented maximum: 2.30; bundled templates use 2.29 for step grouping.
61- OT-2 maximum: 2.28.
62- A shared Flex/OT-2 code generator must not assume API 2.29 or later.
64Do not declare an unsupported API level just because a newer local Python
65package can simulate it.

2. Choose the API Level Deliberately

The API level controls both available methods and behavior. It is not merely a documentation label.

  1. Check the robot's maximum supported level in the App.
  2. Identify the minimum level for each required feature.
  3. Use the highest of those minimums.
  4. Raise the level further only for a specific fix or behavior that the method needs.

At the 2026-10-01 baseline:

  • Flex documented maximum: 2.30; bundled templates use 2.29 for step grouping.
  • OT-2 maximum: 2.28.
  • A shared Flex/OT-2 code generator must not assume API 2.29 or later.

Do not declare an unsupported API level just because a newer local Python package can simulate it.

Raw lines 47 to 66
67## 3. Keep Top-Level Code Declarative
69A protocol file normally contains imports, `metadata`, `requirements`, optional
70parameter definitions, helper functions, and `run()`.
72```python
73from opentrons import protocol_api
75metadata = {
76 "protocolName": "Assay setup",
77 "author": "Automation Team",
78 "description": "Prepare an eight-sample assay plate.",
80requirements = {"robotType": "Flex", "apiLevel": "2.29"}
83def add_parameters(parameters: protocol_api.ParameterContext) -> None:
84 parameters.add_int(
85 variable_name="sample_count",
86 display_name="Sample count",
87 default=8,
88 minimum=1,
89 maximum=24,
90 )
93def run(protocol: protocol_api.ProtocolContext) -> None:
94 sample_count = protocol.params.sample_count
95 protocol.comment(f"Preparing {sample_count} samples")
96```
98Avoid top-level network calls, package installation, local machine paths, or
99other side effects. Protocol analysis evaluates the protocol in a controlled
100environment, and the robot may not have workstation packages or network
101access.
103Do not use `apiLevel` in both `metadata` and `requirements`.

3. Keep Top-Level Code Declarative

A protocol file normally contains imports, metadata, requirements, optional parameter definitions, helper functions, and run().

from opentrons import protocol_api

metadata = {
    "protocolName": "Assay setup",
    "author": "Automation Team",
    "description": "Prepare an eight-sample assay plate.",
}
requirements = {"robotType": "Flex", "apiLevel": "2.29"}


def add_parameters(parameters: protocol_api.ParameterContext) -> None:
    parameters.add_int(
        variable_name="sample_count",
        display_name="Sample count",
        default=8,
        minimum=1,
        maximum=24,
    )


def run(protocol: protocol_api.ProtocolContext) -> None:
    sample_count = protocol.params.sample_count
    protocol.comment(f"Preparing {sample_count} samples")

Avoid top-level network calls, package installation, local machine paths, or other side effects. Protocol analysis evaluates the protocol in a controlled environment, and the robot may not have workstation packages or network access.

Do not use apiLevel in both metadata and requirements.

Raw lines 67 to 104
105## 4. Build a Deck Manifest
107Create a simple manifest before calling load methods:
109| Slot | Item | Adapter/module | Notes |
110| --- | --- | --- | --- |
111| A3 | Flex trash bin | — | Required for dropped tips |
112| C1 | 50 µL tip rack | Flex tip-rack adapter if required | Left pipette |
113| C2 | Sample rack | — | Tubes 1–8 |
114| D1 | Temperature Module GEN2 | Aluminum block | Master mix |
115| Thermocycler | PCR plate | Thermocycler Module GEN2 | Destination |
117Then check:
119- Module and adapter footprints.
120- Flex column-4 staging slots and column-3 conflicts.
121- Thermocycler occupied slots.
122- Heater-Shaker clearance and latch state.
123- Gripper approach paths and labware compatibility.
124- Tall labware adjacent to partial-nozzle pickup.
125- Sufficient room for lids and plate-reader lid travel.
127Use named variables and labels:
129```python
130sample_rack = protocol.load_labware(
131 "opentrons_24_tuberack_nest_1.5ml_snapcap",
132 "C2",
133 label="Samples 1-8",
135```
137Labels appear in setup and run views and are more useful than generic names
138such as `plate1`.

4. Build a Deck Manifest

Create a simple manifest before calling load methods:

SlotItemAdapter/moduleNotes
A3Flex trash bin—Required for dropped tips
C150 µL tip rackFlex tip-rack adapter if requiredLeft pipette
C2Sample rack—Tubes 1–8
D1Temperature Module GEN2Aluminum blockMaster mix
ThermocyclerPCR plateThermocycler Module GEN2Destination

Then check:

  • Module and adapter footprints.
  • Flex column-4 staging slots and column-3 conflicts.
  • Thermocycler occupied slots.
  • Heater-Shaker clearance and latch state.
  • Gripper approach paths and labware compatibility.
  • Tall labware adjacent to partial-nozzle pickup.
  • Sufficient room for lids and plate-reader lid travel.

Use named variables and labels:

sample_rack = protocol.load_labware(
    "opentrons_24_tuberack_nest_1.5ml_snapcap",
    "C2",
    label="Samples 1-8",
)

Labels appear in setup and run views and are more useful than generic names such as plate1.

Raw lines 105 to 139
140## 5. Use Exact Labware Definitions
142The API load name identifies geometry, not merely a product category.
144- Copy load names from the
145 [Labware Library](https://labware.opentrons.com/).
146- Check definition version when a labware has multiple revisions.
147- Do not replace a missing definition with a "close enough" plate or rack.
148- Bundle the exact custom labware JSON with the protocol when needed.
149- Verify custom labware dimensions, well coordinates, and stacking offsets.
150- Run Labware Position Check or the current App equivalent before first use.
152Loading on an adapter should reflect the physical stack:
154```python
155adapter = protocol.load_adapter(
156 "opentrons_96_well_aluminum_block",
157 "D1",
159plate = adapter.load_labware(
160 "opentrons_96_wellplate_200ul_pcr_full_skirt"
162```
164For modules, call the module or module adapter's `load_labware()` method. Do not
165repeat a deck slot already supplied to `load_module()`.

5. Use Exact Labware Definitions

The API load name identifies geometry, not merely a product category.

  • Copy load names from the Labware Library.
  • Check definition version when a labware has multiple revisions.
  • Do not replace a missing definition with a "close enough" plate or rack.
  • Bundle the exact custom labware JSON with the protocol when needed.
  • Verify custom labware dimensions, well coordinates, and stacking offsets.
  • Run Labware Position Check or the current App equivalent before first use.

Loading on an adapter should reflect the physical stack:

adapter = protocol.load_adapter(
    "opentrons_96_well_aluminum_block",
    "D1",
)
plate = adapter.load_labware(
    "opentrons_96_wellplate_200ul_pcr_full_skirt"
)

For modules, call the module or module adapter's load_labware() method. Do not repeat a deck slot already supplied to load_module().

Raw lines 140 to 166
167## 6. Represent Initial Liquids
169Liquid definitions improve setup visualization but do not move or measure
170liquid.
172```python
173master_mix = protocol.define_liquid(
174 name="Master mix",
175 description="2x PCR master mix",
176 display_color="#E377C2",
179reagent_rack.load_liquid(
180 wells=["A1"],
181 volume=500,
182 liquid=master_mix,
184```
186For varying volumes:
188```python
189sample_rack.load_liquid_by_well(
190 volumes={"A1": 30, "A2": 40, "A3": 50},
191 liquid=sample,
193```
195In API 2.22+ protocols, prefer labware-level methods over deprecated
196`Well.load_liquid()`.
198Declared volumes support visualization and meniscus calculations. They do not
199replace physical setup checks or a source-volume budget.

6. Represent Initial Liquids

Liquid definitions improve setup visualization but do not move or measure liquid.

master_mix = protocol.define_liquid(
    name="Master mix",
    description="2x PCR master mix",
    display_color="#E377C2",
)

reagent_rack.load_liquid(
    wells=["A1"],
    volume=500,
    liquid=master_mix,
)

For varying volumes:

sample_rack.load_liquid_by_well(
    volumes={"A1": 30, "A2": 40, "A3": 50},
    liquid=sample,
)

In API 2.22+ protocols, prefer labware-level methods over deprecated Well.load_liquid().

Declared volumes support visualization and meniscus calculations. They do not replace physical setup checks or a source-volume budget.

Raw lines 167 to 200
201## 7. Define Runtime Parameters
203Use runtime parameters for values an operator should set without editing code:
205- Sample count.
206- Transfer volume within a validated range.
207- Number of cycles.
208- Incubation time.
209- Dry-run mode.
210- A fixed choice of pipette or workflow branch.
211- A CSV plate map.
213Keep hardware geometry and safety-critical invariants in code unless every
214allowed choice is separately validated.
216```python
217def add_parameters(parameters: protocol_api.ParameterContext) -> None:
218 parameters.add_float(
219 variable_name="transfer_volume",
220 display_name="Transfer volume",
221 description="Volume delivered to each destination.",
222 default=25.0,
223 minimum=10.0,
224 maximum=40.0,
225 unit="µL",
226 )
227 parameters.add_bool(
228 variable_name="dry_run",
229 display_name="Dry run",
230 description="Shorten delays and use test-safe behavior.",
231 default=False,
232 )
233```
235Constraints:
237- Display name: at most 30 characters.
238- Description: at most 100 characters.
239- Numeric parameters need a min/max range or enumerated choices.
240- A CSV parameter has no default.
241- The App and touchscreen currently support one CSV parameter per run.
242- CSV cells are initially strings; parse and validate every row, column, well
243 name, and numeric value before issuing commands.
245Use defaults that simulate successfully. A parameter named `dry_run` has only
246the behavior your code implements: the bundled template shortens incubation
247but still aspirates and dispenses. It is not a hardware-disable switch.

7. Define Runtime Parameters

Use runtime parameters for values an operator should set without editing code:

  • Sample count.
  • Transfer volume within a validated range.
  • Number of cycles.
  • Incubation time.
  • Dry-run mode.
  • A fixed choice of pipette or workflow branch.
  • A CSV plate map.

Keep hardware geometry and safety-critical invariants in code unless every allowed choice is separately validated.

def add_parameters(parameters: protocol_api.ParameterContext) -> None:
    parameters.add_float(
        variable_name="transfer_volume",
        display_name="Transfer volume",
        description="Volume delivered to each destination.",
        default=25.0,
        minimum=10.0,
        maximum=40.0,
        unit="µL",
    )
    parameters.add_bool(
        variable_name="dry_run",
        display_name="Dry run",
        description="Shorten delays and use test-safe behavior.",
        default=False,
    )

Constraints:

  • Display name: at most 30 characters.
  • Description: at most 100 characters.
  • Numeric parameters need a min/max range or enumerated choices.
  • A CSV parameter has no default.
  • The App and touchscreen currently support one CSV parameter per run.
  • CSV cells are initially strings; parse and validate every row, column, well name, and numeric value before issuing commands.

Use defaults that simulate successfully. A parameter named dry_run has only the behavior your code implements: the bundled template shortens incubation but still aspirates and dispenses. It is not a hardware-disable switch.

Raw lines 201 to 248
249## 8. Structure the Protocol
251Separate configuration from repetitive actions:
253```python
254def transfer_samples(
255 pipette: protocol_api.InstrumentContext,
256 sources: list[protocol_api.Well],
257 destinations: list[protocol_api.Well],
258 volume: float,
259) -> None:
260 for source, destination in zip(sources, destinations, strict=True):
261 pipette.transfer(
262 volume,
263 source,
264 destination,
265 new_tip="always",
266 mix_after=(3, min(volume, 20)),
267 )
268```
270Guidelines:
272- Use explicit lists for safety-critical well order.
273- Use `zip(..., strict=True)` only if the robot's Python version supports it;
274 the current baseline uses Python 3.10 and does.
275- Validate list lengths before command generation.
276- Name physical quantities with units, for example `volume_ul` and
277 `temperature_c`.
278- Put all robot commands under `run()` or helpers called from `run()`.
279- Let Protocol API errors stop analysis or execution. Do not broadly catch
280 exceptions and continue physical motion.
281- Use `protocol.pause()` for required operator intervention.
283`protocol.comment()` text is computed during analysis. Do not rely on it to
284report a live sensor value that only exists during physical execution.

8. Structure the Protocol

Separate configuration from repetitive actions:

def transfer_samples(
    pipette: protocol_api.InstrumentContext,
    sources: list[protocol_api.Well],
    destinations: list[protocol_api.Well],
    volume: float,
) -> None:
    for source, destination in zip(sources, destinations, strict=True):
        pipette.transfer(
            volume,
            source,
            destination,
            new_tip="always",
            mix_after=(3, min(volume, 20)),
        )

Guidelines:

  • Use explicit lists for safety-critical well order.
  • Use zip(..., strict=True) only if the robot's Python version supports it; the current baseline uses Python 3.10 and does.
  • Validate list lengths before command generation.
  • Name physical quantities with units, for example volume_ul and temperature_c.
  • Put all robot commands under run() or helpers called from run().
  • Let Protocol API errors stop analysis or execution. Do not broadly catch exceptions and continue physical motion.
  • Use protocol.pause() for required operator intervention.

protocol.comment() text is computed during analysis. Do not rely on it to report a live sensor value that only exists during physical execution.

Raw lines 249 to 285
286## 9. Plan Tips and Contamination
288Define a policy before choosing `new_tip`:
290- `"always"` with `transfer()`: a new tip before each aspiration, including
291 refills. Standard `distribute()` and `consolidate()` still use just one tip
292 for the command, even with `"always"`; they do not isolate samples.
293- `"once"`: one tip for an entire complex command; safe only when all contacts
294 share a contamination domain.
295- `"never"`: caller must already hold a tip and must handle disposal.
297Examples of unsafe optimization:
299- Dispensing into samples and returning to a shared reagent with the same tip.
300- Reusing a tip across positive and negative controls.
301- Returning a wet tip to a rack for later unrelated use.
302- Reusing tips after liquid presence detection when the next detection expects
303 a fresh, dry tip.
305Count multi-channel tips as tip sets. A full 8-channel pickup consumes eight
306tips, and a full 96-channel pickup consumes an entire rack.

9. Plan Tips and Contamination

Define a policy before choosing new_tip:

  • "always" with transfer(): a new tip before each aspiration, including refills. Standard distribute() and consolidate() still use just one tip for the command, even with "always"; they do not isolate samples.
  • "once": one tip for an entire complex command; safe only when all contacts share a contamination domain.
  • "never": caller must already hold a tip and must handle disposal.

Examples of unsafe optimization:

  • Dispensing into samples and returning to a shared reagent with the same tip.
  • Reusing a tip across positive and negative controls.
  • Returning a wet tip to a rack for later unrelated use.
  • Reusing tips after liquid presence detection when the next detection expects a fresh, dry tip.

Count multi-channel tips as tip sets. A full 8-channel pickup consumes eight tips, and a full 96-channel pickup consumes an entire rack.

Raw lines 286 to 307
308## 10. Budget Volumes
310For each source:
312```text
313required source volume =
314 delivered volume
315 + disposal volume
316 + mixing and pre-wet loss
317 + geometry-dependent dead volume
318 + validated reserve
319```
321For each destination, calculate the maximum intermediate volume, not just the
322final expected volume. Include mix strokes, carryover splits, and any material
323left before a subsequent transfer.
325Do not use a pipette below its supported range. For example, 100 nL is below
326the range of every current Opentrons pipette and requires a different
327dispensing technology or a redesigned dilution scheme.

10. Budget Volumes

For each source:

required source volume =
    delivered volume
  + disposal volume
  + mixing and pre-wet loss
  + geometry-dependent dead volume
  + validated reserve

For each destination, calculate the maximum intermediate volume, not just the final expected volume. Include mix strokes, carryover splits, and any material left before a subsequent transfer.

Do not use a pipette below its supported range. For example, 100 nL is below the range of every current Opentrons pipette and requires a different dispensing technology or a redesigned dilution scheme.

Raw lines 308 to 328
329## 11. Data and Dependencies
331- Prefer runtime CSV parameters for operator-supplied tabular data.
332- Bundle static data when the workflow requires a fixed resource.
333- Validate file schema, required columns, allowed well names, volume ranges,
334 duplicates, and total volume before generating robot commands.
335- Do not download data or packages during a run.
336- Do not embed credentials or make a protocol depend on broad environment
337 variable access.
338- Pin the local `opentrons` package used for simulation, but remember that the
339 robot executes its installed software stack.

11. Data and Dependencies

  • Prefer runtime CSV parameters for operator-supplied tabular data.
  • Bundle static data when the workflow requires a fixed resource.
  • Validate file schema, required columns, allowed well names, volume ranges, duplicates, and total volume before generating robot commands.
  • Do not download data or packages during a run.
  • Do not embed credentials or make a protocol depend on broad environment variable access.
  • Pin the local opentrons package used for simulation, but remember that the robot executes its installed software stack.
Raw lines 329 to 340
341## 12. Review Checklist
343- Correct robot and API level.
344- Current pipette load names and compatible tips.
345- Exact labware definitions and adapters.
346- Explicit Flex trash or waste chute.
347- No deck conflicts or unreachable partial-nozzle targets.
348- Source and destination volume budgets pass.
349- Tip count covers every branch and retry policy.
350- Contamination policy is documented.
351- Runtime parameter defaults are safe and simulation-ready.
352- Module state transitions are complete.
353- Every operator pause has an actionable message.
354- Local simulation and App analysis both succeed.
355- New geometry is covered by a nonhazardous dry run.

12. Review Checklist

  • Correct robot and API level.
  • Current pipette load names and compatible tips.
  • Exact labware definitions and adapters.
  • Explicit Flex trash or waste chute.
  • No deck conflicts or unreachable partial-nozzle targets.
  • Source and destination volume budgets pass.
  • Tip count covers every branch and retry policy.
  • Contamination policy is documented.
  • Runtime parameter defaults are safe and simulation-ready.
  • Module state transitions are complete.
  • Every operator pause has an actionable message.
  • Local simulation and App analysis both succeed.
  • New geometry is covered by a nonhazardous dry run.
Raw lines 341 to 355