A minimal AI agent runtime for ESP32-S3 microcontrollers. Talk to your hardware through Telegram, powered by Claude.
User ↔ Telegram ↔ Agent Loop ↔ Claude API
↕
Tool Registry
↕ ↕
Hardware Flash Storage
picoagent turns an ESP32-S3 into an AI-powered device controller. You message it on Telegram, Claude figures out what tools to call, the ESP32 executes them, and you get the result back. Session history persists across reboots via SPIFFS.
Think of it as a tiny AI-OS where the Tool trait is the syscall interface.
- Rust (esp channel) —
rustup toolchain install esp - espflash —
cargo install espflash - just —
cargo install just - A Freenove ESP32-S3 WROOM (or compatible, 8MB flash)
- A Telegram bot token (from @BotFather)
- A Claude API key or OAuth token
# Clone and configure
cp .env.example .env
# Edit .env with your credentials
# Build and flash
just flash-monitorAll config is compile-time via .env:
WIFI_SSID="your_network"
WIFI_PASS="your_password"
TELEGRAM_BOT_TOKEN="123456:ABC..."
TELEGRAM_CHAT_ID="your_chat_id"
CLAUDE_API_KEY="sk-ant-..."
# Optional
CLAUDE_MODEL="claude-sonnet-4-20250514"
DEVICE_LABEL="Grow Room Controller"Both standard API keys (sk-ant-api...) and OAuth tokens (sk-ant-oat...) are supported. OAuth tokens are auto-detected and use the appropriate auth headers.
The Tool trait is the only API you need:
use crate::tools::{Tool, ToolOutput};
struct TemperatureTool { sensor: Bme680 }
impl Tool for TemperatureTool {
fn name(&self) -> &'static str { "read_temperature" }
fn description(&self) -> &'static str { "Read ambient temperature in Celsius" }
fn parameters_schema(&self) -> serde_json::Value {
serde_json::json!({
"type": "object",
"properties": {},
})
}
fn execute(&mut self, _params: serde_json::Value) -> anyhow::Result<ToolOutput> {
let temp = self.sensor.read()?;
Ok(ToolOutput::ok(format!("{:.1}°C", temp)))
}
}Register it in main.rs:
tools.register(TemperatureTool::new(sensor));Claude will automatically see the tool and invoke it when relevant.
| Tool | Description |
|---|---|
gpio |
Digital pin control — set outputs, read inputs |
system_info |
Device status — free heap, uptime, firmware version |
| Command | Description |
|---|---|
/start |
Show device info |
/clear |
Wipe conversation history |
/status |
Memory, session stats, tool call count |
just build # Compile firmware
just flash # Flash to device
just flash-monitor # Flash + open serial monitor
just monitor # Serial monitor only
just erase # Erase flash (reset storage)
just size # Show binary size
just clean # Clean build artifacts- Synchronous, single-threaded. No async runtime. The main loop polls Telegram, runs the agent, sends the response. Simple.
- Session compaction. Conversation history is bounded (40 messages / 32KB). When exceeded, older messages are summarized by Claude and folded into the system prompt.
- SPIFFS persistence. Session survives reboots. ~5MB storage partition on flash.
- Dual Claude auth. Standard API keys and OAuth tokens (Claude Code compatible). Auto-detected from the key prefix.
picoagent includes spore-core, a tiny stack-based VM for AI-generated embedded programs. Claude generates Spore token streams that run directly on the ESP32 — cooperative multitasking, GPIO/I2C/SPI/BLE/MQTT, event-driven tasks, all in #![no_std] with zero allocations.
DEF read_temp
LIT 0x76 I2C_ADDR
LIT 0xFA I2C_WRITE LIT 3 I2C_READ_BUF
DUP LIT 0 BUF_GET_U8 LIT 12 SHL
SWAP DUP LIT 1 BUF_GET_U8 LIT 4 SHL OR
SWAP LIT 2 BUF_GET_U8 LIT 4 SHR OR
END
TASK monitor
EVERY 30000
read_temp I>F FLIT 5120.0 DIV
DUP FLIT 35.0 GT IF
STR "alert/temp" SWAP F>STR MQTT_PUB
THEN
ENDEVERY
ENDTASK
A spore is a program — a self-contained capsule of instructions that lands on a microcontroller and starts doing things. The name fits: biological, microscopic, produced and dispersed by AI rather than hand-crafted.
See spore-core/CLAUDE.md for the full architecture.
3MB for the app, ~5MB for SPIFFS storage. Binary is ~1.2MB after release optimizations.
MIT