Skip to content

Latest commit

 

History

History
322 lines (259 loc) · 13.3 KB

File metadata and controls

322 lines (259 loc) · 13.3 KB

Labwire

CI License: Apache-2.0

An open protocol for AI-controlled laboratory instruments. Think "MCP for lab equipment": one universal way for AI agents to discover an instrument's capabilities, command it, stream its measurements, and walk away with cryptographically signed proof of what was done.

Working title, protocol v0.3 draft. The wire protocol will change before 1.0. Feedback and prior-art corrections are very welcome; see CONTRIBUTING.md.

Why

Self-driving labs need instruments that agents can operate safely and auditably. Today every vendor speaks a different dialect and every integration is bespoke. Labwire's bet is that the missing piece is small and buildable now:

  • AI-agent-native. Capability discovery modeled on MCP: an instrument describes its commands as JSON Schema, so any agent framework can drive it with zero glue code: the bundled MCP adapter proves it.
  • Signed results. Every run can produce an ed25519-signed manifest over the exact telemetry recorded: portable, tamper-evident evidence of what instrument did what, verified by one CLI command.
  • Safety and physical typing in the protocol. Mandatory UCUM units on every quantity, S0-S3 safety classes where irreversible actions take an operator confirmation and hazardous ones take an operator grant an agent cannot mint, bound to the exact parameters, interlocks, cancellation, and typed errors with retryability. All specified, not vendor add-ons.
  • Things, not only quantities. v0.3 adds resources (typed, readable instrument state, like a liquid handler's deck) and typed references (parameters that name a well or a site, validated against current state, with errors that hand the agent the read that recovers). Designed so that discovery alone leads an agent to the deck, with no prompt coaching; CI enforces the preconditions, and the demo asserts the behaviour.
  • Runnable by a stranger in 5 minutes. Zero hardware: the reference implementation ships three realistic simulated instruments.

Five-minute quickstart

From PyPI (Python 3.12+), no checkout needed:

pip install labwire

That installs the SDK, three simulated instruments, their drivers, and the labwire CLI. Declare an instrument and drive it end to end, straight from a Python file:

import asyncio

from labwire.core import (
    PROTOCOL_VERSION,
    CommandContext,
    IdentityInfo,
    Instrument,
    InstrumentServer,
    LabwireClient,
    MemoryTransport,
    command,
)
from pydantic import BaseModel, ConfigDict


class MassReading(BaseModel):
    model_config = ConfigDict(extra="forbid")  # closed schema: the unit walker demands it

    mass_g: float


class Balance(Instrument):
    """A one-command instrument. Units are mandatory: omit "g" and this refuses to declare."""

    identity = IdentityInfo(
        manufacturer="You", model="Balance-1", serial_number="B-1", firmware_version="1.0"
    )

    @command(returns_units={"mass_g": "g"})
    async def measure(self, ctx: CommandContext) -> MassReading:
        """Report the settled mass."""
        return MassReading(mass_g=12.3456)


async def main() -> None:
    server = InstrumentServer(Balance())
    client_end, server_end = MemoryTransport.pair()
    server.attach(server_end)
    async with LabwireClient.attach(client_end) as client:
        descriptor = await client.describe()
        print(descriptor.identity.model, "speaks Labwire protocol", PROTOCOL_VERSION)
        handle = await client.submit("measure", {})
        result = await handle.result(timeout=5.0)
        print("mass:", result["mass_g"], "g")


asyncio.run(main())

Telemetry streaming, safety confirmations, and ed25519-signed run bundles are a few lines more; examples/quickstart.py shows them.

From source, for the full demos and examples:

git clone https://github.com/benchwire/labwire.git && cd labwire
make setup                       # uv installs Python 3.12 + everything
uv run examples/quickstart.py    # 60 s: drive a simulated balance end to end
uv run examples/streaming.py     # telemetry, cancellation, interlock recovery
make demo                        # closed-loop optimization + signed evidence

make demo runs a full autonomous experiment campaign: a scripted optimizer tunes heater voltage and reagent flow rate across three simulated instruments, converges on the hidden yield optimum, and ends by verifying the winning run's signed bundle:

safety:      pump dispense is class S2 (irreversible); running under the operator standing grant
  run 13  V= 15.0 V -> T= 68.0 degC   q=  127 uL/min   yield= 87.2%   best= 87.2%
converged: best yield 87.2% at 15.0 V (68.0 degC), 127 uL/min in 14 experiments
signed evidence: demo_runs/d3b15e9f-...
  labwire verify: OK - authentic

make demo-claude runs the same loop with a real Claude agent planning the experiments through the instruments' tool schemas (needs ANTHROPIC_API_KEY; degrades gracefully to the scripted optimizer without it).

Architecture

flowchart LR
    subgraph agents [Agents]
        claude[Claude / any MCP client]
        script[Optimizer / LabwireClient]
    end
    adapter["labwire-mcp<br>(MCP adapter)"]
    subgraph servers [Instrument Servers - labwire-core]
        psu[PowerSupply driver]
        pump[SyringePump driver]
        bal[Balance driver]
    end
    subgraph devices [Native wire protocols - labwire-sim]
        scpi["SimPSU-3005<br>SCPI over TCP"]
        serial["SimPump-200<br>serial-style lines"]
        stream["SimBalance-120<br>streaming readings"]
    end
    verify["labwire verify<br>(ed25519 + RFC 8785)"]

    claude -->|MCP tools| adapter
    adapter -->|JSON-RPC / WebSocket| servers
    script -->|discover / command / stream| servers
    psu --> scpi
    pump --> serial
    bal --> stream
    servers -->|signed run bundles| verify
Loading

The protocol is JSON-RPC 2.0 over WebSocket (stdio specified), with an MCP-inspired initialize/capability handshake, a push-first command lifecycle, sequenced telemetry, protocol-level safety interlocks, and normative signed run manifests. The full specification lives at spec/SPEC.md, and every JSON example in it is machine-validated against the implementation in CI.

What's in the box

Package What it is
labwire-core Server + client SDKs, transports, session layer, signing, JCS
labwire-sim Three realistic simulated instruments speaking native wire protocols
labwire-drivers Drivers wrapping those native protocols as Labwire instruments
labwire-mcp MCP adapter: every instrument command becomes an MCP tool
labwire-cli labwire verify <bundle>: authenticate signed run evidence
labwire-ophyd Bridge: serve any ophyd (Bluesky) device as a Labwire instrument
labwire-pylabrobot Bridge: serve a PyLabRobot liquid handler as a Labwire instrument
spec/ The protocol specification (v0.2 draft)
examples/ Quickstart, streaming/recovery, and the closed-loop demo

Wrapping your own device is a class and a decorator:

class MyPump(Instrument):
    identity = IdentityInfo(manufacturer="You", model="Pump-1",
                            serial_number="001", firmware_version="1.0")
    flow = channel("flow_rate", unit="uL/min")   # UCUM codes are mandatory

    @command(
        units={"volume_ul": "uL", "rate_ul_min": "uL/min"},
        returns_units={"dispensed_ul": "uL"},
        safety_class="S2",                       # irreversible: needs confirmation
    )
    async def dispense(self, ctx: CommandContext,
                       volume_ul: float, rate_ul_min: float) -> dict[str, float]:
        """Dispense a volume at a controlled flow rate."""
        ...

Drive it from Claude (MCP)

Serve a simulated instrument in one terminal:

uv run examples/serve_pump.py

Then expose it to any MCP client from another:

uv run labwire-mcp ws://127.0.0.1:9520

Every declared command appears as an MCP tool with its schema, units, and identity, so Claude discovers and drives the hardware natively. See examples/mcp-config.json for a Claude-style MCP server entry.

The adapter speaks the MCP 2026-07-28 revision and the classic handshake era from the same process. On 2026-era hosts, an unconfirmed S2 command becomes an approval the host surfaces to a human, and long-running commands become pollable tasks for clients that declare the tasks extension. The labwire-mcp README has the era matrix and the honest caveats.

The protocol enforces what it can at the instrument boundary. Some conduct only the agent can uphold: respecting an S3 refusal, not treating cancel as an undo, reporting only what the signed record supports. Those expectations are written as behavior specs under .agents/behaviors in the Agent Behavior format, so trace reviewers and eval harnesses have a concrete standard to grade an agent against. A test pins each spec to exact sentences the MCP adapter serves at runtime (tool descriptions for the safety and cancellation conduct, task status text for evidence reporting), so the graded standard and the served instructions cannot drift apart on the pinned wording.

Honesty and scope

The three instruments are original simulated device models, with realistic latency, noise, drift, failure modes, and safety interlocks. They are not emulations of any real vendor's hardware, and no compatibility with real instruments is claimed. In the closed-loop demo, the chemistry between devices is computed by the demo harness. Safety confirmation for S2/S3 commands proves deployment policy, not operator identity; cryptographic operator binding is on the roadmap, not in v0.2. Non-goals for now: fleet control, web UI, auth beyond a stub API key, real hardware drivers, cloud hosting.

Bring your own instruments (ophyd bridge)

Labwire does not aim to reimplement thousands of drivers. The ophyd bridge exposes any classic ophyd device, the hardware layer under Bluesky that is widely used at synchrotron facilities, as a Labwire instrument:

uv run labwire-ophyd annotate ophyd.sim:motor -o labwire-ophyd.yaml
make demo-ophyd     # a peak-finding scan over bridged ophyd.sim devices

ophyd knows a device's structure; it carries no units and no notion of risk. A small YAML annotation file supplies those, the bridge refuses to serve a device whose quantities have no UCUM unit, and actuation is classified S2 so an agent must present an operator confirmation to move anything. Verified against simulated devices and a soft EPICS IOC over Channel Access, never against physical hardware; the package's LIMITATIONS section is explicit about what that does and does not mean.

The PyLabRobot bridge does the same for liquid handling, and was built to test something specific: ophyd devices are signal-shaped, which is the shape this protocol was designed around, so bridging them proved less than it looked like. A liquid handler's commands act on things, and its state is a tree.

make demo-pylabrobot     # a serial dilution, with signed evidence

It works, and the places it strained are written down rather than smoothed over. SPEC-FINDINGS.md is an honest list of eight of them, with concrete recommendations for v0.3. The short version: v0.2 models actions on quantities well and does not yet model things. References cannot be typed the way units type numbers, and instrument state that is a tree has nowhere to live.

Prior art & positioning

Agent-to-instrument protocols became an active space in 2025-2026: LAP (arXiv:2606.03755) is a thoughtful design specification for the same agent-to-instrument edge Labwire targets, and SCP (arXiv:2512.24189) extends MCP with a hub-mediated registry deployed at platform scale. Labwire and LAP are independent convergent designs, and protocol v0.2 adopts two of LAP's ideas, mandatory UCUM unit codes and the S0-S3 safety-class taxonomy, with credit. The practical difference today is simple: LAP is a specification without a published implementation, while Labwire is running code, spec, SDKs, simulators, signed runs, an MCP adapter, and a five-minute quickstart. PRIOR_ART.md has the full honest comparison, including MCP, SiLA 2, OPC-UA LADS, Bluesky/Ophyd, and PyLabRobot, and what each does better than Labwire.

Development

make check   # ruff + pyright strict + full test suite (exactly what CI runs)

See CONTRIBUTING.md for the process and quality gates, and ROADMAP.md for what is planned but not built.

License

Apache-2.0: the patent grant matters for a protocol.