Specification · section 4
The device package
A device is described the way an Agent Skill is: a folder whose contents load in three levels, each only when the agent needs it. This is the same progressive disclosure that lets a harness hold hundreds of skills at about a hundred tokens each, applied to hardware.
thermocycler-01/
├── DEVICE.md Level 1: YAML frontmatter, the card (always cheap)
│ Level 2: Markdown body, operating instructions (when the device is chosen)
├── descriptor.yaml Level 3: full machine-readable spec: limits, params, examples
├── driver.py Level 3: code, a Driver subclass or an adapter's DEVICE object
├── references/ Level 3: manual excerpts, SOPs, calibration tables
└── scripts/ Level 3: ready-made orchestration scripts for mhp_run
| Level | Content | Served by | Loaded when |
|---|---|---|---|
| 1 | frontmatter: id, class, make, model, location, tags, description | device/describe {detail: "card"}, directory/search | search results; about 40 tokens per device |
| 2 | DEVICE.md body: how to operate it, what to check, what never to do | device/describe {detail: "summary"} with a slim capability table | the agent picks this device; under 1k tokens |
| 3 | descriptor items, references, scripts, driver | device/describe {select}, resources/list, resources/read {path} | the agent needs that item; none until asked |
4.1 DEVICE.md
The frontmatter is the card. description is what a directory ranks and what an agent matches its task against, so it must say both what the device is and when to pick it, in under 1,024 characters. The body is written for the agent that has just chosen the device: an operating procedure, what to watch, and pointers to the resources it may need.
---
mhp: "2026-09-09"
id: thermocycler-01
class: thermocycler
make: SimBio
model: TC-96
location: bay 3, bench 3
tags: [pcr, heating, 96-well]
description: 96-well PCR thermocycler with heated lid, 4 to 105 degC block. Use for PCR,
denaturation, ligation holds and any timed temperature program on a 96-well SBS plate.
Not for tubes, not for cooling below 4 degC.
driver: driver.py:SimThermocycler
---
# Thermocycler-01
Sits on bench 3 in bay 3, left of the liquid handler; the plate arm reaches it at `thermocycler`.
The block ramps about 4 °C/s. Turn `lid_heater` on ~40 s before loading to avoid condensation.
## Operating procedure
1. Confirm `lid_closed` is true; `run_protocol` refuses otherwise.
2. Set `lid_heater` true; wait for `lid_temperature` above 100 °C.
3. Invoke `run_protocol`; poll the job. 30 cycles of three steps take ~75 minutes.
4. Write `target_temperature` 4 to hold. Open the lid only below 60 °C (needs a human).
## Resources
- `references/protocols.md`: standard programs per polymerase.
- `scripts/pcr.py`: complete run through `mhp_run`, including the 4 °C hold.
4.2 descriptor.yaml
The machine-readable part. Identity fields live in the frontmatter and are merged in; everything below is enforced or served by the driver.
device:
notes: >
96-well block on bench 3, left of the liquid handler. Lid must be closed
before any run. Block ramps ~4 °C/s; lid heater takes ~40 s to reach 105 °C.
physical: # anything the agent cannot infer from code
mass_kg: 12.5
footprint_mm: [331, 461, 251]
power_w: 851
notes: Bench-mounted, do not relocate while running. Hot lid surface up to 110 °C.
signals: # READ
- name: block_temperature
type: number
unit: degC
notes: Measured at block centre; edge wells lag by ~0.5 °C.
- name: lid_closed
type: boolean
notes: True when the lid latch is engaged.
settings: # WRITE
- name: target_temperature
type: number
unit: degC
limits: {min: 5, max: 106} # enforced by the driver
approval: auto # auto | confirm | forbid
notes: Below 4 °C condensation forms; above 105 °C the seal fails.
actions: # INVOKE -> job
- name: run_protocol
duration: long # short | long
approval: auto
interlocks: [lid_closed] # signals that must be truthy
params:
steps: array of {temp: degC, hold_s: number}
cycles: integer
notes: Runs a cycling program; steps are repeated `cycles` times.
examples: # what normal usage looks like, §3.5
- {steps: [{temp: 95, hold_s: 30}, {temp: 58, hold_s: 30}, {temp: 72, hold_s: 45}], cycles: 30}
- name: open_lid
duration: short
approval: confirm # a human must confirm each invocation
notes: Lid may be hot; a human should be present.
safety:
estop: true # driver implements safety/estop
interlocks: [lid_closed]
watchdog_s: 11 # driver fails safe if no ping within this window
notes: E-stop cuts block and lid heaters; the block cools passively.
4.3 Resources
resources/list returns the relative paths of every file in the package except DEVICE.md. resources/read {path} returns a text file's contents, capped, and MUST refuse paths outside the package. Scripts under scripts/ are written for mhp_run and see lab: an agent reads one, adapts the constants, and runs it, so the device owner's tested procedure is what executes.
4.4 Field rules
| Field | Rule |
|---|---|
device.id | MUST be unique within a host's set of devices. class is an open string; §13 lists the initial vocabulary. |
device.description | Frontmatter. SHOULD state what the device is and when to pick it, at most 1,024 characters. It is the ranking text for directories and the card text for agents. |
notes | Any object may carry one. Notes are natural language for the agent, the mechanism by which tacit knowledge enters the protocol. Drivers MUST pass them through unchanged. |
type, unit | number | integer | boolean | string | object | array. Units SHOULD be UCUM codes or common spellings (degC, mm, percent, N, rpm). |
limits (setting) | {min, max} for numbers, {enum: [...]} for strings. The driver MUST refuse writes outside limits with LimitViolation. |
limits (action) | A map from parameter name to [min, max]. Drivers SHOULD enforce these inside the action. |
approval | auto: the agent may act. confirm: each call needs approved: true, which the host may set only after human confirmation. forbid: never agent-operable; listed so the agent knows the capability exists and why it is off-limits. |
interlocks | Boolean signals that MUST read truthy at the moment of the write or invoke; otherwise InterlockOpen. |
duration | short actions finish within seconds and hosts MAY block on them. long actions run as jobs; the host SHOULD poll or subscribe. |
location, tags, examples | Optional but SHOULD be set. Location and tags are what a directory filters on; examples on an action is an array of parameter objects. |
| unknown keys | Permitted at top level (for example locations on a robot arm) and MUST be preserved by adapters and bridges. |