Skip to main content
This agent switches two relays on a Raspberry Pi from any conversation: a grow light and a fan, addressed by name through a small HTTP API on the Pi. A job turns the light off at 22:00 every night and messages the owner. It is five files, four TypeScript and one Python, and every TypeScript file compiles against lua-cli 3.33.0; run it with the steps on Running any example. Verified against lua-cli 3.33.0.

The conversation

  1. The end user writes “turn the fan on”. The model calls set_output with output: "fan" and state: "on"; the tool posts both values to the Pi, which switches the relay and returns the new state.
  2. The persona repeats the state back and never switches twice for one request.
  3. At 22:00, the lights-out job calls the same tool class directly, then sends a message to the user id in OWNER_USER_ID.

Primitives and channels

  • Skill and tools: greenhouse, with set_output.
  • Job: lights-out, cron 0 22 * * * in Europe/Berlin, three attempts with a 60-second backoff.
  • Runtime objects: User.get(userId), user.send, and env for RELAY_CONTROLLER_URL, RELAY_CONTROLLER_KEY, and OWNER_USER_ID.
  • Channels: any. The nightly message goes to the owner on the channel they last wrote from, when that is WhatsApp, Messenger, Instagram, Teams, SMS, or MessageBird; user.send resolves true either way, so confirm delivery with lua logs.

The code

The Pi maps names to pins and rejects anything else; the agent never sends a pin number.
edge_api.py
The tool’s enums mirror the Pi’s map, so the model can only name outputs that exist.
src/skills/tools/SetOutputTool.ts
The skill’s context prevents double switching.
src/skills/greenhouse.skill.ts
The job reuses the tool class and messages a configured end user, because a job has no conversation to reply into.
src/jobs/LightsOutJob.ts
The agent registers the skill and the job.
src/index.ts

First run

On the Pi (Raspberry Pi OS), install the dependencies and start the API with a secret of your own.
From your laptop on the same network, switch the fan on.
In the agent project, set the variables and run the tool and the job locally; both reach the Pi over your network.
lua chat --ci -e sandbox -m "Turn the fan on" -t runs the conversation with the model. It uploads the .env values with the sandbox version, but the turn runs on the platform, which can’t reach raspberrypi.local, so point RELAY_CONTROLLER_URL at an address the platform can reach first (see Ways to make it yours). Then release it with lua push all --ci --force, lua version create --ci -m "<message>", and lua version promote <n>; Release an agent to production explains what each command changes.

Ways to make it yours

  • Add an output in two places: the OUTPUTS map in edge_api.py and the OUTPUTS tuple in SetOutputTool.ts.
  • Deployed code runs on the platform, so RELAY_CONTROLLER_URL must be an HTTPS address the platform can reach; raspberrypi.local works only for lua test on your own network. To avoid an inbound port, connect the Pi as a device: its commands become tools without any agent code.
  • Find your user id for OWNER_USER_ID by returning user._luaProfile.userId from a temporary tool, where user is await User.get().
  • Add a morning job that turns the light on, or read a sensor through a second endpoint and store readings with Data.create.

Next steps

Schedule a job

Cron, interval, and one-time schedules, retries, and running a job by hand.

Connect your first device

A Node script on the Pi that dials out and exposes its commands as tools.

Send proactive messages

Message an end user from a job or webhook on any channel.