ironharness
we harness LLM agents to hardware.
An agent harness for I/O and firmware. Two modules:
- io-core — a safe I/O layer for agents: transports (serial, Modbus TCP, MQTT,
file sandbox), a Modbus simulator, ESP32 flashing tools (esptool: offline image
inspection, flash/erase on a live board), a JSONL journal of every operation, a
replayer, limits (rate limit, deadlines), effect verification (
expect_read), and an MCP server (22 tools). - ironbench — a benchmark for firmware agents: golden tasks in simulators (Wokwi ESP32/MicroPython, plus Renode), an agent loop over any LLM API, pass@k reports.
Quick start
uvx ironharness-mcp # run the MCP server (no install)
From source:
uv sync # dependencies (+ the project itself, editable)
uv sync --extra flash # + esptool (GPLv2+, kept out of the MIT core)
uv run pytest # tests (no hardware: loop:// and simulators)
uv run ruff check . # linter
uv run ironharness-mcp # MCP server (stdio; or: python -m io_core.mcp_server)
Agent tools (MCP)
echo · serial_open/write/read/read_line/close · modbus_open/read/write/close ·
mqtt_open/publish/subscribe/read/close · esp_image_info/flash/erase · file_write/read/list/delete
Every operation is journaled to JSONL ($IRONHARNESS_HOME/journal.jsonl,
default ~/.ironharness/); file operations are confined by the sandbox
($IRONHARNESS_SANDBOX, default ~/.ironharness/sandbox).
Connecting an external agent
Any MCP-compatible agent (Claude Code, Codex, Cursor, OpenCode…) gets all io-core tools with one config entry — the agent brings its own loop, ironharness provides the hands: transports, sandbox, journal, verification.
{
"mcpServers": {
"ironharness": {
"command": "uvx",
"args": ["ironharness-mcp"]
}
}
}
For unreliable-line testing there is io_core.faults.FaultyTransport — scripted
failures (disconnect, delay, bit corruption, byte loss) over any transport — and
io_core.mqtt_sim.MqttSimBroker, a minimal MQTT broker for offline runs.
ironbench — a benchmark for firmware agents
uv run ironbench list # catalog of golden tasks
uv run ironbench run --all # reference runs (needs WOKWI_CLI_TOKEN)
uv run ironbench solve --task blink --attempts 3 # an LLM agent solves a task
uv run ironbench report # pass@k: report.json + report.html
Tasks run on ESP32/MicroPython in Wokwi (headless wokwi-cli), Renode, the
MicroPython unix port (free local runs), a plant target (a closed-loop
«object + controller» simulation scored on step-response metrics: p-regulator,
pid-antiwindup, system-id), and a live board (--target real: MicroPython
REPL over USB-UART, opt-in via IRONBENCH_REAL_PORT=COM4). The same task can
be run on a simulator and on hardware — that contrast is what real is for.
Every task has a class (io/data/protocol/fsm/control/
resilience) and a level 1–5; ironbench report shows a model's profile across
classes, not a single number. LLM config — environment variables: LLM_BASE_URL
(default: local LM Studio), LLM_MODEL, LLM_API_KEY, LLM_TIMEOUT.
Safety / intended use
- This is a bench tool for developing and testing agents, not production middleware. It is designed to be run locally against simulators and your own dev hardware.
- MQTT transport is plaintext TCP — no TLS, no authentication. Do not point it at production brokers or untrusted networks.
- esp_flash/esp_erase modify real hardware and are gated behind
IRONHARNESS_ALLOW_REAL_FLASH=1(opt-in). erasing flash is irreversible (ESP32 recovers by reflashing, but data is gone). esptool is an optional dependency:pip install 'ironharness[flash]'. - Transports are not restricted to specific hosts/ports by default — the operator
(you) decides what the agent may reach, optionally via the access policy below;
every operation is journaled for audit. Flash/erase failures are journaled with
full details (
esp_flash_failed) even when the MCP envelope truncates them. - Access policy (opt-in): set
IRONHARNESS_ALLOWED_HOSTS(comma-separatedhostorhost:portentries) to restrict Modbus/MQTT connections to the listed hosts — anything else is denied withPolicyViolationbefore a connection is attempted (broker.lan:1883matches that exact port, a barebroker.lanmatches any port). Hosts match as exact strings (no DNS resolution; IPv6 entries are not supported yet). SetIRONHARNESS_ENABLED_KINDS(comma list ofserial,modbus,mqtt,esp,file) to disable whole transport kinds — disabled open/esp/file operations fail fast. Unset variables keep the allow-everything default, and every denial is journaled as apolicy_violationevent.
Status
MVP under active development. Example benchmark results live in reports/
— recorded runs (JSON + HTML, open in a browser).
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ironharness-0.4.3.tar.gz.
File metadata
- Download URL: ironharness-0.4.3.tar.gz
- Upload date:
- Size: 2.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad02d9e2b1cee6262151d7d75461867d78c60ae91c0ed40171be9e44a0098f55
|
|
| MD5 |
54153809897673f41808f601a9abef29
|
|
| BLAKE2b-256 |
75144a95922dda19410dce7d56eaac8b3818f482b07f7c28b984689b3a2a685c
|
Provenance
The following attestation bundles were made for ironharness-0.4.3.tar.gz:
Publisher:
publish.yml on cezman/ironharness
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ironharness-0.4.3.tar.gz -
Subject digest:
ad02d9e2b1cee6262151d7d75461867d78c60ae91c0ed40171be9e44a0098f55 - Sigstore transparency entry: 2763880851
- Sigstore integration time:
-
Permalink:
cezman/ironharness@d3a5d1156a35286b96e60cdafb142a72c15ccbea -
Branch / Tag:
refs/tags/v0.4.3 - Owner: https://github.com/cezman
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d3a5d1156a35286b96e60cdafb142a72c15ccbea -
Trigger Event:
push
-
Statement type:
File details
Details for the file ironharness-0.4.3-py3-none-any.whl.
File metadata
- Download URL: ironharness-0.4.3-py3-none-any.whl
- Upload date:
- Size: 2.1 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
453da950e54cd63d4d34d395ab606fa2fc44023dd9af70f58f6c5108145ab8c9
|
|
| MD5 |
1c10985a33e3e5c43d800cdce7f01b72
|
|
| BLAKE2b-256 |
7f9a5c9259f66c0ca47f81f4e5206aca01bd2fcdd08af8ee159854ac93d9c0a4
|
Provenance
The following attestation bundles were made for ironharness-0.4.3-py3-none-any.whl:
Publisher:
publish.yml on cezman/ironharness
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ironharness-0.4.3-py3-none-any.whl -
Subject digest:
453da950e54cd63d4d34d395ab606fa2fc44023dd9af70f58f6c5108145ab8c9 - Sigstore transparency entry: 2763880922
- Sigstore integration time:
-
Permalink:
cezman/ironharness@d3a5d1156a35286b96e60cdafb142a72c15ccbea -
Branch / Tag:
refs/tags/v0.4.3 - Owner: https://github.com/cezman
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d3a5d1156a35286b96e60cdafb142a72c15ccbea -
Trigger Event:
push
-
Statement type: