Use when adding or updating an output hardware module in this repo, especially actuator devices like lights, vibration blocks, or other controllable modules. Covers how a new output block is recognized from MQTT, how commands are mapped and published, how actuator state is stored, when to create generic versus device-specific tools, and how to validate the full control path from agent tool call to hardware response and Supabase records.
Use this skill when integrating a new output module such as:
This skill is for the current Bun + Hono + MQTT + Supabase repo.
After following this skill, an output module should satisfy all of these:
HardwareStorehardware_eventshardware_historyFor output modules, there are three linked layers:
In repo terms:
HardwareStore holds current actuator stateAihubMqttBridge.publishActuatorCommand(...) turns tool actions into MQTT publishesdevice-tools or generic hardware tools expose the capability to the agentRead these files first:
src/main/hardware/mqtt-bridge.tssrc/main/hardware/mqtt-protocol.tssrc/main/hardware/store.tssrc/main/tools/hardware/index.tssrc/main/tools/device-tools/index.tssrc/main/history/hardware-event-service.tssrc/main/history/supabase-history-service.tssrc/main/agent.tsBefore writing code, pin down:
node_idnode_typeFor a light-like module in the current repo, the existing bridge already assumes firmware-native topics such as:
aihub/cmd/{nodeId}/ws2812aihub/cmd/{nodeId}/ledIf the new module uses the same protocol, the bridge may already be sufficient.
If the module requires a new command subject or a new action vocabulary, extend the bridge first.
The server must infer all of these:
node_typecapabilitytype = actuatorUpdate these files when introducing a truly new actuator type:
src/main/hardware/mqtt-protocol.tssrc/main/hardware/mqtt-bridge.tsTypical work:
If the module is just another light block using led, you usually do not need to change this layer.
All agent-side control eventually reaches:
AihubMqttBridge.publishActuatorCommand(...)This function is the command router.
When adding a new output module type, define:
Rules:
on, off, set_color, pulseIf you need multiple firmware compatibility publishes, keep them inside the bridge, not in tools.
HardwareStorePublishing a command is not enough. The system also needs a current state model.
Current state is updated in two ways:
HardwareStore.controlActuator(...)HardwareStore.applyMessage(...) from MQTT responsesWhen adding a new actuator type, update:
src/main/hardware/store.tsTypical work:
controlActuator(...) for the new action setgetActuatorStateForBlock(...)Keep the state minimal and queryable. Only store fields the UI or agent can actually use.
There are two patterns.
Use the generic hardware tool path when:
block_idCurrent generic actuator entry points live in:
src/main/tools/hardware/index.tsUse src/main/tools/device-tools/index.ts when:
For each dedicated output device define:
blockIdlabeldescriptionThe repo will generate a tool named:
device_<blockId>For a second light block, usually this is the only code you need to touch.
The tool schema should describe user intent, not MQTT JSON.
Good examples:
onoffset_colorset_patternset_speedpulseBad examples:
topiceffect field when that field is only protocol glueThe bridge should translate semantic tool actions into low-level protocol payloads.
For output modules, command observability matters.
You want:
hardware_eventshardware_historyCurrent repo guidance:
hardware_events is the raw command/response evidence layerhardware_history is the block state history layerFor questions like:
prefer hardware_events
For questions like:
use current state or hardware_history
Usually do only this:
led messagessrc/main/tools/device-tools/index.ts/v1/blocksdevice_<blockId> shows up and can control itUsually touch all of these:
mqtt-protocol.tsmqtt-bridge.tsstore.tsRun these after integrating a new output module.
bun run typecheck:node
bun test
Start the server and verify:
GET /v1/blocks includes the actuator blocktype=actuatorcapabilityIssue one real command through the correct tool and verify all of these:
hardware_eventshardware_historyAsk one direct control question and one follow-up question.
Examples:
Expected behavior:
get_hardware_historyget_hardware_eventshardware_historyAn output module integration is not done until all are true:
hardware_events