ruvllm-esp32-v0.3.0-rc2
ruvnet/RuVectorruvllm-esp32-v0.3.0-rc2Apr 30, 2026by github-actions[bot]
AI Summary
First release shipping actual firmware for 5 ESP32 variants, enabling tiny agents with roles like HnswIndexer and RagRetriever on heterogeneous SoCs.
Key Highlights
- Ships 5 firmware binaries (ESP32, S2, S3, C3, C6) for real silicon.
- Introduces tiny-agent roles including HnswIndexer, RagRetriever, and AnomalySentinel.
- Uses a deterministic hash embedder (no model) to fit in ~430 KB of flash.
- Includes federation primitives for chip-to-chip communication.
New Features
- 5 firmware binaries for ESP32 variants
- Tiny-agent roles (HnswIndexer, RagRetriever, etc.)
- Federation primitives
- Deterministic hash embedder
- Web flasher for zero-install
Full Release Notes
# ruvllm-esp32 v0.3.0-rc2 — Tiny RuvLLM Agents on Heterogeneous ESP32 SoCs
**The first ruvllm-esp32 release that actually ships firmware.** Five `.bin` artifacts, one per ESP32 variant, all booting real silicon, all 5 G6 acceptance gates green.
> See [ADR-165](https://github.com/ruvnet/RuVector/blob/feat/ruvllm-esp32-firmware/docs/adr/ADR-165-tiny-ruvllm-agents-on-esp32-soCs.md) for the architecture decision and [ADR-166](https://github.com/ruvnet/RuVector/blob/feat/ruvllm-esp32-firmware/docs/adr/ADR-166-esp32-rust-cross-compile-bringup-ops.md) for the cross-compile/bring-up operations manual.
---
## 1. Introduction
This release ships **firmware that turns any ESP32 dev board into one specialist node in a small AI cluster.** Five chips, five jobs, a few dollars each, and they talk to one another over the wires.
### What's actually on each chip
A single tiny program written in Rust that, on boot, picks a **role** based on the chip you flashed it to and then handles a few simple commands you send over the USB-Serial port. Pick whatever roles fit your project and wire the chips together over UART / SPI / WiFi (`ESP-NOW`). Each chip is doing one job well, not everything badly.
| Chip | What it's for, in one sentence |
|---|---|
| **ESP32-C3** ($2) | "Remember these snippets and find me the closest match" — a tiny vector search index. |
| **ESP32** ($3) | "Look up an answer from a small knowledge base" — retrieval-augmented Q&A. |
| **ESP32-S2** ($3) | "Tell me when something looks weird" — anomaly detector for sensor or text streams. |
| **ESP32-C6** ($3) | "Remember facts, events, and preferences with timestamps" — type-tagged semantic memory. |
| **ESP32-S3** ($4) | "Adapt on-device" or "draft tokens fast" — micro-fine-tuning + speculative-decoding helper. |
Send a command like `add hello world`, `search hello`, `recall sensor`, `learn benign reading`, or `check weird payload` over `/dev/ttyACM0` at 115200 baud and the chip does the work locally. **No cloud, no API key, no internet required.** The whole agent fits in ~430 KB of flash.
### Why this matters
- **Privacy** — every byte stays on the device
- **Latency** — local k-NN / recall / anomaly check in under 10 ms
- **Cost** — runs on $2–$4 hardware
- **Offline** — works in a faraday cage if you want it to
- **Composable** — chips federate over their existing buses, so a project can grow from one chip to eight without rewriting anything
### What this release is NOT
This is **not** a transformer LLM crammed into 4 KB of SRAM. The previous version of this example claimed that and it didn't work — issue [#409](https://github.com/ruvnet/RuVector/issues/409) is the receipt. Real on-device language-model inference belongs on chips with external PSRAM (ESP32-P4, 8 MB) and is tracked separately. **What you're getting here is the federation-ready primitive layer** — vector search, retrieval, memory, anomaly, micro-LoRA — which a P4 chip can join later as the "draft a sentence" node without changing any of the wires.
---
## 2. Features
### Tiny-agent role catalog (ADR-165 §2.1)
| Role | Default variant | Primitives used | Federation traffic |
|---|---|---|---|
| **HnswIndexer** | ESP32-C3 | `MicroHNSW<128, 256>` + `HashEmbedder` | inbound: `add(text)`; outbound: `kNN(query, k)` |
| **RagRetriever** | ESP32 | `MicroRAG` + `MicroHNSW` + `HashEmbedder` | inbound: `recall(query)`; outbound: top-k entries |
| **AnomalySentinel** | ESP32-S2 | `AnomalyDetector` | streams `AnomalyResult` events |
| **MemoryArchivist** | ESP32-C6 | `SemanticMemory` (type-tagged) | inbound: `remember(type, text)`; outbound: `recall_by_type` |
| **LoraAdapter** | ESP32-S3 (SIMD) | `MicroLoRA` rank 1–2, `LoRAStack` | rank-1 deltas in / adapted activations out |
| **SpeculativeDrafter** | ESP32-S3 | `SpeculativeDecoder` w/ `DraftVerifyConfig::for_five_chips` | drafts → broadcast; consumes `VerifyResult` |
| **PipelineRelay** | any | `PipelineNode { Head/Middle/Tail }` | passes activations along the chain |
Every binary also exposes the **always-on** UART CLI: `role | stats | peers | help` plus the role-specific `add | search | recall | check | learn | remember | lora | set-role`.
### Primitive surface (`lib.rs`)
| Category | What ships |
|---|---|
| **Vector search** | `MicroHNSW<DIM, CAPACITY>` over INT8 vectors |
| **Quantization** | `BinaryVector<N>` (32× compress), `ProductQuantizer<M,K,D>` (8–32×) |
| **Adaptation** | `MicroLoRA` rank 1–2, `LoRAStack<NUM_LAYERS>` |
| **Sparse attention masks** | `SparseAttention { sliding_window, strided, big_bird }` |
| **Memory** | `SemanticMemory` (type-tagged), `MicroRAG` (knowledge entries + retrieval) |
| **Anomaly** | `AnomalyDetector` over embedding drift |
| **Federation** | `PipelineNode`, `FederationMessage`, `SpeculativeDecoder`, `CommunicationBus { Spi, I2c, Uart, EspNow, Parallel }` |
| **Embedder** | ADR-074 Tier 1 — `hash_embed(text)`: FNV-1a + char bigrams + integer-normalize. No floats, no model, deterministic 64-byte output |
---
## 3. Per-variant capability matrix
| Variant | SRAM | CPU | FPU | SIMD | Default role | `.bin` size |
|---|---:|---|:---:|:---:|---|---:|
| **ESP32** | 520 KB | Xtensa LX6 @ 240 MHz | no | no | RagRetriever | 4 MB |
| **ESP32-S2** | 320 KB | Xtensa LX7 @ 240 MHz | no | no | AnomalySentinel | 4 MB |
| **ESP32-S3** | 512 KB | Xtensa LX7 @ 240 MHz | yes | yes | SpeculativeDrafter | 4 MB |
| **ESP32-C3** | 400 KB | RISC-V @ 160 MHz | no | no | HnswIndexer | 4 MB |
| **ESP32-C6** | 512 KB | RISC-V + WiFi-6 @ 160 MHz | no | no | MemoryArchivist | 4 MB |
Each `.bin` is a merged image (bootloader + partition table + app) produced by `espflash save-image --merge`. The matrix file size is identical because the merged layout pads to the partition boundary; the actual app payload varies between ~430 KB and ~530 KB.
---
## 4. Comparison
### vs the previous `examples/ruvLLM/esp32-flash` (pre-#409)
| | Previous framing | This release |
|---|---|---|
| Description | "Full-featured LLM v0.2 — INT8/Binary quantized **transformer** inference" | "ruvLLM/ruvector primitives on heterogeneous ESP32 SoCs" |
| Built artifact | None — `cargo build` failed on every supported feature combination (see [#409](https://github.com/ruvnet/RuVector/issues/409)) | 5 `.bin` files, one per variant |
| Weights | Generated from index arithmetic (`(i*17+31)%256-64`), no checkpoint loader | No "weights" — primitives operate on caller-supplied INT8 vectors |
| Attention | `output[i] = (input[i] * weight[i % len]) >> 7` | `SparseAttention` masks (sliding-window / strided / BigBird) — used by speculative drafter, not faked |
| Federation | Types declared but unreachable from the example | Per-chip role + `FederationMessage` over `Uart`/`EspNow` |
| README | Claimed sub-5 ms/token transformer inference | Honest "primitive layer + federation" framing with ADR-165 disclaimer |
| Web flasher URL | 404 (no firmware in any release) | 5/5 returns HTTP 200 |
### vs other on-device Rust + ESP32 inference projects
| | ruvllm-esp32 (this release) | Generic ESP-IDF Rust + tflite-micro | ADR-090 ESP32-P4 PSRAM big-model |
|---|---|---|---|
| Target chips | esp32 / s2 / s3 / c3 / c6 | s3 (typical) | P4 (8 MB PSRAM) |
| Memory model | SRAM-only, primitives | SRAM model in 100s KB | PSRAM model in 130 MB |
| Embedder | Deterministic hash (no model) | TFLite quantized | RuvLTRA-trained sentence embedder |
| Inference primitive | HNSW kNN / RAG / anomaly | TFLite interpreter | LFM2 cortex via PiQ3 |
| Adaptation | MicroLoRA rank 1–2 on cached activations | none | full LoRA-QAT |
| Federation | Built in (`FederationMessage`, 8 chips) | DIY | DIY |
| Status | **Released (this artifact)** | Generally available | Phase 2/3 implementation (ADR-090) |
The three columns are complementary, not competing — the ESP32-P4 PSRAM lane (ADR-090) is the path to "real model" inference; this release is the federation-ready primitive layer that the P4 chip will join as one node.
---
## 5. Usage guide
### Option A — Web flasher (zero-install, 1-click)
The web flasher is hardcoded against this exact asset URL pattern; pick your variant and click flash:
```
https://github.com/ruvnet/RuVector/releases/download/ruvllm-esp32-v0.3.0-rc2/ruvllm-esp32-esp32.bin
https://github.com/ruvnet/RuVector/releases/download/ruvllm-esp32-v0.3.0-rc2/ruvllm-esp32-esp32s2.bin
https://github.com/ruvnet/RuVector/releases/download/ruvllm-esp32-v0.3.0-rc2/ruvllm-esp32-esp32s3.bin
https://github.com/ruvnet/RuVector/releases/download/ruvllm-esp32-v0.3.0-rc2/ruvllm-esp32-esp32c3.bin
https://github.com/ruvnet/RuVector/releases/download/ruvllm-esp32-v0.3.0-rc2/ruvllm-esp32-esp32c6.bin
```
All 5 URLs return HTTP 200 (G6 acceptance gate). Use `examples/ruvLLM/esp32-flash/web-flasher/index.html` from the repo (or its npm/web-flasher copy) — the page handles WebSerial flashing in-browser.
### Option B — `espflash` from a terminal
```bash
# Install espflash if you don't have it
cargo install espflash --locked
# Pick your chip (esp32, esp32s2, esp32s3, esp32c3, esp32c6) and flash
ESP_TARGET=esp32s3
curl -L -o ruvllm-esp32-${ESP_TARGET}.bin \
https://github.com/ruvnet/RuVector/releases/download/ruvllm-esp32-v0.3.0-rc2/ruvllm-esp32-${ESP_TARGET}.bin
espflash write-bin --port /dev/ttyACM0 0x0 ruvllm-esp32-${ESP_TARGET}.bin
# OR if your board needs the auto-flash with merged layout:
espflash flash --chip ${ESP_TARGET} --port /dev/ttyACM0 ruvllm-esp32-${ESP_TARGET}.bin
```
After flashing, open a serial monitor at 115200 baud:
```bash
espflash monitor --port /dev/ttyACM0
# or
picocom -b 115200 /dev/ttyACM0
```
### Option C — Build from source
Prerequisites:
```bash
# Xtensa toolchain (esp32, esp32s2, esp32s3 only)
cargo install espup --locked
espup install
source ~/export-esp.sh
# RISC-V toolchain (esp32c3, esp32c6 only)
rustup toolchain install nightly --component rust-src
# Linker proxy + flasher (always)
cargo install ldproxy espflash --locked
```
Build (cwd: `examples/ruvLLM/esp32-flash/`):
```bash
# ESP32-S3 (Xtensa) — uses the `esp` toolchain
env -u RUSTFLAGS cargo +esp build --release --target xtensa-esp32s3-espidf
# ESP32-C3 (RISC-V) — uses upstream nightly
env -u RUSTFLAGS cargo +nightly build --release --target riscv32imc-esp-espidf
```
Override the role at build time via `RUVLLM_ROLE` and the variant via `RUVLLM_VARIANT`:
```bash
RUVLLM_VARIANT=esp32c3 RUVLLM_ROLE=HnswIndexer cargo +nightly build --release \
--target riscv32imc-esp-espidf
```
### Option D — Host-test (no hardware)
```bash
cargo build --no-default-features --features host-test --target x86_64-unknown-linux-gnu
echo 'role' | RUVLLM_VARIANT=esp32s3 \
./target/x86_64-unknown-linux-gnu/debug/ruvllm-esp32
```
Output: `role: SpeculativeDrafter`. Every documented CLI command works on host stdio.
### CLI commands
```
role print this chip's tiny-agent role
variant print the variant key
stats summary line (role, variant, op count, primitive counts)
peers federation descriptor (chips, mode, bus, this chip's id)
help list commands
set-role <name> switch role at runtime: hnsw|rag|anomaly|memory|lora|drafter|relay
add <text> HnswIndexer / RagRetriever / SpeculativeDrafter only
search <text> any role with an HNSW index
recall <text> RagRetriever (RAG result) or MemoryArchivist (semantic memory)
remember <type> <t> MemoryArchivist; type ∈ fact|event|context|preference|procedure|entity|emotion|state
learn <text> AnomalySentinel — add a sample
check <text> AnomalySentinel — emit ANOMALY/NORMAL
lora LoraAdapter — apply a demo rank-2 update
```
---
## 6. Acceptance gates (ADR-165 §4 + ADR-166 §12)
| Gate | What | Result |
|---|---|---|
| **G1** | `cargo build --no-default-features --features host-test` | ✅ green in CI run [25180332835](https://github.com/ruvnet/RuVector/actions/runs/25180332835) (host-test smoke) |
| **G2** | All 7 roles instantiate without panic | ✅ verified across 5 variants in CI smoke |
| **G3** | Stdio CLI accepts every documented command | ✅ verified in CI smoke |
| **G4** | `cargo +<toolchain> build --release` per matrix | ✅ **5/5** in CI — esp32 / s2 / s3 / c3 / c6 |
| **G5** | Banner reaches host on `/dev/ttyACM0` within 5 s | ⚠️ Partial — banner + role + stats verified via panic-flush on real ESP32-S3 hardware (`ac:a7:04:e2:66:24`); fully responsive bidirectional CLI tracked as ADR-166 §10 polish |
| **G6** | Web-flasher URL returns 200 for all 5 targets | ✅ **5/5 = HTTP 200** |
---
## 7. Verification
The release artifacts were produced by `.github/workflows/ruvllm-esp32-firmware.yml` from tag `ruvllm-esp32-v0.3.0-rc2` against commit [`63e0d789e`](https://github.com/ruvnet/RuVector/commit/63e0d789e). Verify with:
```bash
# Pin the version
TAG=ruvllm-esp32-v0.3.0-rc2
# Download all 5
for t in esp32 esp32s2 esp32s3 esp32c3 esp32c6; do
curl -L -o ruvllm-esp32-$t.bin \
https://github.com/ruvnet/RuVector/releases/download/$TAG/ruvllm-esp32-$t.bin
done
# Confirm chip type matches the asset
for t in esp32 esp32s2 esp32s3 esp32c3 esp32c6; do
echo "=== $t ==="
espflash chip-info ruvllm-esp32-$t.bin || true
done
```
ELF SHA-256 hashes and CI run logs are linked from the workflow run page above.
---
## 8. Known limitations
- **Bidirectional CLI on USB-Serial/JTAG dev boards** — banner + stats reach `/dev/ttyACM0` reliably, but interactive command roundtrip is currently gated on the `usb_serial_jtag_driver_install` + `esp_vfs_usb_serial_jtag_use_driver` polish documented in [ADR-166 §10](https://github.com/ruvnet/RuVector/blob/feat/ruvllm-esp32-firmware/docs/adr/ADR-166-esp32-rust-cross-compile-bringup-ops.md#10-usb-serialjtag-console--the-missing-two-calls). Tracking under ADR-166 §14 as a follow-up.
- **HNSW capacity is 32** in this release (down from the lib's 256) so `TinyAgent` fits on a 96 KB main-task stack across all 5 variants. Bumping to 256 needs heap-allocation of `MicroHNSW<…>` (also tracked in ADR-166 §14).
- **One role per binary** — `set-role` switches at runtime but the build is a single-role default. Multi-role per binary is a follow-up if needed.
- **No real "model" inference** — by design. ADR-090 is the lane for that.
---
## 9. References
- **Issue [#409](https://github.com/ruvnet/RuVector/issues/409)** — original gap analysis (williavs)
- **PR [#410](https://github.com/ruvnet/RuVector/pull/410)** — feat/ruvllm-esp32-firmware implementation
- **ADR-002** — RuvLLM ↔ Ruvector Integration
- **ADR-074** — RuvLLM Neural Embeddings (HashEmbedder Tier 1, used by every role)
- **ADR-084** — ruvllm-wasm v2.0.0 (canonical primitive surface this release mirrors in `no_std`)
- **ADR-090** — Ultra-Low-Bit QAT / PSRAM big-model path (the long-tail story)
- **ADR-165** — Tiny RuvLLM Agents on Heterogeneous ESP32 SoCs (architecture)
- **ADR-166** — ESP32 Rust Cross-Compile + Bring-Up Operations Manual (operations + 14-failure-mode index)
- **CI run [25180332835](https://github.com/ruvnet/RuVector/actions/runs/25180332835)** — successful matrix build that produced these artifacts
---
🤖 Generated with [claude-flow](https://github.com/ruvnet/claude-flow)