Pre(r)amble
This is part of a series of posts documenting some of the process of (building|cat herding an AI agent to build) Clipy, an easily hosted Python teaching tool built with just front-end JS and a WASM port of MicroPython.
You can find the GitHub repo for the project here
I keep a relatively up to date version here if you want to try it
What we had before
Last week was adding the record-replay feature (lots more about that this week, what a rabbit hole) and putting in nicer file management (which still needs some tweaks, but I’m pretty happy with it).
Read about it in the post from last time
What’s happened since
This week is mostly about the record-replay feature. There’s some info about the change in implementation, but here’s a similar demo to the one from last week using multiple files.
You can try this demo out for yourself here or just write your own code and try stuff out. If you find any interesting edge cases, let me know in an issue on GitHub
Record-Replay
Basically this whole week has been discovering hairy edge cases with the mongrel way that the record-replay feature was implemented (through a bunch of Python instrumentation and code transforms). From the get-go it was difficult to do things that way because any time the code was transformed it needed to play nicely with things like tracebacks so the user got information about their code and not the wrapped version.
The more sensible method of course would just to be use something like Python’s
sys.settrace
but the MicroPython WASM
port didn’t have that available, and apparently I didn’t learn my lesson from the very start of this
project when I just routed around the blocking code issue by putting in hacks in the runtime. It
took a few days of bashing my head against successive edge cases where the instrumentation approach
broke to come around to “let’s just go modify the runtime source again”.
I’m not going to lie, I honestly have no idea what the agent did with most of the underlying C in MicroPython. The moment you start really talking about how the bytecode works and the optmisations for how locals work I very much glaze over. In the end, I think what we’ve got works, and although it occasionally looks like it breaks, it’s almost always the front-end code doing the breaking.
The trouble is, debuggers like to tell you what the state is before something happens, and the replay is intended to show what what did happen. This led to some, uh, interesting situations when we had a value being assigned from an expression of a function call, since the trace doesn’t know what it is yet, and the AST can’t evaluate the expression.
Traditional debugger:
msg = "hi" # nothing to see on this line
print(msg) # now msg has a value
Record-Replay:
msg = "hi" # show that on this line we gave `msg` the value "hi"
print(msg) # show on this line the value of `msg` that we used was "hi"
This led to some creative problem solving using lookaheads (which of course broke loads of times, and no doubt will again in the future). Loops also have some… interesting issues, like the trace not seeing some of the conditions as they seem to be optimised away somewhere, the final iteration of a loop with no code after it not having anything to look ahead to, and so on. Fun times.
Lastly, my choice of MicroPython made some things pretty difficult as locals get captured as anonymous slots, which then require a combination of extracting the slot values and re-associating them with their names using the AST in order to show the user what’s going on in a function, look at function parameters, and so on.
This one is still a work in progress as I keep finding little edge cases that behave strangely with my intended replay model like phantom lines of execution in loops. In recording a demo video for this post I ended up falling down a rabbit hole that ended up back in the MicroPython source again that isn’t resolved yet.
(A)Introspection
I’ve been reading a bit about MCP over the last couple of months, and I was coincidentally teaching some of my students about project tracking tools and signed up for a free Jira account. I noticed that Atlassian have an MCP server and thought “Why not?”. So I spent a bit of time writing up issues for my agents to follow instead of using the chat interface as much, which, whilst being kinda weird, actually worked pretty well. Feels super weird having two agents who both use my creds have a conversation as the “MicroPython dev” talks to the “Front-end dev”.
I’ve also been playing around with a couple of the chat modes from AwesomeCopilot like the Implementation Plan mode, which has been interesting. Feels a bit strange switching between different chat modes, and I’m still not super convinced of the effectiveness, but I’ll keep at it.