diff --git a/opentrons-ai-server/api/domain/config_anthropic.py b/opentrons-ai-server/api/domain/config_anthropic.py
index beebc16d5ec..718e8f283c9 100644
--- a/opentrons-ai-server/api/domain/config_anthropic.py
+++ b/opentrons-ai-server/api/domain/config_anthropic.py
@@ -1,23 +1,50 @@
SYSTEM_PROMPT = """
-You are a friendly and knowledgeable AI assistant specializing in Opentrons protocol development.
-You help scientists create and optimize protocols using the Opentrons Python API v2.
-
-Your key responsibilities:
-1. Welcome scientists warmly and understand their protocol needs
-2. Generate accurate Python protocols using standard Opentrons labware (see in )
-3. Provide clear explanations and documentation
-4. Flag potential safety or compatibility issues
-5. Suggest protocol optimizations when appropriate
-
-Important guidelines:
-- Always verify labware compatibility before generating protocols
-- Include appropriate error handling in generated code
-- Provide clear setup instructions and prerequisites
-- Flag any potential safety concerns
-- Format code examples using standard Python conventions
-
-If you encounter requests outside your knowledge of Opentrons capabilities,
-ask for clarification rather than making assumptions.
+You are an expert AI assistant specializing in Opentrons protocol development,
+combining deep knowledge of laboratory automation with practical programming expertise.
+Your mission is to help scientists automate their laboratory workflows efficiently and
+safely using the Opentrons Python API v2 and provided documents in .
+
+
+- Complete mastery of Opentrons Python API v2
+- Deep understanding of laboratory protocols and liquid handling principles
+- Expertise in all Opentrons hardware specifications and limitations
+- Comprehensive knowledge of supported labware and their compatibility
+
+
+1. Protocol Development & Optimization
+ - Generate precise, efficient protocols using provided documentation
+ - Implement proper tip management and resource calculation before code generation
+ - Use transfer functions optimally to avoid unnecessary loops
+ - Validate all variables, well positions, and module compatibility
+ - Follow best practices for error prevention and handling
+ - Verify sufficient tips and proper deck layout
+ - Ensure correct API version compatibility (≥2.16 for Flex features)
+
+2.
+ - Welcome scientists warmly and understand their protocol needs
+ - Maintain a professional yet approachable tone
+ - Ask clarifying questions when requirements are ambiguous
+ - Provide rationale for technical decisions and recommendations
+ - Offer alternatives when requested features aren't possible
+ - Guide users toward best practices
+
+3.
+ - Calculate and validate total tip requirements before protocol generation
+ - Plan efficient tip usage and replacement strategy
+ - Include explicit tip tracking in protocols
+ - Track and optimize reagent usage
+ - Manage deck space efficiently based on provided layout documentation
+ - Ensure proper module-labware compatibility
+ - Verify correct adapter usage for temperature-sensitive labware
+
+4.
+ - Verify all variables are defined before use
+ - Confirm tip rack quantity matches transfer operations
+ - Validate all well positions exist in specified labware
+ - Check module-labware compatibility
+ - Verify correct API version for all features
+ - Ensure proper slot assignments
+ - Validate sufficient resources for complete protocol execution
"""
DOCUMENTS = """
@@ -29,14 +56,15 @@
Follow these instructions to handle the user's prompt:
1. :
- a) A request to generate a protocol
- b) A question about the Opentrons Python API v2 or about details of protocol
- c) A common task (e.g., value changes, OT-2 to Flex conversion, slot correction)
- d) An unrelated or unclear request
- e) A tool calling. If a user calls simulate protocol explicity, then call.
- f) A greeting. Respond kindly.
+ - A request to generate a protocol
+ - A question about the Opentrons Python API v2 or about details of protocol
+ - A common task (e.g., value changes, OT-2 to Flex conversion, slot correction)
+ - An unrelated or unclear request
+ - A tool calling. If a user calls simulate protocol explicity, then call.
+ - A greeting. Respond kindly.
+ - A protocol type (e.g., serial dilution, before generation see in
- Note: when you respond you dont need mention the category or the type.
+ Note: when you respond you do not need mention the category or the type.
2. If the prompt is unrelated or unclear, ask the user for clarification.
I'm sorry, but your prompt seems unclear. Could you please provide more details?
@@ -65,22 +93,17 @@
b) If any crucial information is missing, ask for clarification:
- To generate an accurate protocol, I need more information about [missing elements].
- Please provide details about:
- [List of missing elements]
+ To generate an accurate protocol, I need more information about [missing elements].
+ Please provide details about:
+ [List of missing elements]
- c) If all necessary information is available, generate the protocol using the following structure:
+ c) Generate the protocol using the following structure:
+ - apiLevel and robotType are required otherwise robot does not run.
```python
from opentrons import protocol_api
- metadata = {{
- 'protocolName': '[Protocol name based on user prompt]',
- 'author': 'AI Assistant',
- 'description': '[Brief description based on user prompt]'
- }}
-
requirements = {{
'robotType': '[Robot type: OT-2(default) for Opentrons OT-2, Flex for Opentrons Flex]',
'apiLevel': '[apiLevel, default: 2.19]'
@@ -102,9 +125,12 @@ def run(protocol: protocol_api.ProtocolContext):
# For Flex protocols using API version 2.16 or later, load trash bin
trash = protocol.load_trash_bin('A3')
+ # any calculation, setup, liquids
+
# Protocol steps
[Step-by-step protocol commands with comments]
- [Please make sure that the transfer function is used with the new_tip parameter correctly]
+ [Please make sure that the transfer function is used with the new_tip parameter explicitly and correctly]
+ [Arguments for `new_tip` must be explict all the time, default: `new_tip='once'`]
```
d) Use the `transfer` function to handle iterations over wells and volumes. Provide lists of source and
@@ -140,8 +166,9 @@ def run(protocol: protocol_api.ProtocolContext):
e) In the end, make sure you show generate well-written protocol with proper short but useful comments.
+ f) If it is to fix, then just fix and do not simulate.
-5. Common model issues to avoid:
+5.
- Model outputs `p300_multi` instead of `p300_multi_gen2`.
- Model outputs `thermocyclerModuleV1` instead of `thermocyclerModuleV2`.
- Model outputs `opentrons_flex_96_tiprack_50ul` instead of `opentrons_flex_96_filtertiprack_50ul`.
@@ -195,19 +222,21 @@ def run(protocol: protocol_api.ProtocolContext):
which only has positions A1-D6, causing a KeyError when trying to reference well 'A7'.
- Model tries to close thermocycler before opening it. Attempted to access labware inside a closed thermocycler,
the thermocycler must be opened first.
- - Required Validation Steps:
+ -
- Verify all variables are defined before use
- Confirm tip rack quantity matches transfer count
- Validate all well positions exist in labware
- Check module-labware compatibility
- Verify correct API version for all features used
+ - Verify apiLevel is defined
+ - Verify tips are sufficient for the protocol to cover all steps
6. If slots are not defined, refer to for proper slot definitions.
Make sure slots are different for different labware. If the source and destination are not defined,
then you define yourself but inform user with your choice, because user may want to change them.
7. If the request lacks sufficient information to generate a protocol, use
- as a reference to generate a basic protocol.
+ as a reference to generate a basic protocol. For serial dilution please refer to .
Remember to use only the information provided in the . Do not introduce any external information or assumptions.
diff --git a/opentrons-ai-server/api/storage/docs/serial_dilution_samples.md b/opentrons-ai-server/api/storage/docs/serial_dilution_examples.md
similarity index 52%
rename from opentrons-ai-server/api/storage/docs/serial_dilution_samples.md
rename to opentrons-ai-server/api/storage/docs/serial_dilution_examples.md
index ad9a5ee24be..0fa0ed870e2 100644
--- a/opentrons-ai-server/api/storage/docs/serial_dilution_samples.md
+++ b/opentrons-ai-server/api/storage/docs/serial_dilution_examples.md
@@ -1,4 +1,508 @@
-# Serial dilution examples
+# Tutorial: Creating a Serial Dilution Protocol
+
+## Introduction
+
+This tutorial will guide you through creating a Python protocol file from scratch. At the end of this process you'll have a complete protocol that can run on a Flex or an OT-2 robot.
+
+### What You'll Automate
+
+The lab task that you'll automate in this tutorial is serial dilution: taking a solution and progressively diluting it by transferring it stepwise across a plate from column 1 to column 12. With just a dozen or so lines of code, you can instruct your robot to perform the hundreds of individual pipetting actions necessary to fill an entire 96-well plate. And all of those liquid transfers will be done automatically, so you'll have more time to do other work in your lab.
+
+### Hardware and Labware
+
+Before running a protocol, you'll want to have the right kind of hardware and labware ready for your Flex or OT-2.
+
+- **Flex users** Most Flex code examples will use a Flex 1-Channel 1000 μL pipette.
+- **OT-2 users** You can use either a single-channel or 8-channel pipette for this tutorial. Most OT-2 code examples will use a P300 Single-Channel GEN2 pipette.
+
+The Flex and OT-2 use similar labware for serial dilution. The tutorial code will use the labware listed in the table below, but as long as you have labware of each type you can modify the code to run with your labware.
+
+| Labware type | Labware name | API load name |
+| ------------- | ------------------------------ | --------------------------------- |
+| Reservoir | NEST 12 Well Reservoir 15 mL | `nest_12_reservoir_15ml` |
+| Well plate | NEST 96 Well Plate 200 µL Flat | `nest_96_wellplate_200ul_flat` |
+| Flex tip rack | Opentrons Flex Tips, 200 µL | `opentrons_flex_96_tiprack_200ul` |
+| OT-2 tip rack | Opentrons 96 Tip Rack | `opentrons_96_tiprack_300ul` |
+
+For the liquids, you can use plain water as the diluent and water dyed with food coloring as the solution.
+
+## Create a Protocol File
+
+Let's start from scratch to create your serial dilution protocol. Open up a new file in your editor and start with the line:
+
+```python
+from opentrons import protocol_api
+```
+
+Throughout this documentation, you'll see protocols that begin with the `import` statement shown above. It identifies your code as an Opentrons protocol. This statement is not required, but including it is a good practice and allows most code editors to provide helpful autocomplete suggestions.
+
+Everything else in the protocol file is required. Next, you'll specify the version of the API you're using. Then comes the core of the protocol: defining a single `run()` function that provides the locations of your labware, states which kind of pipettes you'll use, and finally issues the commands that the robot will perform.
+
+For this tutorial, you'll write very little Python outside of the `run()` function. But for more complex applications it's worth remembering that your protocol file _is_ a Python script, so any Python code that can run on your robot can be a part of a protocol.
+
+### Metadata
+
+Every protocol needs to have a metadata dictionary with information about the protocol. At minimum, you need to specify what version of the API the protocol requires. The scripts for this tutorial were validated against API version 2.16, so specify:
+
+```python
+metadata = {"apiLevel": "2.16"}
+```
+
+You can include any other information you like in the metadata dictionary. The fields `protocolName`, `description`, and `author` are all displayed in the Opentrons App, so it's a good idea to expand the dictionary to include them:
+
+```python
+metadata = {
+ "apiLevel": "2.16",
+ "protocolName": "Serial Dilution Tutorial",
+ "description": """This protocol is the outcome of following the
+ Python Protocol API Tutorial located at
+ https://docs.opentrons.com/v2/tutorial.html. It takes a
+ solution and progressively dilutes it by transferring it
+ stepwise across a plate.""",
+ "author": "New API User"
+}
+```
+
+### Requirements
+
+The `requirements` code block can appear before _or_ after the `metadata` code block in a Python protocol. It uses the following syntax and accepts two arguments: `robotType` and `apiLevel`.
+
+Whether you need a `requirements` block depends on your robot model and API version.
+
+- **Flex:** The `requirements` block is always required. And, the API version does not go in the `metadata` section. The API version belongs in the `requirements`. For example:
+
+```python
+requirements = {"robotType": "Flex", "apiLevel": "2.16"}
+```
+
+- **OT-2:** The `requirements` block is optional, but including it is a recommended best practice, particularly if you're using API version 2.15 or greater. If you do use it, remember to remove the API version from the `metadata`. For example:
+
+```python
+requirements = {"robotType": "OT-2", "apiLevel": "2.16"}
+```
+
+### The `run()` function
+
+Now it's time to actually instruct the Flex or OT-2 how to perform serial dilution. All of this information is contained in a single Python function, which has to be named `run`. This function takes one argument, which is the _protocol context_. Many examples in these docs use the argument name `protocol`, and sometimes they specify the argument's type:
+
+```python
+def run(protocol: protocol_api.ProtocolContext):
+```
+
+With the protocol context argument named and typed, you can start calling methods on `protocol` to add labware and hardware.
+
+#### Labware
+
+For serial dilution, you need to load a tip rack, reservoir, and 96-well plate on the deck of your Flex or OT-2. Loading labware is done with the `load_labware()` method of the protocol context, which takes two arguments: the standard labware name as defined in the Opentrons Labware Library, and the position where you'll place the labware on the robot's deck.
+
+##### Flex
+
+Here's how to load the labware on a Flex in slots D1, D2, and D3:
+
+```python
+def run(protocol: protocol_api.ProtocolContext):
+ tips = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "D1")
+ reservoir = protocol.load_labware("nest_12_reservoir_15ml", "D2")
+ plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "D3")
+```
+
+If you're using a different model of labware, find its name in the Labware Library and replace it in your code.
+
+##### OT-2
+
+Here's how to load the labware on an OT-2 in slots 1, 2, and 3:
+
+```python
+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("nest_96_wellplate_200ul_flat", 3)
+```
+
+If you're using a different model of labware, find its name in the Labware Library and replace it in your code.
+
+You may notice that these deck maps don't show where the liquids will be at the start of the protocol. Liquid definitions aren't required in Python protocols, unlike protocols made in Protocol Designer. If you want to identify liquids, see Labeling Liquids in Wells. (Sneak peek: you'll put the diluent in column 1 of the reservoir and the solution in column 2 of the reservoir.)
+
+#### Trash Bin
+
+Flex and OT-2 both come with a trash bin for disposing used tips.
+
+The OT-2 trash bin is fixed in slot 12. Since it can't go anywhere else on the deck, you don't need to write any code to tell the API where it is.
+
+Flex lets you put a trash bin in multiple locations on the deck. You can even have more than one trash bin, or none at all (if you use the waste chute instead, or if your protocol never trashes any tips). For serial dilution, you'll need to dispose used tips, so you also need to tell the API where the trash container is located on your robot. Loading a trash bin on Flex is done with the `load_trash_bin()` method, which takes one argument: its location. Here's how to load the trash in slot A3:
+
+```python
+trash = protocol.load_trash_bin("A3")
+```
+
+#### Pipettes
+
+Next you'll specify what pipette to use in the protocol. Loading a pipette is done with the `load_instrument()` method, which takes three arguments: the name of the pipette, the mount it's installed in, and the tip racks it should use when performing transfers. Load whatever pipette you have installed in your robot by using its standard pipette name. Here's how to load the pipette in the left mount and instantiate it as a variable named `left_pipette`:
+
+For Flex:
+
+```python
+left_pipette = protocol.load_instrument("flex_1channel_1000", "left", tip_racks=[tips])
+```
+
+For OT-2:
+
+```python
+left_pipette = protocol.load_instrument("p300_single_gen2", "left", tip_racks=[tips])
+```
+
+Since the pipette is so fundamental to the protocol, it might seem like you should have specified it first. But there's a good reason why pipettes are loaded after labware: you need to have already loaded `tips` in order to tell the pipette to use it. And now you won't have to reference `tips` again in your code — it's assigned to the `left_pipette` and the robot will know to use it when commanded to pick up tips.
+
+Note: You may notice that the value of `tip_racks` is in brackets, indicating that it's a list. This serial dilution protocol only uses one tip rack, but some protocols require more tips, so you can assign them to a pipette all at once, like `tip_racks=[tips1, tips2]`.
+
+#### Steps
+
+Finally, all of your labware and hardware is in place, so it's time to give the robot pipetting steps (also known as commands). The required steps of the serial dilution process break down into three main phases:
+
+1. Measure out equal amounts of diluent from the reservoir to every well on the plate.
+2. Measure out equal amounts of solution from the reservoir into wells in the first column of the plate.
+3. Move a portion of the combined liquid from column 1 to 2, then from column 2 to 3, and so on all the way to column 12.
+
+Thanks to the flexibility of the API's `transfer()` method, which combines many building block commands into one call, each of these phases can be accomplished with a single line of code! You'll just have to write a few more lines of code to repeat the process for as many rows as you want to fill.
+
+Let's start with the diluent. This phase takes a larger quantity of liquid and spreads it equally to many wells. `transfer()` can handle this all at once, because it accepts either a single well or a list of wells for its source and destination:
+
+```python
+left_pipette.transfer(100, reservoir["A1"], plate.wells())
+```
+
+Breaking down these single lines of code shows the power of complex commands. The first argument is the amount to transfer to each destination, 100 µL. The second argument is the source, column 1 of the reservoir (which is still specified with grid-style coordinates as `A1` — a reservoir only has an A row). The third argument is the destination. Here, calling the `wells()` method of `plate` returns a list of _every well_, and the command will apply to all of them.
+
+In plain English, you've instructed the robot, "For every well on the plate, aspirate 100 µL of fluid from column 1 of the reservoir and dispense it in the well." That's how we understand this line of code as scientists, yet the robot will understand and execute it as nearly 200 discrete actions.
+
+Now it's time to start mixing in the solution. To do this row by row, nest the commands in a `for` loop:
+
+```python
+for i in range(8):
+ row = plate.rows()[i]
+```
+
+Using Python's built-in `range` class is an easy way to repeat this block 8 times, once for each row. This also lets you use the repeat index `i` with `plate.rows()` to keep track of the current row.
+
+In each row, you first need to add solution. This will be similar to what you did with the diluent, but putting it only in column 1 of the plate. It's best to mix the combined solution and diluent thoroughly, so add the optional `mix_after` argument to `transfer()`:
+
+```python
+left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50))
+```
+
+As before, the first argument specifies to transfer 100 µL. The second argument is the source, column 2 of the reservoir. The third argument is the destination, the element at index 0 of the current `row`. Since Python lists are zero-indexed, but columns on labware start numbering at 1, this will be well A1 on the first time through the loop, B1 the second time, and so on. The fourth argument specifies to mix 3 times with 50 µL of fluid each time.
+
+Finally, it's time to dilute the solution down the row. One approach would be to nest another `for` loop here, but instead let's use another feature of the `transfer()` method, taking lists as the source and destination arguments:
+
+```python
+left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50))
+```
+
+There's some Python shorthand here, so let's unpack it. You can get a range of indices from a list using the colon `:` operator, and omitting it at either end means "from the beginning" or "until the end" of the list. So the source is `row[:11]`, from the beginning of the row until its 11th item. And the destination is `row[1:]`, from index 1 (column 2!) until the end. Since both of these lists have 11 items, `transfer()` will _step through them in parallel_, and they're constructed so when the source is 0, the destination is 1; when the source is 1, the destination is 2; and so on. This condenses all of the subsequent transfers down the row into a single line of code.
+
+All that remains is for the loop to repeat these steps, filling each row down the plate.
+
+That's it! If you're using a single-channel pipette, you're ready to try out your protocol.
+
+#### 8-Channel Pipette
+
+If you're using an 8-channel pipette, you'll need to make a couple tweaks to the single-channel code from above. Most importantly, whenever you target a well in row A of a plate with an 8-channel pipette, it will move its topmost tip to row A, lining itself up over the entire column.
+
+Thus, when adding the diluent, instead of targeting every well on the plate, you should only target the top row:
+
+```python
+left_pipette.transfer(100, reservoir["A1"], plate.rows()[0])
+```
+
+And by accessing an entire column at once, the 8-channel pipette effectively implements the `for` loop in hardware, so you'll need to remove it:
+
+```python
+row = plate.rows()[0]
+left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50))
+left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50))
+```
+
+Instead of tracking the current row in the `row` variable, this code sets it to always be row A (index 0).
+
+## Complete Protocol Examples
+
+Here are the complete protocols for both single-channel and 8-channel configurations:
+
+### Single-Channel Protocol (Flex)
+
+```python
+from opentrons import protocol_api
+
+requirements = {"robotType": "Flex", "apiLevel": "2.16"}
+
+metadata = {
+ "protocolName": "Serial Dilution Tutorial",
+ "description": """This protocol is the outcome of following the
+ Python Protocol API Tutorial located at
+ https://docs.opentrons.com/v2/tutorial.html. It takes a
+ solution and progressively dilutes it by transferring it
+ stepwise across a plate.""",
+ "author": "New API User"
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ # Load labware
+ tips = protocol.load_labware("opentrons_flex_96_tiprack_200ul", "D1")
+ reservoir = protocol.load_labware("nest_12_reservoir_15ml", "D2")
+ plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "D3")
+ trash = protocol.load_trash_bin("A3")
+
+ # Load pipette
+ left_pipette = protocol.load_instrument(
+ "flex_1channel_1000",
+ "left",
+ tip_racks=[tips]
+ )
+
+ # Distribute diluent
+ left_pipette.transfer(100, reservoir["A1"], plate.wells())
+
+ # Perform serial dilution
+ for i in range(8):
+ row = plate.rows()[i]
+ # Add solution to first well of row
+ left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50))
+ # Perform dilution down the row
+ left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50))
+```
+
+### 8-Channel Protocol (OT-2)
+
+```python
+from opentrons import protocol_api
+
+requirements = {"robotType": "OT-2", "apiLevel": "2.16"}
+
+metadata = {
+ "protocolName": "Serial Dilution Tutorial",
+ "description": """This protocol is the outcome of following the
+ Python Protocol API Tutorial located at
+ https://docs.opentrons.com/v2/tutorial.html. It takes a
+ solution and progressively dilutes it by transferring it
+ stepwise across a plate.""",
+ "author": "New API User"
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ # Load labware
+ tips = protocol.load_labware("opentrons_96_tiprack_300ul", 1)
+ reservoir = protocol.load_labware("nest_12_reservoir_15ml", 2)
+ plate = protocol.load_labware("nest_96_wellplate_200ul_flat", 3)
+
+ # Load pipette
+ left_pipette = protocol.load_instrument(
+ "p300_single_gen2",
+ "left",
+ tip_racks=[tips]
+ )
+
+ # Distribute diluent to top row (8-channel will handle all rows)
+ left_pipette.transfer(100, reservoir["A1"], plate.rows()[0])
+
+ # Perform serial dilution using only the top row
+ row = plate.rows()[0]
+ left_pipette.transfer(100, reservoir["A2"], row[0], mix_after=(3, 50))
+ left_pipette.transfer(100, row[:11], row[1:], mix_after=(3, 50))
+```
+
+These complete examples show how to implement the entire serial dilution workflow for both Flex and OT-2 robots, using either single-channel or 8-channel pipettes. The protocols will:
+
+1. Load all necessary labware and pipettes
+2. Distribute diluent to all wells
+3. Add solution to the first column
+4. Perform serial dilution across the plate
+5. Mix thoroughly at each step
+
+The key difference between single-channel and 8-channel implementations is that the 8-channel pipette naturally handles all rows simultaneously, eliminating the need for the row-by-row loop.
+
+
+
+
+
+Write a protocol using the Opentrons Python Protocol API v2 for Opentrons OT-2 robot according to the following description:
+
+Application:
+Other
+
+Description:
+A simple serial dilution
+
+Pipette mount(s):
+
+P20 Single-Channel GEN2 is mounted on the left
+Modules:
+
+Labware:
+
+NEST 12 Well Reservoir 15 mL x 1
+NEST 96 Well Plate 100 µL PCR Full Skirt x 1
+Opentrons OT-2 96 Tip Rack 20 µL x 1
+Liquids:
+
+Liquid 1: diluent liquid, solution
+
+Steps:
+
+Step 1: For every well on the plate, aspirate 100 µL of fluid from column 1 of the reservoir and dispense it in the well plate. Use the same tip for each transfer.
+
+Step 2: Dilute the solution down the row:
+
+- Transfer 100ul of diluent from column 2 of the reservoir to the first well of the row. Mix 3 times with 50 ul. Use the same tip for each transfer.
+- Perform a serial dilution by transferring 100 µL from each well to the next well in the row (from left to right). Mix 3 times with 50 µL after each transfer. Use the same tip for each transfer.
+
+```python
+from opentrons import protocol_api
+
+metadata = {
+ 'protocolName': 'Serial Dilution',
+ 'author': 'Assistant',
+ 'description': 'Perform serial dilution with diluent and sample distribution'
+}
+
+requirements = {
+ "robotType": "OT-2",
+ "apiLevel": "2.19"
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ # Load labware
+ tiprack = protocol.load_labware('opentrons_96_tiprack_300ul', 1) # Changed to 300ul tips
+ reservoir = protocol.load_labware('nest_12_reservoir_15ml', 2)
+ plate = protocol.load_labware('nest_96_wellplate_100ul_pcr_full_skirt', 3)
+
+ # Load P300 pipette instead of P20
+ pipette = protocol.load_instrument('p300_single_gen2', 'left', tip_racks=[tiprack])
+
+ # Step 1: Distribute diluent to all wells using the same tip
+ pipette.distribute(
+ 100,
+ reservoir.columns()[0],
+ plate.wells(),
+ new_tip='once'
+ )
+
+ # Step 2: Perform serial dilution for each row
+ for i in range(8): # Process each row
+ row = plate.rows()[i] # Get current row
+
+ # Transfer solution from reservoir to first well and mix
+ pipette.transfer(
+ 100,
+ reservoir.columns()[1],
+ row[0],
+ mix_after=(3, 50),
+ new_tip='once'
+ )
+
+ # Dilute down the row
+ pipette.transfer(
+ 100,
+ row[:11], # Source wells: first 11 wells
+ row[1:], # Destination wells: last 11 wells
+ mix_after=(3, 50),
+ new_tip='once'
+ )
+
+```
+
+
+
+
+
+Application:
+Other
+
+Description:
+A simple serial dilution protocol
+
+Pipette mount(s):
+
+P300 8-Channel GEN2 is mounted on the left
+Modules: None
+
+Labware:
+
+Opentrons OT-2 96 Tip Rack 300 µL x 1
+NEST 12 Well Reservoir 15 mL x 1
+NEST 96 Well Plate 100 µL PCR Full Skirt x 1
+Liquids:
+
+Liquid 1: diluent, solution
+
+Steps:
+
+Step 1: Transfer 100ul of diluent from reservoir first column to all wells of the well plate. Use the same tip for each transfer.
+Step 2: Dilute down the row:
+
+- Transfer 100ul of diluent from second column of reservoir to the first row of the first column of the well plate. Mix 3 times with 50ul. Use the same tip for each transfer.
+- Transfer 100ul of solution from each well to the next well in the row. Mix 3 times with 50ul. Use the same tip for each transfer.
+
+```python
+from opentrons import protocol_api
+
+metadata = {
+ 'protocolName': 'Serial Dilution Protocol',
+ 'author': 'Assistant',
+ 'description': 'A simple serial dilution protocol using multi-channel pipette'
+}
+
+requirements = {
+ "robotType": "OT-2",
+ "apiLevel": "2.19"
+}
+
+def run(protocol: protocol_api.ProtocolContext):
+ # Load labware
+ tips = protocol.load_labware("opentrons_96_tiprack_300ul", "1")
+ reservoir = protocol.load_labware("nest_12_reservoir_15ml", "2")
+ plate = protocol.load_labware("nest_96_wellplate_100ul_pcr_full_skirt", "3")
+
+ # Load pipette
+ p300_multi = protocol.load_instrument(
+ "p300_multi_gen2",
+ "left",
+ tip_racks=[tips]
+ )
+
+ # Step 1: Distribute diluent to all wells
+ p300_multi.transfer(
+ 100,
+ reservoir['A1'],
+ plate.rows()[0],
+ new_tip='once'
+ )
+
+ # Step 2: Serial dilution along the row
+ # Transfer from reservoir and mix
+ p300_multi.transfer(
+ 100,
+ reservoir['A2'],
+ plate.rows()[0][0],
+ mix_after=(3, 50),
+ new_tip='once'
+ )
+
+ # Perform serial dilution down the row
+ p300_multi.transfer(
+ 100,
+ plate.rows()[0][:11],
+ plate.rows()[0][1:],
+ mix_after=(3, 50),
+ new_tip='once'
+ )
+
+```
+
+
+
+
+
+# More serial dilution protocol examples
## 1. Serial dilution example
diff --git a/opentrons-ai-server/api/storage/docs/standard-loadname-info.md b/opentrons-ai-server/api/storage/docs/standard-loadname-info.md
index 5ca402ec2f3..f956a95cd6c 100644
--- a/opentrons-ai-server/api/storage/docs/standard-loadname-info.md
+++ b/opentrons-ai-server/api/storage/docs/standard-loadname-info.md
@@ -570,6 +570,7 @@ Total number of labware: 73
- Well count: 96
- Max volume: 2.4 mL
- Well shape: U-bottom
+
@@ -597,3 +598,38 @@ Total number of labware: 73
- volume: 5–1000 µL
+
+
+
+ - Loadname: `temperature module` or `tempdeck`
+ - Introduced in API Version: 2.0
+
+
+- Loadname: `temperature module gen2`
+- Introduced in API Version: 2.3
+
+
+- Loadname: `magnetic module` or `magdeck`
+- Introduced in API Version: 2.0
+
+
+- Loadname: `magnetic module gen2`
+- Introduced in API Version: 2.3
+
+
+- Loadname: `thermocycler module` or `thermocycler`
+- Introduced in API Version: 2.0
+
+
+- Loadname: `thermocycler module gen2` or `thermocyclerModuleV2`
+- Introduced in API Version: 2.13
+
+
+- Loadname: `heaterShakerModuleV1`
+- Introduced in API Version: 2.13
+
+
+- Loadname: `magneticBlockV1`
+- Introduced in API Version: 2.15
+
+