Connected devices
The optional plumbing for any fleet of devices or agents your app manages, from enrolment and identity to telemetry, policy, commands and health, and what MainSDK leaves to you.
Say you sell a security agent that runs on your customers' laptops, a sensor that sits in their warehouses, or an AI agent that runs on their servers. Your app is the control plane: it has to know which machines belong to which customer, hear from them, change how they behave and ask them to do things, safely. Every fleet needs the same plumbing, whatever it does. MainSDK gives you that plumbing and nothing else.
Optional, opt-in, no cost when unused
You turn it on in two places: a device section in main.yaml and an import of mainsdk/device in your server code. Until you do both, nothing loads, nothing listens, no table is created and nothing is counted. mainsdk doesn't re-export it, so your bundle, start-up time and tests are the same as before. mainsdk check speaks up only when you do one half without the other.
The program on the device uses mainsdk/device/client. It needs no main.yaml and no other part of MainSDK, so you can drop it into any TypeScript agent. The Rust, Python and Go SDKs will implement the same contract in their own languages.
What's in
| Part | What it does for you |
|---|---|
| Enrolment and identity | A device joins one of your tenants with a one-time token, a code a signed-in person approves, its cloud workload's OIDC token, or attestation evidence. It makes its own key and never shares it. You can rotate keys, renew credentials and revoke a device in one call |
| Attestation | Ask for evidence before you trust a device: none, a software self-report, or hardware and confidential-computing evidence through the TAPS driver. The SDK says plainly what each level proves |
| Telemetry transport | client.send(stream, record) signs, batches, buffers through outages and delivers each record once, in order, to your handler inside the right tenant, with size limits, backpressure and redaction on the device |
| Policy sync | Publish signed, versioned settings to a tenant or group, roll them out in stages, roll back on failures, see which version each device runs; playbooks can tighten them for a while, never loosen |
| Commands | Ask a device to run a handler it registered, with an expiry, people's approval for risky ones, a signed receipt and an undo; an agent can ask but never approve |
| Health | A signed heartbeat with versions, licence state, clock, policy and pressure; online, late, silent and revoked; no false alarms when your own app was the one that was down |
| Testing | Simulated devices on a fake clock in @mainsdk/testing, so the whole flow runs in a unit test |
// your server
import { device } from "mainsdk/device";
const { token } = await device.enrolments.create({ group: "warehouse-3", uses: 25 });
device.telemetry.on("sensor.reading", async (records) => { await saveReadings(records); });// the program on the device
import { deviceClient, fileKeyStore } from "mainsdk/device/client";
const client = deviceClient({ url: "https://app.example.com/mainsdk/device", keys: fileKeyStore("/var/lib/acme") });
await client.enrol({ token: process.env.ENROL_TOKEN! });
await client.start();
client.send("sensor.reading", { celsius: 21.5 });What's out
MainSDK carries no security content and isn't bent for one kind of product. It ships no detection rules, no threat signatures, no event formats for processes, files or network flows on a device, no malware handling, no scanners and no sensors. Endpoint detection and response, antivirus, IoT monitoring and AI agent supervision are products you build on top: you decide what your device measures, what your policy means and what your commands do, and you describe each with s.
It also doesn't deliver code. There is no remote shell, no "run this script" command and no software update through commands; a device runs only the handlers it registered, and mainsdk check flags a handler that would run its input as code. Updates to your device program go through signed delivery (TUF), like everything else MainSDK delivers.
Identity on TAPS
Every device is a did:key, the same kind of identity TAPS, HubXero's open trust protocol, uses. The built-in local driver signs with Ed25519 or P-256 and needs nothing else. When you need more, the taps driver in @mainsdk/device-taps issues TAPS device and agent credentials that any TAPS verifier can check, publishes revocation as a status list, appraises TPM, Android, Apple and confidential-computing evidence, and can require a chain of delegation from your customer's own people before a device runs a risky command. Switching drivers changes one line of main.yaml.
What an identity proves
| It proves | It doesn't prove |
|---|---|
| The sender holds the key that enrolled, with a proof you accepted, into this tenant | That the device is healthy, unmodified or free of malware |
| The message wasn't changed and isn't a replay | That what the device reports is true |
| With hardware evidence: the key lives in hardware on a device that booted cleanly | That a software key wasn't copied to another machine |
| With environment evidence: the program matches a build you approved | Anything about the person using the device |
Next
Was this page helpful?