OpenMHS Architecture Overview (informative)
Part 2 of 2 · MHS-000 continues from Part 1. The document is split only for page size; the requirements are continuous.
On this page 19 sections
- 9. Adapter and bridge model
- 9.1 The per-repository deliverable
- 9.2 Adapter manifest (example)
- 9.3 Bridge families
- 9.4 Mapping rules
- 9.5 Integration tiers and effort
- 10. Conformance
- 10.1 Levels
- 10.2 Profiles
- 10.3 Executable conformance
- 11. Host profile
- 11.1 MHS-Host behaviours
- 11.2 The OpenAI4S reference host
- 11.3 The "one call" property
- 12. Document map
- 13. Adoption path for the corpus
- 14. Decision log (alternatives considered)
- 15. Security and privacy (summary)
- 16. What MHS deliberately leaves to others
9. Adapter and bridge model
9.1 The per-repository deliverable
The default deliverable for an upstream repository is a driver package (driver-profile.md): one Driver class (or a config-only subclass of a family driver) that declares what the device reads, writes and does, with sourced limits and facts, plus a fake backend; the SDK runtime enforces everything else and a gate (python3 -m mhs.driver check <dir> --backend fake) says when the package is finished. The earlier design, a declarative mhs-adapter.json with a bindings.json read by a bridge, remains valid and optional (06). Either way a bridge family runs the mapping: a generic runtime per kind of native interface.
An adapter may declare several devices (e.g., a workcell simulator exposing robot + plate reader + liquid handler).
9.2 Adapter manifest (example)
The manifest below is the shipped example MHS/schema/examples/adapter.gymnasium-cartpole.json. It is valid against adapter.schema.json, whose objects are closed: bindings live in a separate mhs/bindings.json (06 §4), not inside the device capabilities.
{
"$schema": "https://mhs.openai4s.org/schema/0.9/adapter.schema.json",
"mhs_adapter": "1.0",
"adapter_id": "com.example.gymnasium-cartpole",
"title": "Gymnasium CartPole as an MHS simulated device",
"family": "gymnasium",
"source": {
"repo": "https://github.com/Farama-Foundation/Gymnasium",
"commit": "5f2f9d3",
"license": "MIT",
"redistributable": true,
"path": "mhs/"
},
"runtime": {
"kind": "python",
"python": ">=3.10",
"install": ["pip install gymnasium==1.0.0"],
"entry": "mhs.bridge.gym:main",
"requirements": ["gymnasium"],
"gpu": false
},
"lifecycle": {
"start": {"kind": "process", "command": ["python", "-m", "mhs.bridge.gym", "--adapter", "mhs/mhs-adapter.json"]},
"ready": {"method": "mhs.hello", "deadline_ms": 30000},
"stop": {"kind": "terminate", "grace_s": 5}
},
"devices": [
{
"$schema": "https://mhs.openai4s.org/schema/0.9/device.schema.json",
"mhs_version": "1.0",
"device_id": "mhs://localhost:7401/gym/CartPole-v1",
"instance_id": "01J8ZQ3V6Y7M2N4P6R8S0T2V4X",
"display_name": "CartPole-v1 (Gymnasium simulator)",
"mode": "simulated",
"profile": {
"title": "Cart-pole balancing",
"summary": "Classic control benchmark: push a cart left or right to keep a pole upright. Demonstrates the MHS world.* capability family and seeded determinism.",
"tags": ["classic control", "cartpole", "reinforcement learning", "benchmark", "seeded simulation"]
},
"resources": [
{
"resource_id": "world",
"kind": "world.sim",
"title": "CartPole world",
"revision": 0,
"hash_exclusions": ["/mono_ms"],
"state_schema": {
"type": "object",
"required": ["cart_position", "pole_angle", "terminated", "truncated"],
"properties": {
"cart_position": {"type": "number", "x-mhs-unit": "m"},
"cart_velocity": {"type": "number", "x-mhs-unit": "m/s"},
"pole_angle": {"type": "number", "x-mhs-unit": "rad"},
"pole_angular_velocity": {"type": "number", "x-mhs-unit": "rad/s"},
"terminated": {"type": "boolean"},
"truncated": {"type": "boolean"}
}
},
"state": {
"cart_position": 0,
"cart_velocity": 0,
"pole_angle": 0,
"pole_angular_velocity": 0,
"terminated": false,
"truncated": false
}
}
],
"capabilities": [
{
"capability_id": "cap.world.step.v1",
"resource": "world",
"native": "gym.make('CartPole-v1').step(action)",
"input_schema": {
"type": "object",
"required": ["action"],
"properties": {"action": {"type": "integer", "enum": [0, 1]}}
},
"output_schema": {"type": "object", "required": ["observation", "reward", "terminated", "truncated"]},
"constraints": {"concurrency": "exclusive", "max_duration_ms": 1000},
"determinism": "seeded",
"exclusive": true
},
{
"capability_id": "cap.world.reset.v1",
"resource": "world",
"native": "gym.make('CartPole-v1').reset(seed=seed)",
"input_schema": {"type": "object", "properties": {"seed": {"type": "integer", "minimum": 0}}},
"output_schema": {"type": "object", "required": ["observation"]},
"determinism": "seeded"
},
{
"capability_id": "cap.world.state.v1",
"resource": "world",
"native": "env.unwrapped.state",
"input_schema": {"type": "object", "properties": {}},
"output_schema": {"type": "object"},
"determinism": "seeded"
}
],
"safety": {
"risk_class": "R0",
"envelope": {},
"interlocks": [],
"estop": {"kind": "software", "latching": false, "latency_ms": 10, "reset": "mhs.safety.reset"},
"safe_state": {"description": "Simulation paused; no physical effect", "achieved_by": []}
},
"fidelity": {
"engine": "gymnasium-classic-control",
"fidelity_level": "F0",
"determinism": {
"kind": "seeded",
"seed_control": "reset_required",
"notes": "Seed must be supplied on reset; no internal RNG isolation."
},
"timestep_ms": 20,
"realtime_factor": 0,
"reset": "full",
"snapshot_restore": false,
"gaps": ["Rigid-body approximation only", "No actuator saturation model", "Observation noise not modeled"]
}
}
],
"conformance_target": {"profile": "MHS-Sim", "level": "CL2"},
"evidence": {
"state": "declared",
"read_by": "static_analysis",
"entry_points": [
"gymnasium/envs/classic_control/cartpole.py:CartPoleEnv.step",
"gymnasium/envs/classic_control/cartpole.py:CartPoleEnv.reset"
]
},
"stability": "stable",
"tier": "A",
"redaction": [],
"registry": {
"package_id": "gh.farama-foundation/gymnasium",
"homepage": "https://gymnasium.farama.org",
"maintainers": ["Farama Foundation"]
}
}9.3 Bridge families
The 0.9 SDK implements these bridge families as runtimes with a Driver class (PythonObjectDriver, SerialDriver, ScpiDriver, GymnasiumDriver, MujocoDriver) and a read-only replay bridge: python-object (import a class/function), serial, scpi (stdlib socket, optional pyvisa), gymnasium, mujoco and replay. The adapter schema also names families that are planned after the pilot: ros2 (rclpy / rosbridge), http-api, sila2, opentrons, micromanager, flowchem, madsci, bluesky-ophyd, qcodes, pylabrobot, lerobot, mavlink, the tag bridges (Modbus, OPC UA, EPICS, Tango, Alpaca) and mcp-proxy. A bridge is generic per family; the driver or adapter supplies the mapping.
9.4 Mapping rules
Declarative transforms are limited to: field path mapping, unit conversion, enumeration mapping, constant injection, clamps (which reject and never truncate), and error-code mapping. Anything else becomes a shim (Python/JS), recorded as binding.kind: "shim" with a file reference. Adapters declare native symbols precisely (module/class/function) and do not rely on undocumented internals; when they do, stability: fragile is declared.
9.5 Integration tiers and effort
| Tier | Definition | Typical effort |
|---|---|---|
| A | Runnable backend with a clear entry point; adapter manifest + bridge flags get you to CL2. | 0.5–2 dev-days |
| B | Wrap-able library/SDK; needs a thin shim or bridge family support. | 2–5 dev-days |
| C | Engine or asset only; needs a scenario/model to become a device. | 5–15 dev-days |
| D | Peripheral/tooling; not a device (visualizer, docs, dataset, CI helper). Not targeted. | — |
| X | Not integrable (empty upstream or inaccessible). | — |
Adapters move through states: declared → packaged → runnable → conformant → certified, each backed by evidence.
10. Conformance
10.1 Levels
| Level | Name | Requires |
|---|---|---|
| CL0 | Declared | Valid mhs-adapter.json/device manifest against the OpenMHS 0.9 schemas; no runtime required. |
| CL1 | Observable | T1/T2 transport; hello, devices.list/get, schema.get, resource.read, subscribe/unsubscribe; stable revisions. |
| CL2 | Actuating | session.open/close, command with idempotency, receipts, operations.get/cancel, revision conflicts, deadlines. |
| CL3 | Governed | safety.status/estop/reset, envelope enforcement, risk classes, permits for gated capabilities, session TTL/heartbeat, safe-state on cancel, MHS.SAFETY.NOT_CERTIFIED disclosure. |
| CL4 | Reproducible | Evidence export/verify (hash chain), and for simulated/replay devices a deterministic replay test (same seed ⇒ identical state_hash sequence). |
| CL5 | Orchestrated | Composition execution with child receipts (reserved in 0.9), multi-device sessions, backpressure/gap semantics, registry publication, and a published conformance report. CL5 is assessed for the MHS-Bridge, MHS-Host and MHS-Registry profiles; MHS-Device and MHS-Sim top out at CL4 (see MHS-001). |
10.2 Profiles
- MHS-Device (hardware): CL0–CL4 as applicable (no replay requirement).
- MHS-Sim: CL0–CL4; the fidelity contract and the CL4 replay test are mandatory.
- MHS-Bridge: adapter manifest + at least one device at CL2.
- MHS-Host: consumes MHS; the behaviours are in 09 (discovery, least privilege, approvals, evidence ledger, mode disclosure).
- MHS-Registry: record schema validity, signing, search latency bounds.
10.3 Executable conformance
- The suite (
python3 -m mhs.conformance, versioned with the spec) runs test cases against an endpoint and emits aconformance-report.json, here a minimal report that is valid againstconformance-report.schema.json(01 §7.1 shows a fuller one):
{"report_version": "1.0.0", "suite_version": "1.0.0", "profile": "MHS-Sim",
"subject": {"kind": "endpoint", "id": "mhs://localhost:7400", "mhs_version": "1.0"},
"level_claimed": "CL3", "level_achieved": "CL3",
"tests": [{"id": "SAFE-014", "status": "pass", "level": "CL3",
"mhs_code": "MHS.SAFETY.ENVELOPE_VIOLATION"}],
"environment": {"os": "macOS 15.6 arm64", "date": "2026-10-20T07:40:00Z", "suite": "openmhs 0.9.0.dev0"},
"report_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000",
"issuer": {"kind": "self"},
"disclaimers": [{"id": "not_certified",
"text": "MHS conformance is a behavioural test result, not a safety certification."}]}- A registry claim is `{package_or_endpoint, profile, level, suite_version, report_digest, issued_at, issuer_kind}
.issuer_kind: self|verified`. - Test IDs are stable and requirements-traced; the suite includes negative tests (interlock open, revision conflict, duplicate command id, deadline exceeded, cancellation, mode mismatch).
11. Host profile
11.1 MHS-Host behaviours
A host (agent harness) claiming MHS-Host conformance (09):
- discovers endpoints from all configured sources and authenticates;
- opens least-privilege sessions and refreshes/closes them correctly;
- obtains approvals before calling
requires_permitcapabilities; - presents
mode,fidelity.gaps,risk_classand trust level to the model; - enforces deadlines and cancellation, and never retries an
unknownoutcome blindly; - records all receipts into an append-only evidence ledger and exports/verifies it;
- never claims or implies that MHS replaces hardware safety;
- exposes capabilities to the model through at least one of MCP tools, native tools, or a code API, and keeps tool descriptions in sync with schemas.
11.2 The OpenAI4S reference host
OpenAI4S (PKU-YuanGroup/OpenAI4S) is the reference host. The integration is MCP-first: one mhs.mcp projection per endpoint, registered as an ordinary connector that is disabled until an operator enables it, with permits and resets on a separate operator route. How its components map to the host behaviours is described in 09 annex A (informative).
11.3 The "one call" property
For any MHS endpoint — a Rust conveyor workcell, a Python liquid-handler simulator, a MuJoCo arm, a driver package on its fake backend — the same host code path runs. This example drives two different endpoints through one helper; it runs against the SDK:
# runnable
from mhs.adapter import load
from mhs.reference import build_endpoint, build_server
from mhs.run import lab
thermostat = load("MHS/sdk/examples/thermostat", backend="fake")
conveyor = build_server(build_endpoint("conveyor", endpoint_id="mhs://localhost:7400", test_profile=False),
log_ready=False)
with lab(thermostat, conveyor, ledger=False) as bench: # one transport API
for name, capability, payload in (("tutorial-thermostat", "cap.thermal.setpoint.v1", {"target_temperature": 30}),
("conveyor-workcell", "cap.world.step.v1", {"ticks": 3})):
receipt = bench[name].invoke(capability, payload) # one command call for every device
assert receipt["status"] == "completed"
state = bench[name].read(bench[name].manifest()["resources"][0]["resource_id"]) # one read
assert state["revision"] == 1
thermostat.close()
conveyor.close()No per-repo branches in the host. The differences live in the adapter plane.
12. Document map
| Doc | Subject |
|---|---|
README.md | Precedence, reading order, requirement identifiers |
core.md | The normative core: object model, primitives, reference file, limits, evaluation order, safety duties, receipts, modes, discovery, surfaces, principals, strict mode, persistence |
driver-profile.md | The per-repository driver package, the Driver API, the gate, review, fakes, the sandbox |
profiles.md | Index of the optional profiles |
00-architecture.md | This file: the informative overview |
01-conformance.md | Levels, profiles, test suite, report format |
02-wire-protocol.md | Full method-by-method reference, errors, examples |
03-capability-registry.md | Capability class definitions, versioning, extension rules |
04-safety.md | Envelopes, interlocks, permits, e-stop, audit, threat model |
05-fidelity.md | Mode and fidelity contract, validation protocol, replay |
06-adapters.md | Adapter manifest, bridge families, mapping rules, recipes |
07-registry.md | Package/device/claim records, federation, trust, search |
08-transports.md | T1–T6 bindings, MCP projection details, auth |
09-host-profile.md | Host requirements; annex A describes the OpenAI4S reference host |
10-governance.md | RFC process, versioning, deprecation, licensing |
appendix-a-capabilities.md | Core capability catalogue (generated) |
appendix-b-errors.md | Error code registry (generated) |
appendix-c-examples.md | Runnable end-to-end examples (conveyor, lab, arm, hybrid, MCP) |
appendix-d-anthropic-mhs.md | Relationship to the Anthropic research preview |
13. Adoption path for the corpus
The workspace catalogues 2,975 repos (1,045 lab, 1,922 robotics, 8 unlisted) and clones 2,967. They are not adopted by hand: each becomes, at most, one driver package, produced from a generated task card by a model and checked by a gate, one repository at a time (driver-profile.md, MHS/handoff/). The order of work:
- Wave 0 — reference endpoints and four exemplar drivers. The reference conveyor, lab, arm and replay endpoints prove the protocol end to end and produce CL2–CL4 conformance reports; four exemplar driver packages (an async bath, a SCPI lock-in amplifier, a robot arm SDK, a Gymnasium environment) show the pattern.
- Wave 1 — a pilot of 20 repositories (direct Python instruments, serial/SCPI devices, Gymnasium/MuJoCo environments, robot SDK drivers on fakes). The exit criterion is that at least 15 pass the gate without any change to the SDK; after it the Driver API is frozen.
- Waves 2 and 3 — the dispatchable cards, strict-ready ones first.
- Wave 4 — reconnaissance only for the untriaged cards: a model proposes a decision and entry symbols, a maintainer decides.
- Waves 5 and 6 — after the post-pilot bridges (ROS, HTTP, PyLabRobot, SiLA2, QCoDeS, ophyd, Micro-Manager, LeRobot, MAVLink, tag bridges, MCP client) and an artifact store exist, and for repositories that need a GPU or a proprietary engine.
Every repo record in the registry states its tier, ring, evidence and next action (07); a passing package is contract_checked, a statement about a fake and never about hardware. No repo is silently marked "supported".
14. Decision log (alternatives considered)
| # | Decision | Alternative rejected | Why |
|---|---|---|---|
| D1 | JSON-RPC 2.0 core | REST resources; gRPC-only; MCP-only | JSON-RPC is trivially embeddable in stdio/HTTP/WS, batch and notification semantics are already specified, and it has a native mapping to MCP. REST forces verb soup; gRPC blocks stdlib hosts; MCP-only excludes direct code APIs and is tool-shaped, not device-shaped. |
| D2 | Sessions as leases | Stateless per-call | Simulators are stateful, hardware needs attribution and exclusive leases, and cancellation needs a scope to act on. |
| D3 | Receipts + revisions + state hash | Simple {"ok": true} responses | Without receipts, an agent cannot distinguish "not done" from "unknown", and evidence is impossible. This is the single largest gap in the preview. |
| D4 | Endpoint-enforced safety | Host-enforced policy | Any model can be prompt-injected or buggy; a host is the wrong trust boundary for limits. |
| D5 | Sim and hardware in one model with explicit mode | Separate sim standard | Thousands of adopters only have simulators; two standards double the work. But conflating them silently is unsafe — hence mode + fidelity contract and no runtime switching. |
| D6 | A Driver class per repository on generic bridge families, with a mechanical gate (reversed in 0.9; the declarative adapter stays optional) | A declarative adapter + bindings as the only deliverable | The original decision wrote a manifest and a bindings file per repository. Review of the first design found that too hard for a model to produce and check mechanically; a short decorated Driver class with a fake backend, sourced limits and one gate command is. The runtime, not the driver, enforces safety, so the driver stays a translation. |
| D7 | Capability classes versioned separately | Monolithic protocol version | Hardware vocabulary evolves faster than the wire format; separate versioning avoids breaking the wire to add a pipetting nuance. |
| D8 | Executable conformance levels | Certification body | Adoption requires self-service; a registry claim plus reproducible suite is enough to start, with room for third-party verification. |
| D9 | Natural-language tags attached to typed capabilities | Docs/wiki only | The preview's tags idea is right, but unstructured prose cannot be validated or searched; attaching tags to schemas keeps both. |
| D10 | Registry as static index | Central live broker | Lab networks are air-gapped; static JSON that can be signed and mirrored is deployable; a broker is a single point of failure and a privacy hazard. |
15. Security and privacy (summary)
The rules are in core.md, 04 and 08. In brief:
- Transport authentication is required outside loopback; tokens are opaque and never logged; stdio endpoints inherit the process identity; HTTP checks
HostandOriginand the content type. - Capability grants are least-privilege and per binding; a session does not gain capabilities after open without re-opening.
input_digestbinds permits to exact arguments; the approval UI shows the human-readable rendering of the same input.- Evidence contains no secrets; adapters declare
redactionrules for credentials and PII fields. - Endpoints rate-limit and size-limit requests (
MHS.LIMIT.*). - Registry records are public by design; device state and telemetry are never published to the registry.
- Supply chain: adapter manifests and driver locks pin source commits and licences. Upstream code runs locally whatever its licence and is never vendored or redistributed; a licence class gates publication of a package, not its execution (driver-profile.md DRV-042).
16. What MHS deliberately leaves to others
- Model training, prompting, planning and skill learning.
- Real-time control loops (MHS is a supervisory contract; bridges may use EtherCAT, CAN, ROS control or vendor RT APIs underneath).
- Physical safety certification, risk assessment and regulatory compliance.
- Device discovery on the factory floor (mDNS/DNS-SD/OPC UA GDS may be used).
- Data storage and analysis of scientific results.
These are integration points, and the MHS documents state them explicitly so that no one mistakes MHS conformance for a safety or quality certificate.