MDK Logo

Workers

Workers as integration components — discovery model, capability contract, and adding hardware

Overview

This page introduces the Worker as a development component. It explains what a Worker owns, how ORK discovers it, what the capability contract is, and how to build a Worker for new hardware. Read this before integrating new hardware, configuring discovery, or building on top of the Worker protocol.

What a worker owns

A Worker wraps a device library and exposes it to ORK via the MDK Protocol. Workers are the integration handlers between physical hardware and @tetherto/mdk-ork, and the unyielding source of truth for that hardware. @tetherto/mdk-ork operates purely as a synchronized state machine over Worker-reported state — it never reads hardware directly.

Workers are passive: they become a reachable endpoint and wait. ORK initiates every RPC call; Workers only ever respond. See the deployment topologies connection model for how this directionality shapes transport choices.

Discovery model

How ORK finds a Worker depends on whether they share a machine.

In all cases, the post-discovery sequence is identical — ORK requests identity, registers the Worker, then queries its capabilities. The full sequence and state machine are described in the ORK — What ORK owns section.

ModeHow ORK finds the workerWhen to use
DHTWorker joins a Hyperswarm DHT topic; ORK listens on the same topic and connects automaticallyProduction microservices, workers on separate hosts or networks
LocalWorker publishes itself to a shared directory; ORK watches the directory: no DHT neededAll components on one machine, restricted outbound networking
Same-processstartWorker(W, { ork }) passes the ORK instance directly: no network lookupGetting started, single-process sites

Discovery is a startup concern only — it determines how ORK obtains the worker's RPC public key, nothing more. Once connected, all three modes use the same HyperswarmRPC transport and the same MDK Protocol envelope (command.request, telemetry.pull, and so on). Local and same-process modes route traffic over the local network interface; DHT mode routes over the public internet. The available commands, telemetry, and operations are identical in all three.

DHT mode

In multi-process DHT mode, ORK and the worker must join the same Hyperswarm topic. Two options:

Auto-generated (default)startWorker() generates a random 32-byte hex topic, writes it to os.tmpdir()/mdk/.dht-topic, and joins the DHT; getOrk() reads the same file and joins the matching topic.

The auto-generated topic file is local to the machine or container that writes it. When ORK and the worker run in separate containers or on separate hosts they cannot share this file and discovery stops responding. Use an explicit topic in any multi-host or multi-container setup.

Explicit topic — pass the same value to both calls directly:

const ork = await getOrk({ topic: '<32-byte-hex>' })
const { manager } = await startWorker(WorkerClass, { orkTopic: '<32-byte-hex>' })

The worker must join the topic before ORK starts listening. Start the worker process first, then start ORK. waitForDiscovery() polls the registry until discovered workers reach READY state.

The DHT pattern is demonstrated end-to-end in dht-worker.js and dht-ork.js.

Local mode

In local mode, ORK and workers coordinate through a shared directory on the same machine (default <root>/.worker-keys/). No Hyperswarm topic is joined and no outbound internet connection is required.

Worker side: on startup, startWorker publishes the worker's identity to the shared directory. The entry is stable across restarts, so restarting a worker is a no-op from ORK's perspective.

ORK side: getOrk watches the directory with fs.watch and runs a full scan every four seconds. Each entry found triggers the normal discovery listener (Identity → Capability → Ready), the same sequence used in DHT mode.

const ork = await getOrk({ discovery: { mode: 'local' } })
const { manager } = await startWorker(WorkerClass, { discovery: { mode: 'local' } })

A custom directory can be passed when the default path is not suitable:

const ork = await getOrk({ discovery: { mode: 'local', dir: '/shared/mdk-keys' } })
const { manager } = await startWorker(WorkerClass, { discovery: { mode: 'local', dir: '/shared/mdk-keys' } })

Keys persist across restarts and the directory is read again each time ORK starts, so workers and ORK can start in any order without coordination.

All processes must share the same filesystem path. Local mode requires every component to run on the same machine — use DHT mode for workers on separate hosts.

The full-site example demonstrates local mode as its default multi-process setup — up --discovery local starts all workers in local mode, and up --discovery dht switches to DHT without any other code change.

Same-process mode

Same-process mode skips all network discovery. Pass the live ORK instance to startWorker() and registration happens as a direct in-process call — no topic, no directory, no network lookup:

const ork = await getOrk(opts)
const { manager } = await startWorker(WorkerClass, { ork })

Two behaviors differ from DHT and local mode:

  • Registration: startWorker calls ork.registerWorker() directly with the adapter's public key. The worker reaches READY synchronously before startWorker returns — no waitForDiscovery() required
  • Lifecycle coupling: the worker's stop handler is pushed onto ORK's internal _cleanup queue. When ORK shuts down, it stops the worker automatically. In DHT and local modes you own the worker's shutdown via your process signal handlers

Use the same-process mode for the get-started tutorial and single-process deployments. For multi-process, use DHT or local mode instead.

Capability contract

mdk-contract.json is the canonical source of truth for a Worker's programmatic capabilities and its AI context. MDK deliberately merges formal validation and semantic guidance into a single JSON contract:

  • description does double duty as the human UI label and AI edge-case rule (for example, "Outlet temperature > 85C requires intervention")
  • constraints governs orchestration limits
  • troubleshooting provides if/then recovery behaviors alongside the payload it evaluates

The exhaustive JSON Schema is mdk-contract.schema.json, with a reference instance at mdk-contract.json.

Add hardware

External integrators add new hardware by building a Worker package that conforms to the strict Device-Lib Contract:

  1. Reference mdk-contract.schema.json to author the mdk-contract.json, validating strict data schemas while injecting explanations, constraints, and troubleshooting directly into the relevant nodes.
  2. Subclass @tetherto/worker-base and implement the two translation hooks, onTelemetryPull and onCommand, in src/hardware.js. All HRPC plumbing is inherited from the base class.
  3. Boot the Worker instance, connect to devices, and register with @tetherto/mdk-ork using the appropriate discovery mode. @tetherto/mdk-ork detects the peer and pulls its identity and capabilities.

(See mdk-whatsminer-worker.js for a reference subclass implementing both hooks against a real device.)

Next steps

On this page