Workers
Device protocol adapters that wrap vendor hardware APIs and expose them through the MDK Protocol
Workers
Workers are device protocol adapters for MDK. Each Worker wraps a specific API, such as a hardware vendor's API, and exposes it through the MDK Protocol, allowing Kernel to discover, query, and command it without knowing anything about the underlying hardware.
Worker categories
Workers are organized by categories, for example:
| Directory | Description |
|---|---|
miners/ | Bitcoin ASIC miners — Whatsminer, Antminer, Avalon |
containers/ | Mining container orchestration — Antspace, Bitdeer |
minerpools/ | Pool API integrations — Ocean, F2Pool |
power-meter/ | Power metering — ABB, SATEC, Schneider |
temperature/ | Temperature/humidity sensors — Seneca |
How Workers fit into MDK
Kernel
│
│ Hyperswarm HRPC (MDK Protocol envelopes)
▼
`WorkerRuntime` ──┐
│ hosts
Worker Plugin ────┘
│
│ Vendor protocol (TCP, HTTP, Modbus, Serial, …)
▼
Physical HardwareWorkers never initiate communication to Kernel. Kernel obtains each Worker's RPC public key through DHT, local-directory, or same-process discovery, then initiates all MDK Protocol calls to the Worker over HRPC.
Worker architecture
Each Worker has:
- A Worker Plugin, e.g.
antminer/plugin/index.js. A plain object{ contract, dir, connect, disconnect? }— no base class, no subclassing - A
WorkerRuntime, the shared runtime that hosts the plugin's devices and exposes them through the MDK Protocol over HRPC - A
mdk-contract.json, e.g. the Antminer contract, the engineering source of truth. Declares every telemetry field (name, unit, type) and every command (name, params) - A mock server, a local HTTP server with canned responses for hardware-free development
1. Worker Plugin
The plugin is the object WorkerRuntime is constructed with — the contract, the plugin's own directory, and a
connect function that turns one device's config into the device object every handler sees. There is no
base class and no subclassing; a plugin package can be built and tested with zero dependency on WorkerRuntime.
Every telemetry/command handler is invoked as (ctx, params), where ctx = { deviceId, device, config, services }.
miners/whatsminer/
plugin/
index.js # the Worker Plugin: { contract, dir, connect, disconnect }
mdk-contract.json
boot.js # startWhatsminerWorker(opts) — constructs WorkerRuntime
lib/whatsminer.js # the device driver plugin.connect() returns2. WorkerRuntime
WorkerRuntime hosts every device behind one HRPC channel to Kernel. It:
- Starts a Hyperswarm RPC server and responds to every MDK Protocol action
- Provides the RPC public key (
getPublicKey()) that the host process registers or publishes according to the selected discovery mode - Dispatches incoming MDK Protocol actions to the plugin's per-device handlers, wrapping results into the protocol envelope itself
- Persists the DHT/RPC keypair in a process-owned store when one is supplied (stable identity across restarts)
Migrating from MDKWorkerAdapter / ThingManager (pre-0.5.0)
WorkerRuntime generalizes the former MDKWorkerAdapter (persistent seeds, single HRPC respond loop, DHT topic
announce carried over) and replaces ThingManager delegation with per-device handler dispatch. See
Worker Runtime legacy services for the full
migration history and the optional opts.services built-in surface that lets a host answer legacy queries and
commands from a manager's store.
3. mdk-contract.json
Each Worker package ships an mdk-contract.json that declares its full capabilities:
- metadata — provider, device family, brand, supported models
- capabilities.telemetry — metric fields with types, units, and descriptions
- capabilities.commands — available commands with parameters, constraints, and AI workflow examples
- capabilities.health — supported states, alert types, troubleshooting rules
- capabilities.errors — error code → description mapping
Kernel fetches this contract once via capability.request and caches it. The Gateway and AI agents use it to derive available operations dynamically.
Start a Worker
Each Worker package ships its own boot function that constructs WorkerRuntime internally — there is no single
generic startWorker() entry point:
const { getKernel } = require('@tetherto/mdk')
const { startWhatsminerWorker } = require('@tetherto/mdk-worker-whatsminer')
const kernel = await getKernel()
const worker = await startWhatsminerWorker({
workerId: 'whatsminer-rack-1',
model: 'm56s',
storeDir: './store/whatsminer-rack-1',
seedDevices: [{
info: { serialNum: 'WM-001' },
opts: { address: '192.168.1.10', port: 14028, password: 'admin' }
}]
})
await kernel.registerWorker(worker.runtime.getPublicKey())seedDevices only seeds a fresh, empty storeDir; add a device to an already-running Worker with the
registerThing command instead (see each package's own USAGE.md, e.g. miners/whatsminer/USAGE.md).
Implement a new Worker
- Read the full build walkthrough — it covers the plugin shape, handlers, mock, tests, and hosting
WorkerRuntimeend to end - Look at an existing Worker of the same family as a template (e.g.
miners/whatsminer/for a new miner) - Author
mdk-contract.jsonfollowingmdk-contract.schema.json - Implement the hardware translation layer (the plugin's
connect/disconnectand per-field/command handlers) - The Worker instance boots, connects to devices, and publishes or registers its RPC public key through the selected discovery mode — Kernel handles the rest
Testing
Each Worker package has its own mock/server.js that simulates the hardware API. Run tests from the package root:
cd backend/workers/miners/whatsminer && npm test
cd backend/workers/miners/antminer && npm testRun mock devices
Boot one or more device mocks locally — no hardware — with the Workers-level runner. Entries are
comma-delimited; the first token of each entry is the device type (case-insensitive) — or,
for a type-less device such as ocean/f2pool, its name. Flags are dash-free so npm forwards
them with no --: a bare number is the port, and key=value sets any other flag:
npm run mock m56s, ocean
npm run mock b23 4009 host=127.0.0.1 mockControlPort=5009, pm180 4008Run npm run mock with no arguments to list every device and its types. A single Worker package can
also run just its own mock on its default port, e.g. cd miners/whatsminer && npm run mock m56s.
Next steps
- Build a minimal dashboard on top of a Worker
- Understand the install pattern any Worker follows
- Build a full Worker for new hardware