# Pipette Desk > Paste an Opentrons Protocol API v2 protocol - for the OT-2 or the Flex - plus the plate map of > what is actually in the labware, and clear the run for the bench. A free in-browser reader builds > the deck map, budgets tips against the racks the pipettes were given, checks every volume against > the pipette that performs it, and adds up what each well gives out and takes in. Four metered > lanes then work the run: a deck-and-volume audit, a dry-run plan, the operator run sheet, and the > ELN record with reagent-lot reconciliation. Live at https://pipette-desk.skillsafe.ai/ ยท API tutorial at https://pipette-desk.skillsafe.ai/api.html ## What it is for The moment this app exists for is the last one before a run starts: the protocol is written, the labware is on the deck, and somebody has to decide whether to close the lid. That decision has an arithmetic half and a judgement half. The arithmetic half is done in the browser, for free, with no account: how many tips this run really consumes once the loops are multiplied out and the channel count is applied; whether every volume sits inside the envelope of the pipette that performs it; which destination well overflows and which source runs dry. The judgement half is what the four lanes are for. ## The free lane, in detail Everything here runs client-side, on the page, before any sign-in and without spending anything. - **Deck map.** Every `load_labware`, `load_module`, `load_adapter`, `load_trash_bin` and `load_waste_chute` resolved to its slot, with the load name, the well format and the per-position working volume. Two labware in one slot is reported as a conflict; labware in the OT-2's slot 12 is reported as a collision with the fixed trash; a Flex on API 2.16 or later with no trash bin is reported as a run-time failure at the first `drop_tip`. - **Labware capacities are derived, not looked up.** Opentrons load names follow `___`, and both the count and the capacity are machine-readable - `nest_96_wellplate_200ul_flat` is 96 positions of 200 uL, `opentrons_24_tuberack_nest_1.5ml_snapcap` is 24 of 1500 uL. A short override table covers the names that encode a total rather than a per-well volume. A custom definition that follows no convention is reported as **unknown**, and every check that depended on its capacity is reported as unassessable rather than guessed. - **Pipettes and the tip budget.** The seventeen OT-2 and Flex load names with their real minimum and maximum volumes and their channel counts. Tips are counted as pickups multiplied by the channel count - an 8-channel pickup consumes eight tips - and by every enclosing loop, against the tips in the racks actually passed in `tip_racks`. A rack loaded but never handed to a pipette is its own finding, because it is invisible to the pipette at run time. - **Loop multipliers, by indentation.** `for i in range(8)` multiplies its body by eight; `for well in plate.wells()` binds the loop variable to the well sequence and multiplies by the plate size; `enumerate` binds both names. A `while` loop or a bound this reader cannot evaluate makes every total inside it an explicit lower bound rather than a wrong number. - **Volume envelope, graded by what actually happens.** `transfer`, `distribute` and `consolidate` split a volume larger than the pipette maximum by themselves, so an over-max transfer is slow rather than broken and is reported as low severity. A bare `aspirate`, `dispense` or `mix` does not split, and over-max there raises at run time - reported as blocking. Under the pipette minimum the delivered volume is not the requested one, and how far under decides whether that is a marginal weigh-out or an unknown volume. - **Per-well liquid accounting.** Every source's total draw and every destination's total delivery, against the well's capacity and, where a plate map declares it, its starting volume. Overflow and depletion come out as arithmetic. Without a starting volume, depletion is only checked against physical capacity and the rest is reported as unassessable - a starting volume that was never supplied is never treated as zero, because that turns every source into a false depletion. - **Plate-map reader.** CSV, TSV or semicolon-separated, with `labware`, `well`, `reagent`, `lot`, `volume_ul`, `concentration` and `expiry`. Header synonyms are matched on the whole normalised token, never as a prefix, so `reagent` and `reagent lot` stay different columns. A volume written as prose - `n/a`, `full`, `unknown` - reads as unknown, not as zero. - **Cross-contamination, with the mitigation.** A `new_tip="never"` transfer whose sources hold several different reagents is a carry-over; the same call over one reagent is a deliberate tip saving worth keeping and worth noting on the run sheet so nobody "fixes" it. With no plate map, neither is provable and that is what is reported. - **Run-time estimate** from per-command costs plus every declared `protocol.delay`, and the operator pause points, which are also the mitigating fact that turns a tip shortfall from a blocker into a run-sheet instruction. - **Golden-ratio sampling.** When a plate map is longer than the model's budget, the rows sent are chosen by a low-discrepancy golden-ratio draw across the whole map, not by every k-th row. A plate map is the most periodic data in a laboratory - reagents cycle every 8 rows down a column, every 12 across - and a fixed stride of 8 or 12 returns one reagent for the entire sample. The overflow and depletion numbers are always computed on every row, locally, whatever is sent. Exports from the free lane: the read as Markdown, the deck map as CSV, the liquid ledger as CSV. ## The four lanes Every lane takes the same work object - the protocol, plus the plate map - and returns the same outer envelope, so the shell, the history record and the export path are single implementations. | `task` | What it answers | What its body carries | | --- | --- | --- | | `audit` | Is this safe to start? | deck review, volume review, tip budget, liquid review, the blocking issues, the pre-run checks | | `dryrun` | What do I prove with water first? | dry-run steps with observable pass criteria, the calibration this protocol depends on, the abort triggers, portability to other platforms | | `runsheet` | What does the operator hold? | a protocols.io-shaped materials list with lots, and numbered steps with durations and hazards | | `eln` | What gets written down? | a Benchling-shaped entry, the inventory that moved with lots and remaining volumes, the registry entities, the deviations to log | The pipeline is inspect, decide, produce, verify, and every result panel carries a button that switches to the next lane with the previous lane's conclusion already carried into the notes. The work object never has to be pasted twice. ## The output envelope Identical in all four lanes: `lane`, `title`, `verdict`, `headline`, `findings[]`, `reconciliation[]`, `context_notes[]`, `unassessable[]`, `body`, `summary`. - `verdict` is one of `clear`, `clear_with_notes`, `hold`, `blocked` - the same four words the free reader uses for its own verdict, deliberately, so a disagreement between them is a real disagreement rather than a translation problem. The page shows those disagreements in their own panel instead of letting a confident `clear` bury arithmetic that says otherwise. - `severity` is one of `blocking`, `high`, `medium`, `low`, `info`; `area` is one of `deck`, `volume`, `tips`, `contamination`, `reagent`, `timing`, `api`, `documentation`, `inventory`. - `reconciliation[]` carries exactly one entry per flag the free reader raised, keyed by its `uid`, with a status of `confirmed`, `adjusted`, `set_aside` or `not_applicable` and a reason. A flag the lane never mentions is displayed as **not mentioned** rather than quietly dropped. - `context_notes[]` gives the operator's own free text a first-class array: every claim comes back as `honoured`, `contradicted` or `unverifiable` against what the protocol actually does. A stated constraint with no effect anywhere is itself a finding. - `unassessable[]` is used in preference to a confident guess, and the free reader's own unassessable list is carried forward rather than silently answered. ## Honest limits - The reader parses the protocol as text. It resolves the common Protocol API location forms and reports the ones it cannot; it does not execute Python, so a volume computed from a value it cannot see is reported as unknown rather than assumed. - Well capacities derived from load names are working volumes by convention, not by measurement. Verify against your own labware definitions. - Run-time estimates are coarse by design - they exist to catch "this run is nine hours long", not to predict the finish to the minute. - None of this replaces a water run. The app says so on the page, and every `dryrun` output is built around the assumption that you will do one. - Nothing pasted leaves the browser until a metered lane is run. The free read never sends anything. ## Source skills, all credited Pipette Desk is a derived work built on four SkillSafe skills, not a republication of any of them: - `@k-dense-ai/opentrons-integration` - the Opentrons Protocol API v2 surface for OT-2 and Flex: load names, deck slots, pipette envelopes, and the `transfer` / `distribute` / `consolidate` semantics that decide how an over-max volume behaves. - `@k-dense-ai/pylabrobot` - vendor-agnostic lab automation: simulation and dry-run practice, and what changes when the same liquid handling moves to another vendor's instrument. - `@k-dense-ai/protocolsio-integration` - the protocols.io document model: a materials list and numbered, individually reproducible steps. - `@k-dense-ai/benchling-integration` - the Benchling model of an ELN entry, a registry entity, and an inventory container with a lot number and a remaining volume. ## For programs Base URL `https://api.skillsafe.ai/v1/app-api`. Mint a token with `POST /guest` (body `{"slug": "pipette-desk"}`, answers 201), read the session with `GET /me`, price a run for free with `POST /estimate`, then `POST /run` and poll, or `POST /run-stream` for SSE. The run body is the input object itself - `task`, `protocol`, `platemap`, `robot`, `goal`, `context`, `prescan_facts` - and is never wrapped in an `input` key. Full tutorial, in eight languages, at https://pipette-desk.skillsafe.ai/api.html. Token management for humans at https://pipette-desk.skillsafe.ai/tokens.html. Model `gpt-terra`. Publisher markup 1000 bps. Reviews are stored in the app's declared `runs` collection, scoped to the calling account, with semantic search over the review title, the protocol name, the pipettes and the verdict.