> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-Describing Commands

> How devices declare their capabilities without any server-side configuration

## The Key Innovation

Traditional IoT platforms require you to define device capabilities on the server, then write matching firmware, then keep both in sync. With Lua devices, the device itself declares what it can do. There is no server-side schema to maintain.

<Card title="Think of it as:" icon="id-card">
  Each device carries its own resume. When it connects, the agent reads the resume and knows exactly what the device can do.
</Card>

When a device connects, it sends an array of `DeviceCommandDefinition` objects. The gateway stores these alongside the connection. The agent runtime reads them and creates tools automatically. When the device disconnects, the tools disappear.

## DeviceCommandDefinition Fields

Each command definition has the following fields:

```typescript theme={null}
interface DeviceCommandDefinition {
  /** Command name (used by agent to invoke) */
  name: string;

  /** Human-readable description (shown to the AI agent as tool description) */
  description: string;

  /** JSON Schema for command input parameters */
  inputSchema?: Record<string, any>;

  /** Command timeout in milliseconds (default: 30000) */
  timeoutMs?: number;

  /** Retry configuration for failed commands */
  retry?: { maxAttempts: number; backoffMs: number };
}
```

<ParamField body="name" type="string" required>
  The command name. This becomes part of the tool name the agent sees (`device:{deviceName}:{name}`). Use lowercase with underscores (e.g., `read_temperature`, `set_brightness`).
</ParamField>

<ParamField body="description" type="string" required>
  A natural-language description of what the command does. The AI agent reads this to decide when to use the tool. Write it as if explaining to a person: "Read the current room temperature in celsius" is better than "temp read".
</ParamField>

<ParamField body="inputSchema" type="Record<string, any>">
  A JSON Schema object describing the parameters the command accepts. The agent uses this to construct the correct payload.
</ParamField>

<ParamField body="timeoutMs" type="number" default="30000">
  How long to wait for the device to respond before the command fails. Increase for slow operations like printing or scanning.
</ParamField>

<ParamField body="retry" type="{ maxAttempts: number; backoffMs: number }">
  Automatic retry on failure. The gateway retries with exponential backoff starting at `backoffMs`.
</ParamField>

## JSON Schema Examples

### No Parameters

<CodeGroup>
  ```typescript TypeScript theme={null}
  {
    name: 'get_status',
    description: 'Get the current status of the printer including paper level and ink',
  }
  ```

  ```python Python theme={null}
  DeviceCommandDefinition(
      name="get_status",
      description="Get the current status of the printer including paper level and ink",
  )
  ```
</CodeGroup>

### Simple Parameters

<CodeGroup>
  ```typescript TypeScript theme={null}
  {
    name: 'set_brightness',
    description: 'Set the display brightness level',
    inputSchema: {
      type: 'object',
      properties: {
        level: {
          type: 'number',
          minimum: 0,
          maximum: 100,
          description: 'Brightness percentage (0-100)',
        },
      },
      required: ['level'],
    },
  }
  ```

  ```python Python theme={null}
  DeviceCommandDefinition(
      name="set_brightness",
      description="Set the display brightness level",
      input_schema={
          "type": "object",
          "properties": {
              "level": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "description": "Brightness percentage (0-100)",
              },
          },
          "required": ["level"],
      },
  )
  ```
</CodeGroup>

### Enum Parameters

<CodeGroup>
  ```typescript TypeScript theme={null}
  {
    name: 'set_mode',
    description: 'Switch the device operating mode',
    inputSchema: {
      type: 'object',
      properties: {
        mode: {
          type: 'string',
          enum: ['idle', 'active', 'maintenance', 'sleep'],
          description: 'Target operating mode',
        },
      },
      required: ['mode'],
    },
  }
  ```

  ```python Python theme={null}
  DeviceCommandDefinition(
      name="set_mode",
      description="Switch the device operating mode",
      input_schema={
          "type": "object",
          "properties": {
              "mode": {
                  "type": "string",
                  "enum": ["idle", "active", "maintenance", "sleep"],
                  "description": "Target operating mode",
              },
          },
          "required": ["mode"],
      },
  )
  ```
</CodeGroup>

### Complex Parameters

<CodeGroup>
  ```typescript TypeScript theme={null}
  {
    name: 'print_label',
    description: 'Print a shipping label with the given details',
    inputSchema: {
      type: 'object',
      properties: {
        recipient: {
          type: 'object',
          properties: {
            name: { type: 'string' },
            address: { type: 'string' },
            city: { type: 'string' },
            postalCode: { type: 'string' },
          },
          required: ['name', 'address', 'city', 'postalCode'],
        },
        copies: {
          type: 'number',
          minimum: 1,
          maximum: 10,
          default: 1,
        },
      },
      required: ['recipient'],
    },
    timeoutMs: 60000,
    retry: { maxAttempts: 2, backoffMs: 1000 },
  }
  ```

  ```python Python theme={null}
  DeviceCommandDefinition(
      name="print_label",
      description="Print a shipping label with the given details",
      input_schema={
          "type": "object",
          "properties": {
              "recipient": {
                  "type": "object",
                  "properties": {
                      "name": {"type": "string"},
                      "address": {"type": "string"},
                      "city": {"type": "string"},
                      "postalCode": {"type": "string"},
                  },
                  "required": ["name", "address", "city", "postalCode"],
              },
              "copies": {
                  "type": "number",
                  "minimum": 1,
                  "maximum": 10,
                  "default": 1,
              },
          },
          "required": ["recipient"],
      },
      timeout_ms=60000,
      retry={"maxAttempts": 2, "backoffMs": 1000},
  )
  ```
</CodeGroup>

## Validation Rules

* **`name`** must be unique within a single device. Two devices can have commands with the same name.
* **`description`** should be a complete sentence. The agent treats it as a tool description.
* **`inputSchema`** must be valid JSON Schema draft-07. The `type` at the top level should be `'object'`.
* **`timeoutMs`** minimum is 1000 (1 second). The default of 30000 (30 seconds) works for most commands.

## Dynamic Updates

Commands are sent at connect time. To change the command list, update the `commands` array in your `DeviceClientConfig` and restart the device (or disconnect and reconnect).

<CodeGroup>
  ```typescript TypeScript theme={null}
  // Version 1: basic sensor
  const device = new DeviceClient({
    deviceName: 'env-sensor',
    commands: [
      { name: 'read_temperature', description: 'Read temperature' },
    ],
    // ...
  });

  // Version 2: added humidity and calibration
  const device = new DeviceClient({
    deviceName: 'env-sensor',
    commands: [
      { name: 'read_temperature', description: 'Read temperature in celsius' },
      { name: 'read_humidity', description: 'Read relative humidity percentage' },
      { name: 'calibrate', description: 'Run sensor calibration routine', timeoutMs: 60000 },
    ],
    // ...
  });
  ```

  ```python Python theme={null}
  # Version 1: basic sensor
  client = DeviceClient(
      device_name="env-sensor",
      commands=[
          DeviceCommandDefinition(name="read_temperature", description="Read temperature"),
      ],
      # ...
  )

  # Version 2: added humidity and calibration
  client = DeviceClient(
      device_name="env-sensor",
      commands=[
          DeviceCommandDefinition(name="read_temperature", description="Read temperature in celsius"),
          DeviceCommandDefinition(name="read_humidity", description="Read relative humidity percentage"),
          DeviceCommandDefinition(name="calibrate", description="Run sensor calibration routine", timeout_ms=60000),
      ],
      # ...
  )
  ```
</CodeGroup>

No `lua push` or `lua deploy` needed. Just restart the device.

<Warning>
  **When a device goes offline, its tools disappear from the agent.** If a user asks the agent to use a device tool and the device is not connected, the agent will not have that tool available. Design your agent persona to handle this gracefully (e.g., "The sensor is currently offline").
</Warning>

## Next Steps

<CardGroup cols={2}>
  <Card title="Agent Tools" icon="wrench" href="/devices/agent-tools">
    How device commands become tools the agent can use
  </Card>

  <Card title="Node.js Client" icon="node-js" href="/devices/node-client">
    Complete DeviceClientConfig reference
  </Card>

  <Card title="Triggers" icon="bolt" href="/devices/triggers">
    The other direction -- device events sent to the agent
  </Card>

  <Card title="Quickstart" icon="rocket" href="/devices/quickstart">
    See self-describing commands in action
  </Card>
</CardGroup>
