lua-cli 3.33.0; run it with the steps on Running any example.
Verified against lua-cli 3.33.0.
The conversation
- The end user writes “turn the fan on”. The model calls
set_outputwithoutput: "fan"andstate: "on"; the tool posts both values to the Pi, which switches the relay and returns the new state. - The persona repeats the state back and never switches twice for one request.
- At 22:00, the
lights-outjob calls the same tool class directly, then sends a message to the user id inOWNER_USER_ID.
Primitives and channels
- Skill and tools:
greenhouse, withset_output. - Job:
lights-out, cron0 22 * * *inEurope/Berlin, three attempts with a 60-second backoff. - Runtime objects:
User.get(userId),user.send, andenvforRELAY_CONTROLLER_URL,RELAY_CONTROLLER_KEY, andOWNER_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.sendresolvestrueeither way, so confirm delivery withlua logs.
The code
The Pi maps names to pins and rejects anything else; the agent never sends a pin number.edge_api.py
src/skills/tools/SetOutputTool.ts
context prevents double switching.
src/skills/greenhouse.skill.ts
src/jobs/LightsOutJob.ts
src/index.ts
First run
On the Pi (Raspberry Pi OS), install the dependencies and start the API with a secret of your own.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
OUTPUTSmap inedge_api.pyand theOUTPUTStuple inSetOutputTool.ts. - Deployed code runs on the platform, so
RELAY_CONTROLLER_URLmust be an HTTPS address the platform can reach;raspberrypi.localworks only forlua teston 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_IDby returninguser._luaProfile.userIdfrom a temporary tool, whereuserisawait 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.

