Pre(r)amble
I’m in a strange place in my head with respect to coding agents. On one hand I see all the really dumb stuff that “intelligent” chatbots produce, on the other hand, I’ve used them myself to turn something from “I should do that sometime when I’m motivated” into something I can actually use. It’s a weird time to be a teacher, let alone one of Digital Technologies.
Anyhow, this post (and hopefully some future ones) is an attempt to use AI to help shift some projects out of my head and backlog, and try and get them made. I’m aware of some of the things I found with my first AI coding experiment, in that rather than necessarily turning me into a “10x Engineer”, in fact it can turn me into a “0.95x Engineer”, getting me 95% of the way there and then getting stuck. The wonderful Cory Doctorow had a turn of phrase “reverse centaur” in this post, referring to when humans are forced to use AI. I loved the term, but probably not for the reason it was intended. I feel that when you’re being a centaur using AI agents, sometimes you’re the human, and sometimes you’re the horse(’s arse).
The point of the post
Back when I worked for Grok (no, not that one), lots of students logged in to learn to code, and used the platform as the place to do it. Lots of front-end editing, back-end execution, front-end reporting. There was a lot going on in terms of keep tracking of student progress, reporting that back to teachers, and so on, but the coding feedback loop was always something that was really interesting.
Students got light syntax/pattern-based feedback as they wrote their code (generally in Python), then ran their code, and got effectively unit test based feedback on execution. We served students all over Australia (and internationally) and some of the regions are really remote without the best Internet connections. Bad network connections could lead to bad experiences since there was a lot of back and forth communication - so what if we could do almost everything client-side?
WASM ports of Python (through things like Pyodide and Pyscript) have been getting more love, and there are some really neat things that use it. A couple that really made me pay attention were:
- PySheets - a local spreadsheets app
- Marimo WebAssembly notebooks - a Python notebook similar (but also not very similar) to Jupyter
The project
The project is to create a “learn Python” style page that:
- Lets students write and save their code locally (using browser storage for snapshots)
- Be configurable to present students with coding problems to solve, instructions, and sample files to work with
- Uses JavaScript locally to give students edit-time feedback on what they’re doing, both with pattern matching and static analysis
- Unit test student code locally (TBH I’m still not entirely sure how this one will work 😅)
- Will run on a vanilla HTTP server with no server-end architecture beyond hosting files
I’ve been noodling around with this for a week or so now, and currently have:
- A pleasantly utilitarian page for instructions, feedback, and editing
- Browser-based code snapshots, which are tied to configured problems and versions
- Locally running user-edited Python code that runs in a Micropython WASM
- A lightly configured CodeMirror editor that will highlight runtime errors
:=
your way around). You don’t get the library
ecosystem, but it’s a tradeoff in terms of making it pretty snappy.

The weeds
Some technical stuff about what has had to be solved so far. It’s been interesting doing this with an agent, because as I’ve said before, they do some really dumb things. I’ve got a background in Computer Science, but I have also spent my career teaching which has pretty much killed off any deeply technical experience I had as I spent years covering surface level concepts over and over again.
Still, I can generally see when the agent is doing something stupid, and have a general feel for some potential solutions, even if I don’t have the experience to implement them myself. So far there’s been a lot of fairly mundane issues, mostly related to my eyes glazing over whenever I see JS code about promise resolution, but there have been a couple of interesting technical hurdles to overcome.
Blocking code
Students write a lot of blocking code like input()
calls, loops using time.sleep()
, and tight loops
where they don’t really think too hard about how to break out of the loop before they run the program.
This presents a real problem for code running in Micropython in the browser, because it all runs
synchronously in the VM, locking you out until it’s finished executing or raises an error.
If you’re trying to use JS to poke at that code? You’re out of luck until it’s good and ready.
input()
The most obvious problem to tackle first was input()
which has two problems:
- Where’s
stdin
coming from as a VM running inside the browser?!? input()
is blocking, so how do we get some input from JS in to Python if it’s stopping us from doing anything?
AI suggestions for answering this problem mostly involved rewriting student code and wrapping everything
in async
/await
, which sort of works, but is incredibly fragile since you’re rewriting code blocks
all over the place. An input
call inside a loop or selection? BOOM, have a SyntaxError
for your trouble.
Also it makes reporting tracebacks to the student a mission because now you’re undoing all that code
munging to give them the right line numbers.
The second suggestion from AI was to use asyncify
which lets you jump in and out of VM execution preserving state. The trouble is, input
is still a
blocking function and I didn’t want to rewrite student code.
The solution to when input
is the problem? Edit what input
does, recompile the Micropython WASM
port (with asyncify), and we’re off to the races! My solution was that input
doesn’t actually have do be input
in my version of Python, so we turned it into something else that just acts as something that yields
control and lets us use JS for getting user input.
Loops
Infinite loops suck. When you’re running your own interactive program though, you can just hit the good old ^C and interrupt it. In WASM land though? Oh no, you’re waiting for the browser to notice that not much is happening because you’re locked out.
What about loops that aren’t infinite, but are just busy sleep
ing? Well that’s a problem too, because
time.sleep()
is another blocking function we can’t break into.
The solution to both of these? Yep, time to recompile Micropython again, this time to make both loop
iterations and time.sleep()
yield control back so we can send it an interrupt and SIGINT our way out
of there. I’m sure there are performance implications for doing this, but again, this is supposed to be
a toy environment to learn in, not for production.
So far this is where I’m at. I have a client-side environment where you can write and run Python code that a student would typically write, and a virtual filesystem backed by browser storage and a basic snapshot system for version control.
I’ve got configuration implemented in a barebones way so that you can pass in instructions, starter code, files into the filesystem, and entry points for pattern matching and AST-based feedback. This will probably be the next thing to focus on.
Because this is a pretty volatile project (see the “with AI” part) I’ll put the code on GitHub when it’s less likely to get rewrites as I discover some major architectural change is necessary (happened a couple of times so far 🤣).