Skip to main content
Someone messages your agent on WhatsApp (or any other channel), the agent calls a command on your board as a tool and answers with the result, and the board can wake the agent the other way with a trigger that runs a handler on the platform. Verified against lua-cli 3.45.0. This page gets a Node script on your machine connected as a device named bench-sensor with one command, and shows how to go live and test. Any board works the same way: for a microcontroller, follow the Raspberry Pi Pico W guide after step 2. The device opens one outbound connection over port 443, so it needs no inbound port or public address.

1. Get set up

You need Node 18 or later. If you build with Claude Code, add the Lua plugin first; inside a Claude Code session run:
Claude Code
/lua-doctor checks Node, lua-cli, and your sign-in and offers to fix what’s missing. See Claude Code plugin. Then install the CLI, sign in, and create the agent project. Choose Email when lua auth configure asks for a method: issuing a device credential needs an email sign-in.

2. Issue the device credential

A device connects with its own credential, bound to one agent and one device name. The API keys on the dashboard don’t work on a device; the gateway refuses them. From the agent project, issue one and write it to a file in the folder the device will run from:
The file holds LUA_AGENT_ID, LUA_DEVICE_NAME, and LUA_DEVICE_CREDENTIAL, with mode 600. The credential grants commands and triggers by default; add --operations commands,triggers,assets.upload if the device will upload files. Keep the file out of version control. See Create a device credential for rotation and revocation.

3. The primitives that matter

4. Your first command

In the device folder, install the Node device client and a TypeScript runner:
Describe one command and what runs when the agent calls it:
device.ts
Load the credential file and run it. Leave it running; it sends a heartbeat every 30 seconds and reconnects on its own.
It prints bench-sensor connected. The command list is sent when the device connects, so nothing needs to be pushed: from the agent project, ask the agent.
The reply reports 22.5 degrees. The handler’s return value is the tool result the model sees, so return plain JSON with clear field names.
The handler runs on your machine whenever the agent calls the tool, from every channel the agent is connected to. Stop the script to take the tool away.

5. Go live and test

Skills and the other code primitives go live through an agent version:
A pushed persona is staged; lua deploy persona --set-version latest --force serves it, and a promote doesn’t change it. See Release an agent to production. Devices and device triggers are not part of an agent version. A self-describing device like the one above needs no push. Push a defineDevice declaration, with its trigger handlers, on its own; it reaches the agent on the next turn. Push a standalone defineDeviceTrigger with --auto-deploy, because it runs only once its version is published:
Then test each piece from the agent project. The test-trigger line assumes you have written a temperature_alert handler as in Handle device triggers.
status prints 🟢 Device 'bench-sensor' is online. test sends the command and prints the device’s reply, or the device’s own error. test-trigger fires the trigger through the live pipeline, so the handler really runs, and lua logs --type device-trigger shows its run.

Things that save you time

  • Write the credential to a file. --out plus set -a; . ./.env.device; set +a saves copying secrets around. Use one credential per device; the agent ID and device name must match it exactly.
  • Don’t use a dashboard API key on a device. It fails with AUTH_FAILED over Socket.IO and Not authorized over MQTT. Issue a device credential.
  • Sign in by email. lua devices credential refuses an API-key sign-in, and a LUA_API_KEY in your shell or the project’s .env overrides the email session.
  • has never connected to this agent from lua devices status means the device connected with a different name or agent, or not at all.
  • lua devices list shows every device, including self-describing ones, with online, offline, or disabled.
  • Use underscores in command and trigger names. Hyphens in a device name become underscores in tool names: bench-sensor → device__bench_sensor__read_temperature.
  • Write descriptions for the model. A command’s description and the persona decide when the agent calls it.
  • Test without chatting. lua devices test --command … --ci checks a command and lua devices test-trigger --trigger … runs a handler; both run in scripts.
  • Make trigger handlers idempotent. They are delivered at least once and run up to three times.
  • Reconnect daily. A self-describing device loses its command tools 24 hours after it connected, until it reconnects.
  • Scaffold with Claude Code. /lua-new device and /lua-new device-trigger create, register, and compile the declarations.

Go deeper

How devices work

The connection lifecycle: handshake, tools, triggers, heartbeats, reconnection.

Create a device credential

Operations, the REST route, rotation, and revocation.

Expose device commands as tools

Input schemas, timeouts, retries, and the results the model sees.

Handle device triggers

Fire an event and run a handler on the platform.

Run a device on a Raspberry Pi Pico W

A microcontroller as a device, with the single-file MicroPython client.

Reliability and limits

Reconnection, heartbeats, offline semantics, and every cap.