Skip to main content

Architecture Overview

The Device Gateway sits between your physical devices and the Lua agent runtime. It handles authentication, connection management, command routing, and trigger delivery.
┌──────────────┐        ┌──────────────────────┐        ┌──────────────┐
│              │        │   Device Gateway      │        │              │
│   Device 1   │◄──────►│                      │◄──────►│  Lua Agent   │
│  (Node.js)   │  SIO   │  - Auth              │ Valkey  │  Runtime     │
│              │        │  - Connection Mgmt   │ Streams │              │
├──────────────┤        │  - Command Routing    │        │  - Skills    │
│              │        │  - Trigger Delivery   │        │  - Tools     │
│   Device 2   │◄──────►│  - Heartbeat Monitor │◄──────►│  - Triggers  │
│  (Pico W)    │  MQTT  │                      │        │              │
│              │        └──────────────────────┘        └──────────────┘
└──────────────┘

Self-Describing Flow

When a device connects, it does not need any server-side configuration. The device tells the gateway what it can do, and the gateway tells the agent.
1

Device connects with command manifest

The device client sends its commands array during the Socket.IO auth handshake or MQTT online status message. Each command includes a name, description, and optional JSON Schema for input parameters.
2

Gateway registers device

The gateway validates the API key, registers the device in Valkey, and stores the command manifest alongside the connection metadata.
3

Agent discovers tools

When the agent processes a user message, it queries connected devices and merges their commands into the tool list. Each device command appears as a tool named device:{deviceName}:{commandName}.
4

Device disconnects, tools disappear

When a device goes offline (intentional disconnect, network loss, or missed heartbeats), its tools are removed from the agent. No stale tools remain.

Command Delivery Path

When the agent decides to use a device tool, the command flows through the gateway:
User says: "Read the temperature from pico-sensor"

1. Agent selects tool: device:pico-sensor:read_temperature
2. Agent runtime sends command to Device Gateway via Valkey Stream
3. Gateway looks up device connection (Socket.IO socket or MQTT client)
4. Gateway sends command message to device
5. Device executes handler, returns result
6. Gateway publishes response back to Valkey Stream
7. Agent runtime receives result, includes in response to user
Each command carries a unique commandId for idempotency. If the device receives the same command twice (due to retry or redelivery), it returns the cached response.

Trigger Flow

Triggers go in the opposite direction — from device to agent:
Device detects: temperature > 40C

1. Device calls client.trigger('high_temp', { temperature: 42.1 })
2. Gateway receives trigger and acknowledges receipt to device
3. Gateway publishes trigger to Valkey Stream
4. Agent runtime picks up trigger
5. Agent runs the matching defineDeviceTrigger() execute function
6. Execute function can call agent.chat(), use tools, notify users
Triggers are fire-and-forget from the device’s perspective. The device gets an acknowledgment that the gateway received the trigger, but does not wait for the agent to finish processing it.

Transport Comparison

FeatureSocket.IOMQTT
ProtocolWebSocket over HTTPSMQTT 3.1.1 over TLS
Default URLhttps://api.heylua.aimqtts://mqtt.heylua.ai:8883
ReconnectionBuilt-in with jitterBuilt-in with backoff
Message orderingGuaranteed (single socket)Guaranteed per topic (QoS 1)
Offline queueingNoYes (persistent session, clean: false)
Last Will (LWT)Not applicableAutomatic offline status on disconnect
Best forNode.js, desktops, serversMicrocontrollers, battery devices, flaky networks
RAM footprint~10 MB (Node.js)~30 KB (MicroPython on Pico W)

Security Model

API Key Authentication

Every device connection requires a valid API key. The key is sent during the auth handshake (Socket.IO) or as the MQTT password. Keys are never stored in retained MQTT messages.

Agent Scoping

A device can only interact with the agent it authenticates against. Cross-agent communication is not possible.

TLS Encryption

All transports use TLS. Socket.IO connects over HTTPS. MQTT connects over port 8883 with TLS.

Heartbeat Monitoring

The gateway expects a heartbeat every 30 seconds. Missed heartbeats trigger disconnect detection. MQTT additionally uses Last Will and Testament (LWT) for instant offline notification.

Connection Lifecycle

1. Client opens WebSocket to https://api.heylua.ai/devices
2. Auth handshake: { apiKey, agentId, deviceName, commands[] }
3. Server validates, emits 'connected'
4. Client starts heartbeat (every 30s)
5. Bidirectional command/trigger exchange
6. On disconnect: auto-reconnect with jittered exponential backoff
7. On reconnect: re-sends auth (commands may have changed)
8. On SIGTERM/SIGINT: graceful disconnect, no reconnect

Next Steps

Self-Describing Commands

Deep dive into how devices declare their capabilities

Triggers

Understand device-to-agent event flow

MQTT Transport

Configure MQTT for constrained devices

Agent Tools

How device commands become agent tools