PySpell
Tailscale, English to Python LLM and 8 containers local
Last verified:
What is PySpell?
PySpell is a sandboxed language evaluator for running small, safe Python or Rust expressions on tiny devices like the ESP32-S3 microcontroller. A PySpell program is a single expression (Python) or let bindings followed by a trailing expression (Rust) that evaluates to a value such as a number, boolean, string, or list. Free identifiers are resolved at evaluation time against a host-supplied environment containing CLI variables on a laptop or live device readings on a microcontroller. The only I/O is a host-granted, allowlisted fetch_json function; there are no loops, functions, or imports, making it small, fast, and safe to accept from elsewhere.
Key features include two compilation methods: full-fidelity front-ends using syn (Rust) and rustpython-parser (Python) on the host, and a tiny hand-written parser (a few kB, no_std) that builds the same AST directly on the device. The language supports literals (integers, floats, booleans, strings, lists), arithmetic and comparison operators, boolean operators with short-circuiting, membership tests, negative indexing, conditional expressions, and built-in functions like len, abs, min, max, sum, fetch, fetch_json, json_get, and show. Network access is mediated through an allowlist, and fetch_json streams HTTP responses without buffering the entire body.
PySpell is designed for developers working with embedded systems, microcontrollers, and IoT devices who need lightweight, pushable units of code on tiny devices. It enables an offline AI coding agent served directly from the chip, with a ~0.45 M-parameter language model (< 500 kB, int8) that turns natural language commands into PySpell code and runs them live on the chip. The tool targets the
PySpell pricing
Pricing model: Freemium
Free and open source - no pricing tiers mentioned. The project is hosted on GitHub at punnerud.github.io/pyspell and appears to be a research/development project with no commercial paid plans. Users can download, compile, and deploy the evaluator themselves using cargo on the host or flash it to ESP32-S3 devices.
PySpell pros
- Sandboxed evaluator with deny-by-default grammar for security
- No loops, functions, recursion, attribute access, imports, or unallowed I/O
- Supports both Python and Rust syntax subsets
- Tiny on-device parser (few kB, no_std) for direct device compilation
- Full-fidelity host compilation using syn and rustpython-parser
- Single expression Python or let bindings plus trailing expression Rust
- Free identifiers resolved from host-supplied environment at evaluation time
- Allowlisted fetch_json for safe network access only
- fetch_json streams HTTP responses without buffering entire body
- Built-in functions: len, abs, min, max, sum, any, all, round, int, float, bool
- Supports negative list indexing and chained comparisons
- Wall-clock timeout enforcement with ESP timer on devices
- Instruction budget guards against runaway evaluations
- Reads live device variables: free_heap, min_free_heap, uptime_ms, uptime_s
- Offline AI coding agent served directly from the chip
- ~0.45 M-parameter language model under 500 kB in int8 format
- Browser copies literal content verbatim while model emits semantic directives
- Inference runs in WebAssembly client-side, never resident in chip heap
- 512-token vocabulary with frozen embeddings from all-MiniLM
- Portable IR blob compilation for pushing to devices
- REPL interface for interactive device programming over USB-serial
- Tailscale tunnel integration for browser-based device access
- Adds only ~62 kB overhead to networking firmware on ESP32
- json_get extracts scalar at dotted path without full JSON parse
PySpell cons
- No loops allowed in the language subset
- No functions or recursion permitted
- No attribute access available
- No imports of external modules
- Full Python/Rust not supported, only safe subset
- Single TCP segment limit (~1.2 kB) for POST request body
- No PSRAM on ESP32-S3 limits parallelism
- 8-way parallel pool and full Tailscale don't coexist on esp-idf stack
- ~60 kB worst-case peak free heap is very constrained
- Model too small to reliably copy arbitrary tokens like numbers
- HTTP GET code must fit URL-encoded in query string
- Timeout clamped to 1-60 seconds maximum
- Network access requires host allowlist configuration
- Device gates show function via config (allow/on/off, auto-revert)
- Bytes Scans for JSON replace full DOM trees limiting complexity
- Crypto uses SPKI pinning only, no CA-chain validation
- Cooperative shared stack only on lean build for parallelism
- Heap and stack share one DRAM pool with hand-tuned balance
- Static content must be &'static str in flash
- serde_json from_reader skips huge fields instead of buffering
Frequently asked questions about PySpell
What is PySpell?
PySpell is a sandboxed language evaluator for running small, safe Python or Rust expressions on tiny devices like the ESP32-S3 microcontroller. A PySpell program is a single expression (Python) or let bindings followed by a trailing expression (Rust) that evaluates to a value such as a number, boolean, string, or list. The only I/O is a host-granted, allowlisted fetch_json function; there are no loops, functions, or imports.
What devices can run PySpell?
PySpell runs on the ESP32-S3 microcontroller, which has 512 kB of SRAM and no PSRAM. The portable evaluator (pyspell-core, no_std + alloc) runs unchanged on ESP32-S3. It adds only ~62 kB on top of networking firmware and can run a full Tailscale node, the evaluator, a browser agent IDE served off the chip, a native MCP server, and TLS.
How do I run PySpell on the host?
On the host, you can evaluate with bound free variables using: cargo run -p pyspell-cli -- run examples/health.py --set free_heap=120000 --set uptime_ms=45000. You can compile to a portable IR blob with: cargo run -p pyspell-cli -- compile examples/health.py. You can also run an interactive REPL over USB-serial with: cargo run -p pyspell-cli -- repl --port /dev/cu.usbmodem2101 --lang python.
What is the syntax for PySpell Python?
PySpell Python is a single expression. Examples include: free_heap > 100000 and uptime_s < 60, 250 if distance > 1000 else 0, 0 < temp < 60 (chained), 20 not in peers, sum(), readings[-1] (negative index), and max(a, b). It supports integers, floats, booleans, strings, lists, arithmetic operators, comparison operators including chaining, boolean operators (and, or, not), membership tests, and negative indexing.
What is the syntax for PySpell Rust?
PySpell Rust uses let bindings followed by a trailing expression. Examples include: free_heap > 100000 && uptime_s < 60, if distance > 1000 { 250 } else { 0 }, let used = total - free; used * 100 / total, !peers.contains(20), sum(), readings[readings.len() - 1], and max(a, b). It supports the same literal types and most operators, but use, but uses &&, ||, ! for boolean operations and requires else in if expressions.
How does network access work in PySpell?
Network access uses fetch(url) for HTTP(S) GET and fetch_json(url, path) to stream and extract a scalar at a dotted/indexed JSON path. fetch is mediated by a host allowlist so programs can't reach arbitrary URLs. fetch_json streams the HTTP body and stops when the field is found, never buffering the whole body. On the host, use --allow-host to permit specific hosts: pyspell run oslo_temp.py --allow-host api.met.no.
What built-in functions are available?
Built-in functions include: len(list) for number of elements, abs(x) for absolute value, min(list) or min(a,b,...) for minimum, max(list) or max(a,b,...) for maximum, sum(list) for sum of numeric list, any(list) for true if any element is truthy, all(list) for true if all elements are truthy, round(x) to nearest integer, int(x) truncating toward zero, float(x) converting to float, bool(x) for truthiness, index(list,x) for position or -1, before(list,a,b), first(list), last(list), str(x), json_get(text,path), fetch(url), fetch_json(url,path), and show(x) for rendering to text.
How does the offline AI coding agent work?
Open http://<dongle>/ over the tunnel for a Cursor-like agent. Type commands like 'flash the light', 'show the text hello', 'what is 7 plus 5', or 'reverse the word robot'. A ~0.45 M-parameter language model (< 500 kB, int8) turns natural language into PySpell code, runs it live on the chip, and shows the result or physical action. Runtime, model, tokenizer, and dictionary are all served from the dongle offline with no cloud and no key required (OpenAI is optional).
What sandbox limits does PySpell enforce?
PySpell enforces a deny-by-default grammar with only whitelisted expression nodes and built-ins (no loops, functions, recursion, attribute access, imports, strings, or I/O). Every evaluation has an instruction budget step limit as a runaway guard. Callers can supply a wall-clock deadline (e.g. 10s) enforced by the ESP timer on devices. The on-device parser accepts only the safe subset, keeping the device's attack surface as just a bounded decoder and evaluator.
How does PySpell fit in 512 kB on ESP32?
PySpell fits through multiple memory tricks: SPKI leaf-key pinning instead of CA-chain validation saves 6 kB; heap admission gate bounds concurrency; serde_json from_reader skips huge fields; fetch_json stops when value is found; raw byte-scans replace JSON DOM trees; static content lives in flash as &'static str streamed as 512-byte TCP segments; heap and stack share one DRAM pool tuned by hand; SO_LINGER=0 frees lwIP sockets immediately; cooperative shared stack on lean build makes parallelism cheap.