WireClaw
ESP32 AI agent with persistent memory and offline rule engine - chat to configure, run locally 24/7. (Telegram / Serial / NATS).
Install / Use
/learn @M64GitHub/WireClawREADME
WireClaw
An AI agent that lives on a $5 microcontroller and controls real hardware.
Supported chips: ESP32-C6, ESP32-S3, ESP32-C3 (4 MB flash required)
Flash it to your ESP32 from the browser - no tools to install, configure from your phone. The web flasher auto-detects your chip.
Tell it what you want in plain language - over Telegram, serial, or NATS - and it wires up GPIO pins, reads sensors, switches relays, and sets up automation rules that keep running without the AI. It remembers your preferences across reboots, knows what time it is, can talk to other WireClaw devices on the network, and bridges to any serial device - Arduinos, GPS modules, CO2 sensors, RFID readers - over UART.
You: "When the chip temperature goes above 28, set the LED orange.
When it drops back down, set it cyan."
WireClaw: "I've created a rule to set the LED to orange when the temperature exceeds 28
degrees and to cyan when it goes below. If you need any further adjustments
or assistance, feel free to ask!"
Serial Debug: rule_create(sensor_name="chip_temp", condition="gt", threshold=28,
on_action="led_set", on_r=255, on_g=80, on_b=0,
off_action="led_set", off_r=0, off_g=255, off_b=255)
-> Rule created: rule_01 'heat warning' - chip_temp > 28 (every 5s) with auto-off
The rule now runs in the main loop. No cloud, no server, no LLM calls. Just the ESP32 checking the sensor every 5 seconds and flipping the LED.
It remembers things too:
You: "My favorite color is blue."
WireClaw: "I've noted that your favorite color is blue. If there's anything else
you'd like to share or ask, feel free!"
(writes "User's favorite color is blue." to /memory.txt)
--- reboot, hours later ---
You: "Set the LED to my favorite color."
WireClaw: "The LED is now set to your favorite color, blue!
If you need anything, just let me know."
Serial Debug: led_set(r=0, g=0, b=255)
The AI persists notes to flash and reloads them into every conversation. Preferences, device nicknames, observations - they survive reboots.
It can also text you:
You: "Send me a Telegram message when chip temperature goes above 40."
WireClaw: "I've created a new rule to send a Telegram message when the temperature exceeds 40°C.
You will receive a warning message, and another notification when the temperature
returns to normal. Let me know if you need anything else!"
Serial Debug: rule_create(sensor_name="chip_temp", condition="gt", threshold=40,
on_action="telegram",
on_telegram_message="Chip is {value}°C - overheating!",
off_action="telegram",
off_telegram_message="Back to normal at {value}°C.")
-> Rule created: rule_01 'heat alert' - chip_temp > 40 (every 5s) with auto-off
The ESP32 monitors the sensor and sends you a Telegram message the moment the threshold is crossed - with {value} replaced by the actual reading - and another when it clears. No LLM in the loop.
It knows what time it is too:
You: "Please send me a telegram message at 10:12 with the chip temperature"
WireClaw: "I've set up a rule to send you a Telegram message with the chip
temperature at 10:12."
Serial Debug: rule_create(sensor_name="clock_hhmm", condition="eq", threshold=1012,
on_action="telegram",
on_telegram_message="Chip temp: {chip_temp}°C")
-> Rule created: rule_01 'Send Telegram at 10:12' - clock_hhmm == 1012 (every 5s)
[Rule] rule_01 'Send Telegram at 10:12' TRIGGERED (reading=1012, threshold=1012)
Telegram received: "Chip temp: 28.7°C"
Time synced via NTP, the clock_hhmm sensor encodes time as hour*100+minute (1012 = 10:12), and {chip_temp} is replaced with the live sensor reading when the rule fires. Schedule-based automation with real data - no LLM involved at runtime.
It can also react to data from other systems on the network:
You (on Telegram):
"Register a NATS sensor called room_temp on subject home.room.temp with unit C"
Serial: device_register({"name":"room_temp","type":"nats_value","subject":"home.room.temp","unit":"C"})
[NATS] Subscribed 'room_temp' -> home.room.temp (sid=3)
-> Registered nats_value sensor 'room_temp' on subject 'home.room.temp'
The device registry now includes the built-in sensors and the new NATS sensor:
> /devices
--- devices ---
chip_temp [internal_temp] pin=255 = 27.7 C
clock_hour [clock_hour] pin=255 = 4.0 h
clock_minute [clock_minute] pin=255 = 11.0 m
clock_hhmm [clock_hhmm] pin=255 = 411.0
room_temp [nats_value] nats=home.room.temp = 0.0 C
---
Now any system on the NATS network can push values to it:
$ nats pub home.room.temp "25.3"
You (on Telegram): "whats the room temp?"
Serial: sensor_read({"name":"room_temp"})
-> room_temp: 25.3 C
WireClaw: "The current room temperature is 25.3°C."
And you can set up rules on it - same as any other sensor:
You (on Telegram):
"please send me a telegram message, when the room temperature reaches 30 degrees"
Serial: rule_create(sensor_name="room_temp", condition="gt", threshold=30,
on_action="telegram",
on_telegram_message="Room temperature has reached {value}°C.")
-> Rule created: rule_01 'Room Temp Alert' - room_temp > 30 (every 5s)
$ nats pub home.room.temp "45.3"
Serial: [Rule] rule_01 'Room Temp Alert' TRIGGERED (reading=45, threshold=30)
Telegram received: "Room temperature has reached 45°C."
$ nats pub home.room.temp "25.3"
Serial: [Rule] rule_01 'Room Temp Alert' CLEARED (reading=25)
Python scripts, Home Assistant, industrial PLCs, other WireClaws - anything that can publish to NATS can feed data into WireClaw's rule engine. The ESP32 handles the reactions: GPIO, LEDs, relays, Telegram alerts.
It can also talk to other microcontrollers over serial:
You (on Telegram):
"Set up a serial connection to an Arduino at 9600 baud, call it arduino"
Serial: device_register({"name":"arduino","type":"serial_text","baud":9600})
SerialText: UART1 at 9600 baud (RX=4 TX=5)
-> Registered serial_text sensor 'arduino' at 9600 baud (RX=4 TX=5)
Now WireClaw can send commands and receive data over the UART:
You: "Ask the Arduino for a temperature reading"
Serial: serial_send({"text":"GET_TEMP"})
-> Sent to serial: GET_TEMP
[SerialText] '23.5' -> val=23.5 msg='23.5'
sensor_read({"name":"arduino"})
-> arduino: 23.5 (last: '23.5')
WireClaw: "The Arduino reports a temperature of 23.5°C."
And rules work on serial data just like any other sensor:
You: "Alert me on Telegram when the Arduino reading goes above 30."
Serial: rule_create(sensor_name="arduino", condition="gt", threshold=30,
on_action="telegram",
on_telegram_message="Arduino temp: {value}°C - {arduino:msg}")
-> Rule created: rule_01 'arduino alert' - arduino > 30 (every 5s)
GPS modules, CO2 sensors, RFID readers, custom Arduinos - anything with a serial port becomes a sensor that feeds into WireClaw's rule engine. The AI sets it up; the rules run without it.
How It Works
WireClaw runs two loops on the ESP32:
The AI loop handles conversation. When you send a message - via Telegram, serial, or NATS - it calls an LLM (OpenRouter over HTTPS, or a local server like Ollama over HTTP), which responds with tool calls. The AI can read sensors, flip GPIOs, set LEDs, create automation rules, remember things, and talk to other devices. Up to 5 tool-call iterations per message. This is the setup phase.
The rule loop runs every iteration of loop(), continuously, with no network and no LLM. Each cycle it walks through all enabled rules, reads their sensors, evaluates their conditions, and fires their actions - GPIO writes, LED changes, NATS publishes, Telegram alerts - directly from the microcontroller. Rules persist to flash and survive reboots. This is what runs 24/7.
The AI creates the rules. The rules run without the AI.
Time is synced via NTP on boot. Persistent memory is loaded into every conversation as a system message, so the AI always has context about you and your setup.
loop() - runs continuously on the ESP32:
rulesEvaluate()- every iteration, no network- For each enabled rule: read sensor -> check condition -> fire action
- Actions: GPIO write, LED set, NATS publish, Telegram alert, serial send
serialTextPoll()- reads incoming UART bytes, stores last complete line
- AI chat - triggered by incoming messages
- Input: Telegram poll / Serial / NATS
- ->
chatWithLLM()-> LLM API -> tool calls - Tools:
rule_create,led_set,gpio_write,sensor_read,serial_send,remote_chat, ...
The rule loop and the AI loop share the same loop() function but serve different purposes. The rule engine evaluates every cycle regardless of whether anyone is chatting. Multiple rules monitoring the same sensor see the exact same reading per cycle (cached internally), so they always trigger and clear together.
Features
- Rule Engine - persistent local automation, evaluated every loop iteration, edge-triggered or periodic
- Rule Chaining -
chain_createtool builds multi-step sequences in one call (e.g. alert + LED change + auto-off), with non-blocking delays - Time-Aware Rules - NTP sync with POSIX timezone,
clock_hour,clock_minute, andclock_hhmmvirtual sensors for schedule-based automation - Persistent Memory - AI remembers user preferences, device nicknames, and observations across reboots
- Telegram Alerts - rules send push notifications
