<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: micropython</title><link href="http://feeds.simonwillison.net/" rel="alternate"/><link href="http://feeds.simonwillison.net/tags/micropython.atom" rel="self"/><id>http://feeds.simonwillison.net/</id><updated>2026-06-06T04:26:06+00:00</updated><author><name>Simon Willison</name></author><entry><title>micropython-wasm 0.1a2</title><link href="https://simonwillison.net/2026/Jun/6/micropython-wasm/#atom-tag" rel="alternate"/><published>2026-06-06T04:26:06+00:00</published><updated>2026-06-06T04:26:06+00:00</updated><id>https://simonwillison.net/2026/Jun/6/micropython-wasm/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/micropython-wasm/releases/tag/0.1a2"&gt;micropython-wasm 0.1a2&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;I added a CLI to &lt;code&gt;micropython-wasm&lt;/code&gt; (&lt;a href="https://github.com/simonw/micropython-wasm/issues/7"&gt;issue #7&lt;/a&gt;), inspired by the first draft of &lt;a href="https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/"&gt;the blog entry&lt;/a&gt; when I realized it would be a great way to illustrate the &lt;a href="https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/#try-it-yourself"&gt;Try it yourself&lt;/a&gt; section.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sandboxing"&gt;sandboxing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/micropython"&gt;micropython&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="python"/><category term="sandboxing"/><category term="webassembly"/><category term="micropython"/></entry><entry><title>Running Python code in a sandbox with MicroPython and WASM</title><link href="https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/#atom-tag" rel="alternate"/><published>2026-06-06T03:53:34+00:00</published><updated>2026-06-06T03:53:34+00:00</updated><id>https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/#atom-tag</id><summary type="html">
    &lt;p&gt;I've been experimenting with different approaches to running code in a sandbox for several years now, but my latest attempt feels like it might finally have all of the characteristics I've been looking for. I've released it as an alpha package called &lt;a href="https://github.com/simonw/micropython-wasm"&gt;micropython-wasm&lt;/a&gt;, and I'm using it for a code execution sandbox plugin for &lt;a href="https://github.com/datasette/datasette-agent"&gt;Datasette Agent&lt;/a&gt; called &lt;a href="https://github.com/datasette/datasette-agent-micropython"&gt;datasette-agent-micropython&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/#why-do-i-want-a-sandbox-"&gt;Why do I want a sandbox?&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/#what-i-want-from-a-sandbox"&gt;What I want from a sandbox&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/#webassembly-looks-really-promising-here"&gt;WebAssembly looks really promising here&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/#micropython-in-webassembly"&gt;MicroPython in WebAssembly&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/#building-the-first-version"&gt;Building the first version&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/#try-it-yourself"&gt;Try it yourself&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Jun/6/micropython-in-a-sandbox/#should-you-trust-my-vibe-coded-sandbox-"&gt;Should you trust my vibe-coded sandbox?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="why-do-i-want-a-sandbox-"&gt;Why do I want a sandbox?&lt;/h4&gt;
&lt;p&gt;My key open source projects - &lt;a href="https://datasette.io/"&gt;Datasette&lt;/a&gt;, &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt;, even &lt;a href="https://sqlite-utils.datasette.io/"&gt;sqlite-utils&lt;/a&gt; - all support plugins.&lt;/p&gt;
&lt;p&gt;I absolutely love plugins as a mechanism for extending software. A carefully designed plugin system reduces the risk involved in trying new things to almost nothing - even the wildest ideas won't leave a lasting influence on the core application itself. My software can grow a new feature overnight and I don't even have to review a pull request!&lt;/p&gt;
&lt;p&gt;There's one major drawback: my plugin systems all use Python and &lt;a href="https://pluggy.readthedocs.io/en/latest/"&gt;Pluggy&lt;/a&gt;, and plugin code executes with full privileges within my applications. A buggy or malicious plugin could break everything or leak private data.&lt;/p&gt;
&lt;p&gt;I'd love to be able to run plugin-style code in an environment where it is unable to read unapproved files, connect to a network, or generally operate in a way that's risky or harmful to the rest of the application or the user's computer.&lt;/p&gt;
&lt;p&gt;My interest covers more than just plugins. For Datasette in particular there are many features I'd like to support where arbitrary code execution would be useful. I've already experimented with this for &lt;a href="https://enrichments.datasette.io/"&gt;Datasette Enrichments&lt;/a&gt;, where code can be used to transform values stored in a table. I'd love to build a mechanism where you can run code on a schedule that fetches JSON from an approved location, runs a tiny bit of code to reformat it into a list of dictionaries, then inserts those as rows in a SQLite database table.&lt;/p&gt;
&lt;h4 id="what-i-want-from-a-sandbox"&gt;What I want from a sandbox&lt;/h4&gt;
&lt;p&gt;My goal is to execute code safely within my own Python applications. Here's what I need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dependencies that &lt;strong&gt;cleanly install from PyPI&lt;/strong&gt;, including binary wheels across multiple platforms if necessary. I don't want people using my software to have to take any extra steps beyond directly installing my Python package.&lt;/li&gt;
&lt;li&gt;Executed code must be subject to both &lt;strong&gt;memory&lt;/strong&gt; and &lt;strong&gt;CPU&lt;/strong&gt; limits. I don't want &lt;code&gt;while True: s += "longer string"&lt;/code&gt; to crash my application or the user's computer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File access must be strictly controlled&lt;/strong&gt;. Either no filesystem access at all or I get to define exactly which files can be read and which files can be written to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network access is controlled as well&lt;/strong&gt;. Sandboxed code should not be able to communicate with anything without going through a layer I fully control.&lt;/li&gt;
&lt;li&gt;Support for interaction with &lt;strong&gt;host functions&lt;/strong&gt;. A sandbox isn't much use if I can't carefully expose selected platform features to the code that it's running.&lt;/li&gt;
&lt;li&gt;It has to be &lt;strong&gt;robust, supported, and clearly documented&lt;/strong&gt;. I've lost count of the number of sandbox projects I've seen in repos with warnings that they aren't actively maintained!&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="webassembly-looks-really-promising-here"&gt;WebAssembly looks really promising here&lt;/h4&gt;
&lt;p&gt;Web browsers operate in the most hostile environment imaginable when it comes to malicious code. Their job is to download &lt;em&gt;and execute&lt;/em&gt; untrusted code from the web on almost every page load.&lt;/p&gt;
&lt;p&gt;Given this, JavaScript engines should be excellent candidates for sandboxes. Sadly those engines are also extremely complicated, and are not designed for easy embedding in other projects. Most of the v8-in-Python projects I've seen are infrequently maintained and come with warnings not to use them with completely untrusted code.&lt;/p&gt;
&lt;p&gt;WebAssembly is a &lt;em&gt;much better&lt;/em&gt; candidate. It was designed from the start to support all of the characteristics I care about and has been tested in browsers for nearly a decade. The &lt;a href="https://pypi.org/project/wasmtime"&gt;wasmtime&lt;/a&gt; Python library is actively maintained and has binary wheels.&lt;/p&gt;
&lt;h4 id="micropython-in-webassembly"&gt;MicroPython in WebAssembly&lt;/h4&gt;
&lt;p&gt;WebAssembly engines like wasmtime run WebAssembly binaries. Some programming languages like Rust are easy to compile directly to WebAssembly. Dynamic languages like JavaScript and Python are harder - they support language primitives like &lt;code&gt;eval()&lt;/code&gt;, which means they need a full interpreter available at runtime.&lt;/p&gt;
&lt;p&gt;To run Python we need a full Python interpreter compiled to WebAssembly, wired up in a way that makes it easy to feed it code, hook up host functions and access the results.&lt;/p&gt;
&lt;p&gt;Pyodide offers an outstanding package for running Python using WebAssembly in the browser, but using Pyodide in server-side Python isn't supported. The most recent advice I could find was &lt;a href="https://github.com/pyodide/pyodide/discussions/5145"&gt;from October 2024&lt;/a&gt; stating "Pyodide is built by the Emscripten toolchain and can only run in a browser or Node.js".&lt;/p&gt;
&lt;p&gt;The other day I decided to take a look at &lt;a href="https://micropython.org"&gt;MicroPython&lt;/a&gt; as an option for this. The MicroPython site says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers and in constrained environments.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;WebAssembly sure feels like a constrained environment to me!&lt;/p&gt;
&lt;h4 id="building-the-first-version"&gt;Building the first version&lt;/h4&gt;
&lt;p&gt;I had GPT-5.5 Pro &lt;a href="https://chatgpt.com/share/6a1e2a5c-58b8-8328-ba1c-0e6aadb0a051"&gt;do some research for me&lt;/a&gt;, which turned up &lt;a href="https://github.com/micropython/micropython/pull/13676"&gt;this PR against MicroPython&lt;/a&gt; by &lt;a href="https://github.com/yamt"&gt;Yamamoto Takahashi&lt;/a&gt; titled "Experimental WASI support for ports/unix".&lt;/p&gt;
&lt;p&gt;It then produced this &lt;a href="https://github.com/simonw/micropython-wasm/blob/c08fbd2276b15dc8c9bdff82845f750971f45647/research.md"&gt;research.md document&lt;/a&gt;, so I let Codex Desktop and GPT-5.5 high &lt;a href="https://gist.github.com/simonw/27461a16d76f28f8619c609444d544fe"&gt;loose on it&lt;/a&gt; to see what would happen:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;read the research.md document and build this. You will probably need to write a script that compiles a custom WASM version of MicroPython as part of this project - fetch the MicroPython code to a /tmp directory for this as part of that script.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It worked. I now had a prototype Python library that could execute Python code inside a WebAssembly sandbox!&lt;/p&gt;
&lt;p&gt;The trickiest piece to solve was persistent interpreter state. The WASM build we are using here exposes a single entry point which starts the interpreter, runs the code and then stops the interpreter at the end.&lt;/p&gt;
&lt;p&gt;This works fine for one-off scripts, but for Datasette Agent I want variables and functions to stay resident in memory so I can reuse them across multiple code execution calls.&lt;/p&gt;
&lt;p&gt;A neat thing about working with coding agents is that you can get from an idea to a proof of concept quickly. I prompted:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;For keeping variables resident: what if we ran code inside micropython itself which called a host function get_next_python_code() and then passed that to eval() - and that host function blocked until new code was available, maybe by running in a thread with a queue? Could that or a similar idea help here?&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After some iteration we got to a version of this that works! In Python code you can now do this:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;micropython_wasm&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;MicroPythonSession&lt;/span&gt;

&lt;span class="pl-k"&gt;with&lt;/span&gt; &lt;span class="pl-en"&gt;MicroPythonSession&lt;/span&gt;() &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-s1"&gt;session&lt;/span&gt;:
    &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;session&lt;/span&gt;.&lt;span class="pl-c1"&gt;run&lt;/span&gt;(&lt;span class="pl-s"&gt;"x = 10&lt;span class="pl-cce"&gt;\n&lt;/span&gt;print(x)"&lt;/span&gt;).&lt;span class="pl-c1"&gt;stdout&lt;/span&gt;)
    &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;session&lt;/span&gt;.&lt;span class="pl-c1"&gt;run&lt;/span&gt;(&lt;span class="pl-s"&gt;"x += 5&lt;span class="pl-cce"&gt;\n&lt;/span&gt;print(x)"&lt;/span&gt;).&lt;span class="pl-c1"&gt;stdout&lt;/span&gt;)
    &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s1"&gt;session&lt;/span&gt;.&lt;span class="pl-c1"&gt;run&lt;/span&gt;(&lt;span class="pl-s"&gt;"print(x * 2)"&lt;/span&gt;).&lt;span class="pl-c1"&gt;stdout&lt;/span&gt;)&lt;/pre&gt;
&lt;p&gt;Under the hood this starts a thread, sets up a request queue and then sends messages to that queue for the &lt;code&gt;session.run()&lt;/code&gt; command, each time waiting on a reply queue for the result of that execution. Inside WASM the MicroPython interpreter blocks waiting for a &lt;code&gt;__session_next__()&lt;/code&gt; host function to return the next line of code, which it runs &lt;code&gt;eval()&lt;/code&gt; on before calling &lt;code&gt;__session_result__({"id": request_id, "ok": True})&lt;/code&gt; when each block has been successfully executed.&lt;/p&gt;
&lt;p&gt;The other piece of complexity was supporting host functions, so my Python library could selectively expose functions that could then be called by code running in MicroPython.&lt;/p&gt;
&lt;p&gt;Codex ended up solving this with &lt;a href="https://github.com/simonw/micropython-wasm/blob/0.1a1/micropython_wasm/usercmodule/host/hostmodule.c"&gt;78 lines of C&lt;/a&gt;, which ends up compiled into the &lt;a href="https://github.com/simonw/micropython-wasm/blob/0.1a1/micropython_wasm/artifacts/micropython-wasi.wasm"&gt;362KB WebAssembly blob&lt;/a&gt; I'm distributing with the package.&lt;/p&gt;
&lt;p&gt;I am by no means a C programmer, but I've read the C and had two different models explain it to me (here's &lt;a href="https://claude.ai/share/62f74371-cc3c-44f2-b406-33d03513de9e"&gt;Claude's explanation&lt;/a&gt;) and I've subjected it to a barrage of tests.&lt;/p&gt;
&lt;p&gt;The great thing about working with WebAssembly is that if the C turns out to be fatally flawed the worst that can happen is the WebAssembly execution will fail with an exception. I can live with that risk.&lt;/p&gt;
&lt;p&gt;Memory limits are directly supported by wasmtime. CPU limits are a little harder: wasmtime offers a "fuel" concept to limit how many operations a WebAssembly call can execute, and that's the correct fit for this problem, but the units are hard to reason about. I'm experimenting with a 20 million default "fuel" setting now but I'm not confident that it's the most appropriate value.&lt;/p&gt;
&lt;h4 id="try-it-yourself"&gt;Try it yourself&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;micropython-wasm&lt;/code&gt; alpha is now &lt;a href="https://pypi.org/project/micropython-wasm"&gt;live on PyPI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can try it from your own Python code as &lt;a href="https://github.com/simonw/micropython-wasm"&gt;described in the README&lt;/a&gt;. I've also added a simple CLI mode in &lt;a href="https://github.com/simonw/micropython-wasm/releases/tag/0.1a2"&gt;version 0.1a2&lt;/a&gt; which means you can try it using &lt;code&gt;uvx&lt;/code&gt; without first installing it like so:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx micropython-wasm -c &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;print("Hello world")&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; To see it run out of fuel:&lt;/span&gt;
uvx micropython-wasm -c &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;s = ""; while True: s += "longer"&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Outputs: micropython-wasm: guest exited with code 1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can also try it in &lt;a href="https://agent.datasette.io/"&gt;Datasette Agent&lt;/a&gt; like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx llm keys &lt;span class="pl-c1"&gt;set&lt;/span&gt; openai
&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;#&lt;/span&gt; Paste in an OpenAI key, then:&lt;/span&gt;
uvx --with datasette-agent \
  --with datasette-agent-micropython \
  --prerelease allow \
  datasette --internal internal.db \
    -s plugins.datasette-llm.default_model gpt-5.5 \
    --root -o&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then navigate to &lt;a href="http://127.0.0.1:8001/-/agent"&gt;http://127.0.0.1:8001/-/agent&lt;/a&gt; and run the prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;show me some micropython&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/micropython-in-datasette-agent.jpg" style="max-width: 100%" alt="Screenshot of a chat application interface with a dark blue-grey header reading &amp;quot;home&amp;quot; on the left and &amp;quot;root&amp;quot; with a hamburger menu icon on the right. Below is a navigation row with &amp;quot;← Back&amp;quot; and &amp;quot;Chat&amp;quot; on the left and an &amp;quot;EXPORT&amp;quot; button on the right. A blue user message bubble reads &amp;quot;show me some micropython&amp;quot;. Below it a collapsed thinking section reads &amp;quot;▸Thinking: … to show the result clearly. After that, I can wrap up with a brief explanation!&amp;quot; followed by a &amp;quot;▶ Tool: execute_micropython&amp;quot; label. A code block follows: &amp;quot;# A tiny MicroPython example: blink-style logic + Fibonacci&amp;quot; / &amp;quot;def fib(n):&amp;quot; / &amp;quot;    a, b = 0, 1&amp;quot; / &amp;quot;    out = []&amp;quot; / &amp;quot;    for _ in range(n):&amp;quot; / &amp;quot;        out.append(a)&amp;quot; / &amp;quot;        a, b = b, a + b&amp;quot; / &amp;quot;    return out&amp;quot; / &amp;#39;print(&amp;quot;Hello from MicroPython!&amp;quot;)&amp;#39; / &amp;#39;print(&amp;quot;First 10 Fibonacci numbers:&amp;quot;, fib(10))&amp;#39; / &amp;quot;# MicroPython often runs on microcontrollers, e.g.:&amp;quot; / &amp;quot;# from machine import Pin&amp;quot; / &amp;quot;# led = Pin(2, Pin.OUT)&amp;quot; / &amp;quot;# led.value(1)  # turn LED on&amp;quot; / &amp;quot;# led.value(0)  # turn LED off&amp;quot;. Below a horizontal divider is the output: &amp;quot;Hello from MicroPython!&amp;quot; / &amp;quot;First 10 Fibonacci numbers: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]&amp;quot;, followed by a &amp;quot;▶ Result: execute_micropython&amp;quot; label. At the bottom is a text input field with placeholder &amp;quot;Type a message...&amp;quot; and a blue &amp;quot;Send&amp;quot; button." /&gt;&lt;/p&gt;

&lt;h4 id="should-you-trust-my-vibe-coded-sandbox-"&gt;Should you trust my vibe-coded sandbox?&lt;/h4&gt;
&lt;p&gt;Having complained about immature, loosely-maintained sandboxing libraries, it's deeply ironic that I've now built my own!&lt;/p&gt;
&lt;p&gt;I deliberately slapped an alpha release version on it, and I'm not ready to recommend it to anyone who isn't willing to take a significant risk.&lt;/p&gt;
&lt;p&gt;I've put it through enough testing that I'm OK using it myself. I've shipped my first plugin that uses it, &lt;a href="https://github.com/datasette/datasette-agent-micropython"&gt;datasette-agent-micropython&lt;/a&gt;. I've also locked GPT-5.5 xhigh in that Datasette Agent plugin and &lt;a href="https://gist.github.com/simonw/5de497c44d25f9fd459c8aa2c959fe4a"&gt;challenged it to break out of the sandbox&lt;/a&gt; and so far it has not managed to.&lt;/p&gt;
&lt;p&gt;I'm hoping this implementation can convince some companies with professional security teams and high-stakes problems to commit to using Python in WebAssembly as a sandboxing approach and open source their own solutions.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sandboxing"&gt;sandboxing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/codex"&gt;codex&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette-agent"&gt;datasette-agent&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/micropython"&gt;micropython&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="python"/><category term="sandboxing"/><category term="ai"/><category term="datasette"/><category term="webassembly"/><category term="generative-ai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="codex"/><category term="datasette-agent"/><category term="micropython"/></entry><entry><title>datasette-agent-micropython 0.1a0</title><link href="https://simonwillison.net/2026/Jun/2/datasette-agent-micropython/#atom-tag" rel="alternate"/><published>2026-06-02T19:28:36+00:00</published><updated>2026-06-02T19:28:36+00:00</updated><id>https://simonwillison.net/2026/Jun/2/datasette-agent-micropython/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-agent-micropython/releases/tag/0.1a0"&gt;datasette-agent-micropython 0.1a0&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;I want &lt;a href="https://agent.datasette.io"&gt;Datasette Agent&lt;/a&gt; to be able to generate and execute Python code safely. This alpha is looking promising so far. GPT-5.5 has so far failed to break out of the sandbox!&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sandboxing"&gt;sandboxing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette-agent"&gt;datasette-agent&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/micropython"&gt;micropython&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="python"/><category term="sandboxing"/><category term="datasette"/><category term="webassembly"/><category term="datasette-agent"/><category term="micropython"/></entry><entry><title>micropython-wasm 0.1a1</title><link href="https://simonwillison.net/2026/Jun/2/micropython-wasm/#atom-tag" rel="alternate"/><published>2026-06-02T19:20:47+00:00</published><updated>2026-06-02T19:20:47+00:00</updated><id>https://simonwillison.net/2026/Jun/2/micropython-wasm/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/micropython-wasm/releases/tag/0.1a1"&gt;micropython-wasm 0.1a1&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;Fixes for some limitations that emerged while I was trying to use this to build &lt;code&gt;datasette-agent-micropython&lt;/code&gt;.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sandboxing"&gt;sandboxing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/micropython"&gt;micropython&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="python"/><category term="sandboxing"/><category term="webassembly"/><category term="micropython"/></entry><entry><title>micropython-wasm 0.1a0</title><link href="https://simonwillison.net/2026/Jun/2/micropython-wasm-2/#atom-tag" rel="alternate"/><published>2026-06-02T03:43:45+00:00</published><updated>2026-06-02T03:43:45+00:00</updated><id>https://simonwillison.net/2026/Jun/2/micropython-wasm-2/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/micropython-wasm/releases/tag/0.1a0"&gt;micropython-wasm 0.1a0&lt;/a&gt;&lt;/p&gt;
        &lt;p&gt;My latest sandboxing experiment: This alpha package bundles a lightly customized WASM build of &lt;a href="https://micropython.org/"&gt;MicroPython&lt;/a&gt; with a wrapper to execute code in it via &lt;a href="https://wasmtime.dev/"&gt;wasmtime&lt;/a&gt;.&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sandboxing"&gt;sandboxing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/micropython"&gt;micropython&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="python"/><category term="sandboxing"/><category term="webassembly"/><category term="micropython"/></entry><entry><title>Hacking the WiFi-enabled color screen GitHub Universe conference badge</title><link href="https://simonwillison.net/2025/Oct/28/github-universe-badge/#atom-tag" rel="alternate"/><published>2025-10-28T17:17:44+00:00</published><updated>2025-10-28T17:17:44+00:00</updated><id>https://simonwillison.net/2025/Oct/28/github-universe-badge/#atom-tag</id><summary type="html">
    &lt;p&gt;I'm at &lt;a href="https://githubuniverse.com/"&gt;GitHub Universe&lt;/a&gt; this week (thanks to a free ticket from Microsoft). Yesterday I picked up my conference badge... which incorporates a &lt;s&gt;full Raspberry Pi&lt;/s&gt; Raspberry Pi Pico microcontroller with a battery, color screen, WiFi and bluetooth.&lt;/p&gt;
&lt;p&gt;GitHub Universe has a tradition of hackable conference badges - the badge last year had an eInk display. This year's is a huge upgrade though - a color screen and WiFI connection makes this thing a genuinely useful little computer!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/gitub-universe-badge.jpg" alt="Photo of the badge - it has a color screen with six app icons" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;The only thing it's missing is a keyboard - the device instead provides five buttons total - Up, Down, A, B, C. It might be possible to get a bluetooth keyboard to work though I'll believe that when I see it - there's not a lot of space on this device for a keyboard driver.&lt;/p&gt;
&lt;p&gt;Everything is written using MicroPython, and the device is designed to be hackable: connect it to a laptop with a USB-C cable and you can start modifying the code directly on the device.&lt;/p&gt;
&lt;h4 id="getting-setup-with-the-badge"&gt;Getting setup with the badge&lt;/h4&gt;
&lt;p&gt;Out of the box the badge will play an opening animation (implemented as a sequence of PNG image frames) and then show a home screen with six app icons.&lt;/p&gt;
&lt;p&gt;The default apps are mostly neat Octocat-themed demos: a flappy-bird clone, a tamagotchi-style pet, a drawing app that works like an etch-a-sketch, an IR scavenger hunt for the conference venue itself (this thing has an IR sensor too!), and a gallery app showing some images.&lt;/p&gt;
&lt;p&gt;The sixth app is a badge app. This will show your GitHub profile image and some basic stats, but will only work if you dig out a USB-C cable and make some edits to the files on the badge directly.&lt;/p&gt;
&lt;p&gt;I did this on a Mac. I plugged a USB-C cable into the badge which caused MacOS to treat it as an attached drive volume. In that drive are several files including &lt;code&gt;secrets.py&lt;/code&gt;. Open that up, confirm the WiFi details are correct and add your GitHub username. The file should look like this:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;WIFI_SSID&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;"..."&lt;/span&gt;
&lt;span class="pl-c1"&gt;WIFI_PASSWORD&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;"..."&lt;/span&gt;
&lt;span class="pl-c1"&gt;GITHUB_USERNAME&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;"simonw"&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;The badge comes with the SSID and password for the GitHub Universe WiFi network pre-populated.&lt;/p&gt;
&lt;p&gt;That's it! Unmount the disk, hit the reboot button on the back of the badge and when it comes back up again the badge app should look something like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/badge-profile.jpg" alt="Badge shows my GitHub avatar, plus 10,947 followers, 4,083 contribs, 893 repos" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="building-your-own-apps"&gt;Building your own apps&lt;/h4&gt;
&lt;p&gt;Here's &lt;a href="https://badger.github.io/"&gt;the official documentation&lt;/a&gt; for building software for the badge.&lt;/p&gt;
&lt;p&gt;When I got mine yesterday the official repo had not yet been updated, so I had to figure this out myself.&lt;/p&gt;
&lt;p&gt;I copied all of the code across to my laptop, added it to a Git repo and then fired up Claude Code and told it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Investigate this code and add a detailed README&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/simonw/github-universe-2025-badge/blob/15773c7a53275e7836216c3aa9a8a781c06f7859/README.md"&gt;the result&lt;/a&gt;, which was really useful for getting a start on understanding how it all worked.&lt;/p&gt;
&lt;p&gt;Each of the six default apps lives in a &lt;code&gt;apps/&lt;/code&gt; folder, for example &lt;a href="https://github.com/simonw/github-universe-2025-badge/tree/main/apps/sketch"&gt;apps/sketch/&lt;/a&gt; for the sketching app.&lt;/p&gt;
&lt;p&gt;There's also a menu app which powers the home screen. That lives in &lt;a href="https://github.com/simonw/github-universe-2025-badge/tree/main/apps/menu"&gt;apps/menu/&lt;/a&gt;. You can edit code in here to add new apps that you create to that screen.&lt;/p&gt;
&lt;p&gt;I told Claude:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Add a new app to it available from the menu which shows network status and other useful debug info about the machine it is running on&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This was a bit of a long-shot, but it totally worked!&lt;/p&gt;
&lt;p&gt;The first version had an error:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/badge-error.jpg" alt="A stacktrace! file badgeware.py line 510 has a list index out of range error." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;I OCRd that photo (with the Apple Photos app) and pasted the message into Claude Code and it fixed the problem.&lt;/p&gt;
&lt;p&gt;This almost worked... but the addition of a seventh icon to the 2x3 grid meant that you could select the icon but it didn't scroll into view. I had Claude &lt;a href="https://github.com/simonw/github-universe-2025-badge/commit/2a60f75db101dc1dc7568ff466ad5c97dc86b336"&gt;fix that for me too&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here's the code for &lt;a href="https://github.com/simonw/github-universe-2025-badge/blob/main/apps/debug/__init__.py"&gt;apps/debug/__init__.py&lt;/a&gt;, and &lt;a href="https://gistpreview.github.io/?276d3e0c6566ddbc93adc7020ef6b439"&gt;the full Claude Code transcript&lt;/a&gt; created using my terminal-to-HTML app &lt;a href="https://simonwillison.net/2025/Oct/23/claude-code-for-web-video/"&gt;described here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here are the four screens of the debug app:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/badge-debug-network.jpg" alt="Network info, showing WiFi network details and IP address" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/badge-debug-storage.jpg" alt="Storage screen, it has 1MB total, 72BK used. Usage 7%. CMD is /system/apps/debug" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/badge-debug-system.jpg" alt="System: Platform rp2, Python 1.26.0, CPU freq 200MHz, Uptime 13m46s" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/badge-debug-memory.jpg" alt="Memory info - 100KB used, 241KB total, and a usage bar. Press B to run GC." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="an-icon-editor"&gt;An icon editor&lt;/h4&gt;
&lt;p&gt;The icons used on the app are 24x24 pixels. I decided it would be neat to have a web app that helps build those icons, including the ability to start by creating an icon from an emoji.&lt;/p&gt;
&lt;p&gt;I bulit this one &lt;a href="https://claude.ai/share/ca05bd58-859e-4ceb-b5c7-7428b348df3c"&gt;using Claude Artifacts&lt;/a&gt;. Here's the result, now available at &lt;a href="https://tools.simonwillison.net/icon-editor"&gt;tools.simonwillison.net/icon-editor&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/icon-editor.jpg" alt="A stacktrace! file badgeware.py line 510 has a list index out of range error." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="and-a-repl"&gt;And a REPL&lt;/h4&gt;
&lt;p&gt;I noticed that last year's badge configuration app (which I can't find in &lt;a href="https://github.com/badger/badger.github.io/"&gt;github.com/badger/badger.github.io&lt;/a&gt; any more, I think they reset the history on that repo?) worked by talking to MicroPython over the Web Serial API from Chrome. Here's &lt;a href="https://github.com/simonw/2004-badger.github.io/blob/e3501d631a987bfbc12d93c9e35bf2c64e55d052/public/script.js#L305-L394"&gt;my archived copy of that code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Wouldn't it be useful to have a REPL in a web UI that you could use to interact with the badge directly over USB?&lt;/p&gt;
&lt;p&gt;I pointed Claude Code at a copy of that repo and told it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Based on this build a new HTML with inline JavaScript page that uses WebUSB to simply test that the connection to the badge works and then list files on that device using the same mechanism&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It took a bit of poking (here's &lt;a href="https://gistpreview.github.io/?13d93a9e3b0ce1c921cd20303f2f1d84"&gt;the transcript&lt;/a&gt;) but the result is now live at &lt;a href="https://tools.simonwillison.net/badge-repl"&gt;tools.simonwillison.net/badge-repl&lt;/a&gt;. It only works in Chrome - you'll need to plug the badge in with a USB-C cable and then click "Connect to Badge".&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/badge-repl.jpg" alt="Badge Interactive REPL. Note: This tool requires the Web Serial API (Chrome/Edge on desktop). Connect to Badge, Disconnect and Clear Terminal buttons. Then a REPL interface displaying: Ready to connect. Click &amp;quot;Connect to Badge&amp;quot; to start.Traceback (most recent call last):ddae88e91.dirty on 2025-10-20; GitHub Badger with RP2350 Type &amp;quot;help()&amp;quot; for more information.  &amp;gt;&amp;gt;&amp;gt;  MicroPython v1.14-5485.gddae88e91.dirty on 2025-10-20; GitHub Badger with RP2350 Type &amp;quot;help()&amp;quot; for more information. &amp;gt;&amp;gt;&amp;gt; os.listdir() ['icon.py', 'ui.py', 'init.py', '._init.py', '._icon.py'] &amp;gt;&amp;gt;&amp;gt; machine.freq() 200000000 &amp;gt;&amp;gt;&amp;gt; gc.mem_free() 159696 &amp;gt;&amp;gt;&amp;gt; help() Welcome to MicroPython!" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="get-hacking"&gt;Get hacking&lt;/h4&gt;
&lt;p&gt;If you're a GitHub Universe attendee I hope this is useful. The official &lt;a href="https://badger.github.io/"&gt;badger.github.io&lt;/a&gt; site has plenty more details to help you get started.&lt;/p&gt;
&lt;p&gt;There isn't yet a way to get hold of this hardware outside of GitHub Universe - I know they had some supply chain challenges just getting enough badges for the conference attendees!&lt;/p&gt;
&lt;p&gt;It's a very neat device, built for GitHub by &lt;a href="https://www.pimoroni.com/"&gt;Pimoroni&lt;/a&gt; in Sheffield, UK. A version of this should become generally available in the future under the name "Pimoroni Tufty 2350".&lt;/p&gt;

&lt;h4 id="iphone-only"&gt;Update: Setup with iPhone only&lt;/h4&gt;

&lt;p&gt;If you don't have a laptop with you it's still possible to start hacking on the device using just a USB-C cable.&lt;/p&gt;

&lt;p&gt;Plug the badge into the phone, hit the reset button on the back twice to switch it into disk mode and open the iPhone Files app - the badge should appear as a mounted disk called BADGER.&lt;/p&gt;

&lt;p&gt;I used &lt;a href="https://apps.apple.com/us/app/textastic-code-editor/id1049254261"&gt;Textastic&lt;/a&gt; to edit that &lt;code&gt;secrets.py&lt;/code&gt; and configure a new badge, then hit reset again to restart it.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/hardware-hacking"&gt;hardware-hacking&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/microsoft"&gt;microsoft&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/raspberry-pi"&gt;raspberry-pi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude-code"&gt;claude-code&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/disclosures"&gt;disclosures&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/micropython"&gt;micropython&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="github"/><category term="hardware-hacking"/><category term="microsoft"/><category term="ai"/><category term="generative-ai"/><category term="raspberry-pi"/><category term="llms"/><category term="claude-code"/><category term="disclosures"/><category term="micropython"/></entry><entry><title>Badge Interactive REPL</title><link href="https://simonwillison.net/2025/Oct/27/badge-repl/#atom-tag" rel="alternate"/><published>2025-10-27T22:54:02+00:00</published><updated>2025-10-27T22:54:02+00:00</updated><id>https://simonwillison.net/2025/Oct/27/badge-repl/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Tool:&lt;/strong&gt; &lt;a href="https://tools.simonwillison.net/badge-repl"&gt;Badge Interactive REPL&lt;/a&gt;&lt;/p&gt;
        
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/micropython"&gt;micropython&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="micropython"/></entry><entry><title>MicroPython Code Executor</title><link href="https://simonwillison.net/2025/Sep/25/micropython/#atom-tag" rel="alternate"/><published>2025-09-25T15:44:23+00:00</published><updated>2025-09-25T15:44:23+00:00</updated><id>https://simonwillison.net/2025/Sep/25/micropython/#atom-tag</id><summary type="html">
    
        &lt;p&gt;&lt;strong&gt;Tool:&lt;/strong&gt; &lt;a href="https://tools.simonwillison.net/micropython"&gt;MicroPython Code Executor&lt;/a&gt;&lt;/p&gt;
        
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/micropython"&gt;micropython&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="webassembly"/><category term="micropython"/></entry><entry><title>PyScript Updates: Bytecode Alliance, Pyodide, and MicroPython</title><link href="https://simonwillison.net/2022/Nov/9/pyscript-updates-bytecode-alliance-pyodide-and-micropython/#atom-tag" rel="alternate"/><published>2022-11-09T22:26:09+00:00</published><updated>2022-11-09T22:26:09+00:00</updated><id>https://simonwillison.net/2022/Nov/9/pyscript-updates-bytecode-alliance-pyodide-and-micropython/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.anaconda.com/blog/pyscript-updates-bytecode-alliance-pyodide-and-micropython"&gt;PyScript Updates: Bytecode Alliance, Pyodide, and MicroPython&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Absolutely huge news about Python on the Web tucked into this announcement: Anaconda have managed to get a version of MicroPython compiled to WebAssembly running in the browser. Pyodide weighs in at around 6.5MB compressed, but the MicroPython build is just 303KB—the size of a large image. This makes Python in the web browser applicable to so many more potential areas.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pyodide"&gt;pyodide&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/micropython"&gt;micropython&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="webassembly"/><category term="pyodide"/><category term="micropython"/></entry></feed>