Skip to content

LIMS example

The LIMS example is the autonomous-science / cloud-lab analog of the bank example. It exercises the same relation primitives — allowlists, numeric aggregation, and per-field substring guards — against a wet-lab effect vocabulary: pipetting, instrument scheduling, and oligo ordering.

It is a toy: a production trusted layer for autonomous science (MADSci nodes, Emerald Cloud Lab, Strateos) would live in a separate domain repo and be installed with clauz3 install. See issue #41 for the bud-off plan and #42 for the biosecurity-screening angle that the no_hazard_sequence contract here gestures at.

Trusted module

Three trusted effects, each with a per-call precondition lifted into a proof obligation. The volume, hours, and copies bounds are written in natural chained-comparison form (0 < volume_ul <= 200); see issue #43 for the fix that made this idiomatic form actually enforce both bounds.

examples/lims/tools/lims/trusted/effects.py

import deal


@deal.pre(
    lambda plate, well, volume_ul, reagent: 0 < volume_ul <= 200,
    message="volume_ul must be in (0, 200]",
)
@deal.has("trusted")
def pipette(plate: str, well: str, volume_ul: int, reagent: str) -> None:
    """MOCK trusted pipette action.

    Transfers ``volume_ul`` microlitres of ``reagent`` into ``well`` on
    ``plate``. Per-call volume is bounded by the trusted layer; total volume
    per reagent and per-plate scope are agent-stated guarantees.
    """
    pass


@deal.pre(
    lambda instrument, plate, hours: 0 < hours <= 24,
    message="hours must be in (0, 24]",
)
@deal.has("trusted")
def submit_protocol(instrument: str, plate: str, hours: int) -> None:
    """MOCK trusted instrument submission.

    Books ``instrument`` for ``hours`` to run a protocol on ``plate``.
    Per-call duration is bounded; instrument allowlist and total runtime
    are agent-stated guarantees.
    """
    pass


@deal.pre(
    lambda seq, copies: 0 < copies <= 1000,
    message="copies must be in (0, 1000]",
)
@deal.has("trusted")
def order_oligo(seq: str, copies: int) -> None:
    """MOCK trusted oligo order.

    Submits an order for ``copies`` of synthesized oligonucleotide ``seq``.
    Sequence content (length, hazard motifs) is an agent-stated guarantee
    and is the surface biosecurity policies attach to.
    """
    pass

The contract vocabulary covers plate scope, reagent and runtime budgets, oligo-length bounds, and a substring-based hazard guard.

examples/lims/tools/lims/trusted/contracts.py

"""LIMS / cloud-lab predicates built from generic trusted effect facts.

These contracts let an agent state policies about a wet-lab campaign before
any reagent moves:

- spatial scope (``only_plate``);
- instrument allowlist (``only_instruments``);
- reagent and runtime budgets (``reagent_volume_at_most``, ``total_runtime_at_most``);
- absence of effects (``no_protocols``, ``no_oligos``);
- sequence-content guards on synthesis orders (``oligo_length_at_most``,
  ``no_hazard_sequence``).

The sequence-content guards ride on the same z3.Contains primitive used by
``examples/text``'s ``must_not_contain``: they are the static-screening shape
biosecurity policy can attach to before submission, rather than after.
"""

from clauz3.spec import ContractSpec, contract, effect
from clauz3.spec import no_guarantees as core_no_guarantees

Pipette = effect("pipette")
Protocol = effect("submit_protocol")
Oligo = effect("order_oligo")


@contract
def no_guarantees() -> ContractSpec:
    """Make no guarantees about lab effects."""
    return core_no_guarantees()


# ── Pipette / plate scope ────────────────────────────────────────────────────


@contract
def only_plate(plate: str) -> ContractSpec:
    """Guarantee every pipette targets this plate."""
    return Pipette.all(lambda e: e.plate == plate)


@contract
def reagent_volume_at_most(reagent: str, max_ul: int) -> ContractSpec:
    """Guarantee total volume of ``reagent`` dispensed is at most ``max_ul``."""
    by_reagent = Pipette.where(lambda e: e.reagent == reagent)
    return by_reagent.sum(lambda e: e.volume_ul) <= max_ul


@contract
def total_pipette_volume_at_most(max_ul: int) -> ContractSpec:
    """Guarantee total volume across all pipette calls is at most ``max_ul``."""
    return Pipette.sum(lambda e: e.volume_ul) <= max_ul


# ── Instrument / runtime ──────────────────────────────────────────────────────


@contract
def only_instruments(allowed: list[str]) -> ContractSpec:
    """Guarantee every submitted protocol uses an instrument in ``allowed``."""
    return Protocol.all(lambda e: e.instrument in allowed)


@contract
def total_runtime_at_most(hours: int) -> ContractSpec:
    """Guarantee total instrument runtime booked is at most ``hours``."""
    return Protocol.sum(lambda e: e.hours) <= hours


@contract
def no_protocols() -> ContractSpec:
    """Guarantee no instrument protocol is submitted."""
    return Protocol.empty()


# ── Oligo / sequence content ─────────────────────────────────────────────────


@contract
def no_oligos() -> ContractSpec:
    """Guarantee no oligo is ordered."""
    return Oligo.empty()


@contract
def oligo_length_at_most(max_bases: int) -> ContractSpec:
    """Guarantee every ordered oligo is at most ``max_bases`` long."""
    return Oligo.all(lambda e: len(e.seq) <= max_bases)


@contract
def no_hazard_sequence(motif: str) -> ContractSpec:
    """Guarantee no ordered oligo contains ``motif`` as a substring.

    This is the static-screening shape for biosecurity: the prover discharges
    the guarantee before any synthesis order is submitted, so a hazard-motif
    list maintained by a biosafety officer can be enforced *before* a vendor
    sees the sequence rather than after.
    """
    return Oligo.all(lambda e: motif not in e.seq)

A DBTL campaign

The pattern that motivates the example: an LLM-driven design-build-test-learn loop submits one plan per iteration. The user approves the campaign-level guarantee once; each iteration's plan is discharged automatically as long as it entails the guarantee.

examples/lims/cases/dbtl_campaign_pass.py

# ruff: noqa: F821
from tools.lims.trusted import contracts as lims
from tools.lims.trusted.effects import pipette, submit_protocol

import clauz3


@clauz3.guarantee(lims.only_plate("plate_42"))
@clauz3.guarantee(lims.reagent_volume_at_most("ATP", 500))
@clauz3.guarantee(lims.only_instruments(["qPCR-1"]))
@clauz3.guarantee(lims.total_runtime_at_most(10))
def main() -> None:
    for _ in range(8):
        pipette("plate_42", "A1", 50, "ATP")
    submit_protocol("qPCR-1", "plate_42", 4)

A biosecurity guard

no_hazard_sequence(motif) lowers to the same z3.Contains primitive that the text example's must_not_contain uses. Before any oligo synthesis is submitted, the prover discharges the substring guard over every ordered sequence — exactly the static-screening shape a biosafety officer's policy can attach to.

examples/lims/cases/no_hazard_sequence_pass.py

# ruff: noqa: F821
from tools.lims.trusted import contracts as lims
from tools.lims.trusted.effects import order_oligo

import clauz3


@clauz3.guarantee(lims.no_hazard_sequence("GATTACA"))
@clauz3.guarantee(lims.oligo_length_at_most(60))
def main() -> None:
    order_oligo("ACGTACGTACGT", 10)

A sequence containing the hazard motif is rejected:

examples/lims/cases/no_hazard_sequence_fail.py

# ruff: noqa: F821
from tools.lims.trusted import contracts as lims
from tools.lims.trusted.effects import order_oligo

import clauz3


@clauz3.guarantee(lims.no_hazard_sequence("GATTACA"))
def main() -> None:
    order_oligo("AAAGATTACATTT", 5)

All cases

Browse the full set on the all-cases page.

How they run

examples/lims/Justfile

trusted := "tools/lims/trusted"
effects := trusted + "/effects.py"
prove := "uv run clauz3 prove --trusted-root " + trusted

test: lint cases

lint:
    uv run python -m deal lint {{effects}}

cases: only-plate-pass only-plate-fail reagent-budget-pass reagent-budget-fail only-instruments-pass only-instruments-fail total-runtime-pass total-runtime-fail no-hazard-sequence-pass no-hazard-sequence-fail dbtl-campaign-pass pipette-volume-precondition-fail lims-no-guarantees-pass

only-plate-pass:
    {{prove}} cases/only_plate_pass.py

only-plate-fail:
    if {{prove}} cases/only_plate_fail.py; then exit 1; fi

reagent-budget-pass:
    {{prove}} cases/reagent_budget_pass.py

reagent-budget-fail:
    if {{prove}} cases/reagent_budget_fail.py; then exit 1; fi

only-instruments-pass:
    {{prove}} cases/only_instruments_pass.py

only-instruments-fail:
    if {{prove}} cases/only_instruments_fail.py; then exit 1; fi

total-runtime-pass:
    {{prove}} cases/total_runtime_pass.py

total-runtime-fail:
    if {{prove}} cases/total_runtime_fail.py; then exit 1; fi

no-hazard-sequence-pass:
    {{prove}} cases/no_hazard_sequence_pass.py

no-hazard-sequence-fail:
    if {{prove}} cases/no_hazard_sequence_fail.py; then exit 1; fi

dbtl-campaign-pass:
    {{prove}} cases/dbtl_campaign_pass.py

pipette-volume-precondition-fail:
    if {{prove}} cases/pipette_volume_precondition_fail.py; then exit 1; fi

lims-no-guarantees-pass:
    {{prove}} cases/lims_no_guarantees_pass.py